3 from sfa.util.faults import *
4 from sfa.rspecs.aggregates.vini.topology import *
8 <endpoint>i2atla1</endpoint>
9 <endpoint>i2chic1</endpoint>
13 <endpoint>i2atla1</endpoint>
14 <endpoint>i2hous1</endpoint>
18 <endpoint>i2atla1</endpoint>
19 <endpoint>i2wash1</endpoint>
23 <endpoint>i2chic1</endpoint>
24 <endpoint>i2kans1</endpoint>
28 <endpoint>i2chic1</endpoint>
29 <endpoint>i2wash1</endpoint>
33 <endpoint>i2hous1</endpoint>
34 <endpoint>i2kans1</endpoint>
38 <endpoint>i2hous1</endpoint>
39 <endpoint>i2losa1</endpoint>
43 <endpoint>i2kans1</endpoint>
44 <endpoint>i2salt1</endpoint>
48 <endpoint>i2losa1</endpoint>
49 <endpoint>i2salt1</endpoint>
53 <endpoint>i2losa1</endpoint>
54 <endpoint>i2seat1</endpoint>
58 <endpoint>i2newy1</endpoint>
59 <endpoint>i2wash1</endpoint>
63 <endpoint>i2salt1</endpoint>
64 <endpoint>i2seat1</endpoint>
68 # Taken from bwlimit.py
70 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
71 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
72 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
73 # "kibit" and that if an older version is installed, all rates will
74 # be off by a small fraction.
82 "gibit": 1024*1024*1024,
84 "tibit": 1024*1024*1024*1024,
85 "tbit": 1000000000000,
91 "gibps": 8*1024*1024*1024,
93 "tibps": 8*1024*1024*1024*1024,
100 Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
105 m = re.match(r"([0-9.]+)(\D*)", s)
108 suffix = m.group(2).lower()
109 if suffixes.has_key(suffix):
110 return int(float(m.group(1)) * suffixes[suffix])
114 def format_tc_rate(rate):
116 Formats a bits/second rate into a tc rate string
119 if rate >= 1000000000 and (rate % 1000000000) == 0:
120 return "%.0fgbit" % (rate / 1000000000.)
121 elif rate >= 1000000 and (rate % 1000000) == 0:
122 return "%.0fmbit" % (rate / 1000000.)
124 return "%.0fkbit" % (rate / 1000.)
126 return "%.0fbit" % rate
130 def __init__(self, node, bps = 1000 * 1000000):
131 self.id = node['node_id']
132 self.hostname = node['hostname']
133 self.shortname = self.hostname.replace('.vini-veritas.net', '')
134 self.site_id = node['site_id']
135 self.ipaddr = socket.gethostbyname(self.hostname)
139 def get_link_id(self, remote):
140 if self.id < remote.id:
141 link = (self.id<<7) + remote.id
143 link = (remote.id<<7) + self.id
146 def get_iface_id(self, remote):
147 if self.id < remote.id:
153 def get_virt_ip(self, remote):
154 link = self.get_link_id(remote)
155 iface = self.get_iface_id(remote)
157 second = ((link & 0x3f)<<2) + iface
158 return "192.168.%d.%d" % (first, second)
160 def get_virt_net(self, remote):
161 link = self.get_link_id(remote)
163 second = (link & 0x3f)<<2
164 return "192.168.%d.%d/30" % (first, second)
166 def get_site(self, sites):
167 return sites[self.site_id]
169 def get_topo_rspec(self, link):
170 if link.end1 == self:
172 elif link.end2 == self:
175 raise Error("Link does not connect to Node")
177 my_ip = self.get_virt_ip(remote)
178 remote_ip = remote.get_virt_ip(self)
179 net = self.get_virt_net(remote)
180 bw = format_tc_rate(link.bps)
181 return (remote.id, remote.ipaddr, bw, my_ip, remote_ip, net)
183 def add_link(self, link):
186 def add_tag(self, sites):
187 s = self.get_site(sites)
188 words = self.hostname.split(".")
189 index = words[0].replace("node", "")
191 self.tag = s.tag + index
195 # Assumes there is at most one Link between two sites
196 def get_sitelink(self, node, sites):
197 site1 = sites[self.site_id]
198 site2 = sites[node.site_id]
199 sl = site1.links.intersection(site2.links)
206 def __init__(self, end1, end2, bps = 1000 * 1000000):
216 def __init__(self, site):
217 self.id = site['site_id']
218 self.node_ids = site['node_ids']
219 self.name = site['abbreviated_name'].replace(" ", "_")
220 self.tag = site['login_base']
221 self.public = site['is_public']
224 def get_sitenodes(self, nodes):
226 for i in self.node_ids:
230 def add_link(self, link):
235 def __init__(self, slice):
236 self.id = slice['slice_id']
237 self.name = slice['name']
238 self.node_ids = set(slice['node_ids'])
239 self.slice_tag_ids = slice['slice_tag_ids']
241 def get_tag(self, tagname, slicetags, node = None):
242 for i in self.slice_tag_ids:
244 if tag.tagname == tagname:
245 if (not node) or (node.id == tag.node_id):
250 def get_nodes(self, nodes):
252 for id in self.node_ids:
257 # Add a new slice tag
258 def add_tag(self, tagname, value, slicetags, node = None):
259 record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
261 record['node_id'] = node.id
263 record['node_id'] = None
264 tag = Slicetag(record)
265 slicetags[tag.id] = tag
266 self.slice_tag_ids.append(tag.id)
271 # Update a slice tag if it exists, else add it
272 def update_tag(self, tagname, value, slicetags, node = None):
273 tag = self.get_tag(tagname, slicetags, node)
274 if tag and tag.value == value:
280 tag = self.add_tag(tagname, value, slicetags, node)
283 def assign_egre_key(self, slicetags):
284 if not self.get_tag('egre_key', slicetags):
286 key = free_egre_key(slicetags)
287 self.update_tag('egre_key', key, slicetags)
289 # Should handle this case...
293 def turn_on_netns(self, slicetags):
294 tag = self.get_tag('netns', slicetags)
295 if (not tag) or (tag.value != '1'):
296 self.update_tag('netns', '1', slicetags)
299 def turn_off_netns(self, slicetags):
300 tag = self.get_tag('netns', slicetags)
301 if tag and (tag.value != '0'):
305 def add_cap_net_admin(self, slicetags):
306 tag = self.get_tag('capabilities', slicetags)
308 caps = tag.value.split(',')
310 if cap == "CAP_NET_ADMIN":
313 newcaps = "CAP_NET_ADMIN," + tag.value
314 self.update_tag('capabilities', newcaps, slicetags)
316 self.add_tag('capabilities', 'CAP_NET_ADMIN', slicetags)
319 def remove_cap_net_admin(self, slicetags):
320 tag = self.get_tag('capabilities', slicetags)
322 caps = tag.value.split(',')
325 if cap != "CAP_NET_ADMIN":
328 value = ','.join(newcaps)
329 self.update_tag('capabilities', value, slicetags)
334 # Update the vsys/setup-link and vsys/setup-nat slice tags.
335 def add_vsys_tags(self, slicetags):
337 for i in self.slice_tag_ids:
339 if tag.tagname == 'vsys':
340 if tag.value == 'setup-link':
342 elif tag.value == 'setup-nat':
345 self.add_tag('vsys', 'setup-link', slicetags)
347 self.add_tag('vsys', 'setup-nat', slicetags)
353 def __init__(self, tag):
354 self.id = tag['slice_tag_id']
356 # Make one up for the time being...
357 self.id = Slicetag.newid
359 self.slice_id = tag['slice_id']
360 self.tagname = tag['tagname']
361 self.value = tag['value']
362 self.node_id = tag['node_id']
367 # Mark a tag as deleted
372 def write(self, api):
375 api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
377 api.plshell.AddSliceTag(api.plauth, self.slice_id,
378 self.tagname, self.value, self.node_id)
379 elif self.deleted and int(self.id) > 0:
380 api.plshell.DeleteSliceTag(api.plauth, self.id)
384 A topology is a compound object consisting of:
385 * a dictionary mapping site IDs to Site objects
386 * a dictionary mapping node IDs to Node objects
387 * the Site objects are connected via SiteLink objects representing
388 the physical topology and available bandwidth
389 * the Node objects are connected via Link objects representing
390 the requested or assigned virtual topology of a slice
393 def __init__(self, api):
395 self.sites = get_sites(api)
396 self.nodes = get_nodes(api)
397 self.tags = get_slice_tags(api)
401 for (s1, s2) in PhysicalLinks:
402 self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
404 for id in self.nodes:
405 self.nodes[id].add_tag(self.sites)
409 if tag.tagname == 'topo_rspec':
410 node1 = self.nodes[tag.node_id]
412 for (id, realip, bw, lvip, rvip, vnet) in l:
413 allocbps = get_tc_rate(bw)
414 node1.bps -= allocbps
416 node2 = self.nodes[id]
417 if node1.id < node2.id:
418 sl = node1.get_sitelink(node2, self.sites)
424 def lookupSite(self, id):
429 raise KeyError("site ID %s not found" % id)
435 sites.append(self.sites[s])
438 def lookupNode(self, id):
443 raise KeyError("node ID %s not found" % id)
449 nodes.append(self.nodes[n])
452 def nodesInTopo(self):
455 if self.nodes[n].links:
456 nodes.append(self.nodes[n])
459 def lookupSliceTag(self, id):
464 raise KeyError("slicetag ID %s not found" % id)
467 def getSliceTags(self):
470 tags.append(self.tags[t])
473 def lookupSiteLink(self, node1, node2):
474 site1 = self.sites[node1.site_id]
475 site2 = self.sites[node2.site_id]
476 for link in self.sitelinks:
477 if site1 == link.end1 and site2 == link.end2:
479 if site2 == link.end1 and site1 == link.end2:
483 def nodeTopoFromRspec(self, rspec):
485 raise Error("virtual topology already present")
487 rspecdict = rspec.toDict()
489 for node in self.getNodes():
490 nodedict[node.tag] = node
492 linkspecs = rspecdict['Rspec']['Request'][0]['NetSpec'][0]['LinkSpec']
494 n1 = nodedict[l['endpoint'][0]]
495 n2 = nodedict[l['endpoint'][1]]
496 bps = get_tc_rate(l['bw'][0])
497 self.nodelinks.append(Link(n1, n2, bps))
499 def nodeTopoFromSliceTags(self, slice):
501 raise Error("virtual topology already present")
503 for node in slice.get_nodes(self.nodes):
504 linktag = slice.get_tag('topo_rspec', self.tags, node)
506 l = eval(linktag.value)
507 for (id, realip, bw, lvip, rvip, vnet) in l:
509 bps = get_tc_rate(bw)
510 remote = self.lookupNode(id)
511 self.nodelinks.append(Link(node, remote, bps))
513 def updateSliceTags(self, slice):
514 if not self.nodelinks:
517 slice.update_tag('vini_topo', 'manual', self.tags)
518 slice.assign_egre_key(self.tags)
519 slice.turn_on_netns(self.tags)
520 slice.add_cap_net_admin(self.tags)
522 for node in slice.get_nodes(self.nodes):
524 for link in node.links:
525 linkdesc.append(node.get_topo_rspec(link))
527 topo_str = "%s" % linkdesc
528 slice.update_tag('topo_rspec', topo_str, self.tags, node)
530 # Update slice tags in database
531 for tag in self.getSliceTags():
532 if tag.slice_id == slice.id:
533 if tag.tagname == 'topo_rspec' and not tag.updated:
538 Check the requested topology against the available topology and capacity
540 def verifyNodeTopo(self, hrn, topo, maxbw):
541 maxbps = get_tc_rate(maxbw)
542 for link in self.nodelinks:
544 raise GeniInvalidArgument(bw, "BW")
545 if link.bps > maxbps:
546 raise PermissionError(" %s requested %s but max BW is %s" %
547 (hrn, format_tc_rate(link.bps), maxbw))
551 sitelink = self.lookupSiteLink(n1, n2)
553 raise PermissionError("%s: nodes %s and %s not adjacent" % (hrn, n1.tag, n2.tag))
554 if sitelink.bps < link.bps:
555 raise PermissionError("%s: insufficient capacity between %s and %s" % (hrn, n1.tag, n2.tag))
558 Produce XML directly from the topology specification.
560 def toxml(self, hrn = None):
561 xml = """<?xml version="1.0"?>
562 <Rspec xmlns="http://www.planet-lab.org/sfa/rspec/" name="vini">
564 <NetSpec name="physical_topology">"""
566 for site in self.getSites():
571 <SiteSpec name="%s"> """ % site.name
573 for node in site.get_sitenodes(self.nodes):
579 <hostname>%s</hostname>
581 </NodeSpec>""" % (node.tag, node.hostname, format_tc_rate(node.bps))
585 for link in self.sitelinks:
588 <endpoint>%s</endpoint>
589 <endpoint>%s</endpoint>
591 </SiteLinkSpec>""" % (link.end1.name, link.end2.name, format_tc_rate(link.bps))
597 name = 'default_topology'
602 <NetSpec name="%s">""" % name
605 for link in self.nodelinks:
608 <endpoint>%s</endpoint>
609 <endpoint>%s</endpoint>
611 </LinkSpec>""" % (link.end1.tag, link.end2.tag, format_tc_rate(link.bps))
613 xml += default_topo_xml
620 # Remove all leading whitespace and newlines
621 lines = xml.split("\n")
624 noblanks += line.strip()
629 Create a dictionary of site objects keyed by site ID
633 for site in api.plshell.GetSites(api.plauth):
634 t = site['site_id'], Site(site)
640 Create a dictionary of node objects keyed by node ID
644 for node in api.plshell.GetNodes(api.plauth):
645 t = node['node_id'], Node(node)
650 Create a dictionary of slice objects keyed by slice ID
652 def get_slice(api, slicename):
653 slice = api.plshell.GetSlices(api.plauth, [slicename])
655 return Slice(slice[0])
660 Create a dictionary of slicetag objects keyed by slice tag ID
662 def get_slice_tags(api):
664 for tag in api.plshell.GetSliceTags(api.plauth):
665 t = tag['slice_tag_id'], Slicetag(tag)
672 def free_egre_key(slicetags):
676 if tag.tagname == 'egre_key':
677 used.add(int(tag.value))
679 for i in range(1, 256):
684 raise KeyError("No more EGRE keys available")