1 from __future__ import with_statement
4 from sfa.util.namespace import *
5 from sfa.util.faults import *
6 from xmlbuilder import XMLBuilder
9 from StringIO import StringIO
11 # Taken from bwlimit.py
13 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
14 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
15 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
16 # "kibit" and that if an older version is installed, all rates will
17 # be off by a small fraction.
25 "gibit": 1024*1024*1024,
27 "tibit": 1024*1024*1024*1024,
28 "tbit": 1000000000000,
34 "gibps": 8*1024*1024*1024,
36 "tibps": 8*1024*1024*1024*1024,
43 Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
48 m = re.match(r"([0-9.]+)(\D*)", s)
51 suffix = m.group(2).lower()
52 if suffixes.has_key(suffix):
53 return int(float(m.group(1)) * suffixes[suffix])
57 def format_tc_rate(rate):
59 Formats a bits/second rate into a tc rate string
62 if rate >= 1000000000 and (rate % 1000000000) == 0:
63 return "%.0fgbit" % (rate / 1000000000.)
64 elif rate >= 1000000 and (rate % 1000000) == 0:
65 return "%.0fmbit" % (rate / 1000000.)
67 return "%.0fkbit" % (rate / 1000.)
69 return "%.0fbit" % rate
72 def __init__(self, node):
74 self.network = node.network
75 self.slice = node.network.slice
78 def read_from_tags(self):
79 self.vsys_tags = self.slice.get_tags("vsys", self.node)
81 def write_to_tags(self):
86 for tag in self.vsys_tags:
92 def __init__(self, network, iface):
93 self.network = network
94 self.id = iface['interface_id']
95 self.idtag = "i%s" % self.id
96 self.ipv4 = iface['ip']
97 self.bwlimit = iface['bwlimit']
98 self.hostname = iface['hostname']
101 Just print out bwlimit right now
103 def toxml(self, xml):
106 xml << format_tc_rate(self.bwlimit)
110 def __init__(self, network, node, bps = 1000 * 1000000):
111 self.network = network
112 self.id = node['node_id']
113 self.idtag = "n%s" % self.id
114 self.hostname = node['hostname']
115 self.name = self.shortname = self.hostname.replace('.vini-veritas.net', '')
116 self.site_id = node['site_id']
117 #self.ipaddr = socket.gethostbyname(self.hostname)
120 self.iface_ids = node['interface_ids']
121 self.iface_ids.sort()
123 self.whitelist = node['slice_ids_whitelist']
125 def get_link_id(self, remote):
126 if self.id < remote.id:
127 link = (self.id<<7) + remote.id
129 link = (remote.id<<7) + self.id
132 def get_iface_id(self, remote):
133 if self.id < remote.id:
139 def get_ifaces(self):
141 for id in self.iface_ids:
142 i.append(self.network.lookupIface(id))
143 # Only return the first interface
147 def get_virt_ip(self, remote):
148 link = self.get_link_id(remote)
149 iface = self.get_iface_id(remote)
151 second = ((link & 0x3f)<<2) + iface
152 return "192.168.%d.%d" % (first, second)
154 def get_virt_net(self, remote):
155 link = self.get_link_id(remote)
157 second = (link & 0x3f)<<2
158 return "192.168.%d.%d/30" % (first, second)
161 return self.network.lookupSite(self.site_id)
163 def get_topo_rspec(self, link):
164 if link.end1 == self:
166 elif link.end2 == self:
169 raise Error("Link does not connect to Node")
171 my_ip = self.get_virt_ip(remote)
172 remote_ip = remote.get_virt_ip(self)
173 net = self.get_virt_net(remote)
174 bw = format_tc_rate(link.bps)
175 return (remote.id, remote.ipaddr, bw, my_ip, remote_ip, net)
177 def add_link(self, link):
180 # Assumes there is at most one Link between two sites
181 def get_sitelink(self, node, sites):
182 site1 = sites[self.site_id]
183 site2 = sites[node.site_id]
184 sl = site1.links.intersection(site2.links)
189 def add_sliver(self):
190 self.sliver = Sliver(self)
192 def toxml(self, xml):
193 slice = self.network.slice
194 if self.whitelist and not self.sliver:
195 if not slice or slice.id not in self.whitelist:
198 with xml.node(id = self.idtag):
201 if self.network.type == "VINI":
203 xml << str(int(self.bps/1000))
204 for iface in self.get_ifaces():
207 self.sliver.toxml(xml)
211 def __init__(self, end1, end2, bps = 1000000000, parent = None):
222 self.parent.children.append(self)
224 def toxml(self, xml):
225 end_ids = "%s %s" % (self.end1.idtag, self.end2.idtag)
228 element = xml.vlink(endpoints=end_ids)
230 element = xml.link(endpoints=end_ids)
233 with xml.description:
234 xml << "%s -- %s" % (self.end1.name, self.end2.name)
236 xml << str(int(self.bps/1000))
237 for child in self.children:
242 def __init__(self, network, site):
243 self.network = network
244 self.id = site['site_id']
245 self.idtag = "s%s" % self.id
246 self.node_ids = site['node_ids']
248 self.name = site['abbreviated_name']
249 self.tag = site['login_base']
250 self.public = site['is_public']
251 self.enabled = site['enabled']
253 self.whitelist = False
255 def get_sitenodes(self):
257 for i in self.node_ids:
258 n.append(self.network.lookupNode(i))
261 def add_link(self, link):
264 def toxml(self, xml):
265 if not (self.public and self.enabled and self.node_ids):
267 with xml.site(id = self.idtag):
271 for node in self.get_sitenodes():
276 def __init__(self, network, hrn, slice):
278 self.network = network
279 self.id = slice['slice_id']
280 self.name = slice['name']
281 self.node_ids = set(slice['node_ids'])
282 self.slice_tag_ids = slice['slice_tag_ids']
283 self.peer_id = slice['peer_slice_id']
286 Use with tags that can have more than one instance
288 def get_tags(self, tagname, node = None):
290 for i in self.slice_tag_ids:
291 tag = self.network.lookupSliceTag(i)
292 if tag.tagname == tagname:
293 if not (tag.node_id and node and node.id != tag.node_id):
298 Use with tags that have only one instance
300 def get_tag(self, tagname, node = None):
301 for i in self.slice_tag_ids:
302 tag = self.network.lookupSliceTag(i)
303 if tag.tagname == tagname:
304 if (not node) or (node.id == tag.node_id):
310 for id in self.node_ids:
311 n.append(self.network.nodes[id])
314 # Add a new slice tag
315 def add_tag(self, tagname, value, node = None):
316 record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
318 record['node_id'] = node.id
320 record['node_id'] = None
321 tag = Slicetag(record)
322 self.network.slicetags[tag.id] = tag
323 self.slice_tag_ids.append(tag.id)
328 # Update a slice tag if it exists, else add it
329 def update_tag(self, tagname, value, node = None):
330 tag = self.get_tag(tagname, node)
331 if tag and tag.value == value:
337 tag = self.add_tag(tagname, value, node)
344 slicetags = self.network.slicetags
348 if tag.tagname == 'egre_key':
349 used.add(int(tag.value))
351 for i in range(1, 256):
356 raise KeyError("No more EGRE keys available")
361 def assign_egre_key(self):
362 if not self.get_tag('egre_key'):
364 key = self.new_egre_key()
365 self.update_tag('egre_key', key)
367 # Should handle this case...
371 def turn_on_netns(self):
372 tag = self.get_tag('netns')
373 if (not tag) or (tag.value != '1'):
374 self.update_tag('netns', '1')
377 def turn_off_netns(self):
378 tag = self.get_tag('netns')
379 if tag and (tag.value != '0'):
383 def add_cap_net_admin(self):
384 tag = self.get_tag('capabilities')
386 caps = tag.value.split(',')
388 if cap == "CAP_NET_ADMIN":
391 newcaps = "CAP_NET_ADMIN," + tag.value
392 self.update_tag('capabilities', newcaps)
394 self.add_tag('capabilities', 'CAP_NET_ADMIN')
397 def remove_cap_net_admin(self):
398 tag = self.get_tag('capabilities')
400 caps = tag.value.split(',')
403 if cap != "CAP_NET_ADMIN":
406 value = ','.join(newcaps)
407 self.update_tag('capabilities', value)
412 # Update the vsys/setup-link and vsys/setup-nat slice tags.
413 def add_vsys_tags(self):
415 for i in self.slice_tag_ids:
416 tag = self.network.lookupSliceTag(i)
417 if tag.tagname == 'vsys':
418 if tag.value == 'setup-link':
420 elif tag.value == 'setup-nat':
423 self.add_tag('vsys', 'setup-link')
425 self.add_tag('vsys', 'setup-nat')
431 def __init__(self, tag):
432 self.id = tag['slice_tag_id']
434 # Make one up for the time being...
435 self.id = Slicetag.newid
437 self.slice_id = tag['slice_id']
438 self.tagname = tag['tagname']
439 self.value = tag['value']
440 self.node_id = tag['node_id']
445 # Mark a tag as deleted
450 def write(self, api):
453 api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
455 api.plshell.AddSliceTag(api.plauth, self.slice_id,
456 self.tagname, self.value, self.node_id)
457 elif self.deleted and int(self.id) > 0:
458 api.plshell.DeleteSliceTag(api.plauth, self.id)
462 A Network is a compound object consisting of:
463 * a dictionary mapping site IDs to Site objects
464 * a dictionary mapping node IDs to Node objects
465 * a dictionary mapping interface IDs to Iface objects
466 * the Site objects are connected via Link objects representing
467 the physical topology and available bandwidth
468 * the Node objects are connected via Link objects representing
469 the requested or assigned virtual topology of a slice
472 def __init__(self, api, type = "PlanetLab", physical_links = [],
476 self.sites = self.get_sites(api)
477 self.nodes = self.get_nodes(api)
478 self.ifaces = self.get_ifaces(api)
479 self.tags = self.get_slice_tags(api)
485 for (s1, s2) in physical_links:
486 self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
490 if tag.tagname == 'topo_rspec':
491 node1 = self.nodes[tag.node_id]
493 for (id, realip, bw, lvip, rvip, vnet) in l:
494 allocbps = get_tc_rate(bw)
495 node1.bps -= allocbps
497 node2 = self.nodes[id]
498 if node1.id < node2.id:
499 sl = node1.get_sitelink(node2, self.sites)
505 """ Lookup site based on id or idtag value """
506 def lookupSite(self, id):
508 if isinstance(id, basestring):
509 id = int(id.lstrip('s'))
513 raise KeyError("site ID %s not found" % id)
519 sites.append(self.sites[s])
522 """ Lookup node based on id or idtag value """
523 def lookupNode(self, id):
525 if isinstance(id, basestring):
526 id = int(id.lstrip('n'))
530 raise KeyError("node ID %s not found" % id)
536 nodes.append(self.nodes[n])
539 """ Lookup iface based on id or idtag value """
540 def lookupIface(self, id):
542 if isinstance(id, basestring):
543 id = int(id.lstrip('i'))
545 val = self.ifaces[id]
547 raise KeyError("interface ID %s not found" % id)
552 for i in self.ifaces:
553 ifaces.append(self.ifaces[i])
556 def nodesWithSlivers(self):
564 def lookupSliceTag(self, id):
569 raise KeyError("slicetag ID %s not found" % id)
572 def getSliceTags(self):
575 tags.append(self.tags[t])
578 def lookupSiteLink(self, node1, node2):
579 site1 = self.sites[node1.site_id]
580 site2 = self.sites[node2.site_id]
581 for link in self.sitelinks:
582 if site1 == link.end1 and site2 == link.end2:
584 if site2 == link.end1 and site1 == link.end2:
589 def __add_vlink(self, vlink, slicenodes, parent = None):
591 endpoints = vlink.get("endpoints")
593 (end1, end2) = endpoints.split()
594 n1 = self.lookupNode(end1)
595 n2 = self.lookupNode(end2)
597 """ Try to infer the endpoints for the virtual link """
598 site_endpoints = parent.get("endpoints")
599 (n1, n2) = self.__infer_endpoints(site_endpoints, slicenodes)
601 raise Error("no endpoints given")
603 #print "Added virtual link: %s -- %s" % (n1.tag, n2.tag)
604 bps = int(vlink.findtext("kbps")) * 1000
605 sitelink = self.lookupSiteLink(n1, n2)
607 raise PermissionError("nodes %s and %s not adjacent" %
608 (n1.idtag, n2.idtag))
609 self.nodelinks.append(Link(n1, n2, bps, sitelink))
613 Infer the endpoints of the virtual link. If the slice exists on
614 only a single node at each end of the physical link, we'll assume that
615 the user wants the virtual link to terminate at these nodes.
617 def __infer_endpoints(self, endpoints, slicenodes):
619 ends = endpoints.split()
622 site = self.lookupSite(end)
623 for id in site.node_ids:
625 n.append(slicenodes[id])
628 raise Error("could not infer endpoint for site %s" % site.id)
629 #print "Inferred endpoints: %s %s" % (n[0].idtag, n[1].idtag)
632 def annotateFromRSpec(self, xml):
634 raise Error("virtual topology already present")
637 for node in self.getNodes():
638 nodedict[node.idtag] = node
642 tree = etree.parse(StringIO(xml))
645 # Validate the incoming request against the RelaxNG schema
646 relaxng_doc = etree.parse(self.schema)
647 relaxng = etree.RelaxNG(relaxng_doc)
649 if not relaxng(tree):
650 error = relaxng.error_log.last_error
651 message = "%s (line %s)" % (error.message, error.line)
652 raise InvalidRSpec(message)
654 rspec = tree.getroot()
657 Handle requests where the user has annotated a description of the
658 physical resources (nodes and links) with virtual ones (slivers
661 # Find slivers under node elements
662 for sliver in rspec.iterfind("./network/site/node/sliver"):
663 elem = sliver.getparent()
664 node = nodedict[elem.get("id")]
665 slicenodes[node.id] = node
668 # Find vlinks under link elements
669 for vlink in rspec.iterfind("./network/link/vlink"):
670 link = vlink.getparent()
671 self.__add_vlink(vlink, slicenodes, link)
674 Handle requests where the user has listed the virtual resources only
676 # Find slivers that specify nodeid
677 for sliver in rspec.iterfind("./request/sliver[@nodeid]"):
678 node = nodedict[sliver.get("nodeid")]
679 slicenodes[node.id] = node
682 # Find vlinks that specify endpoints
683 for vlink in rspec.iterfind("./request/vlink[@endpoints]"):
684 self.__add_vlink(vlink, slicenodes)
688 def annotateFromSliceTags(self):
691 raise Error("no slice associated with network")
694 raise Error("virtual topology already present")
696 for node in slice.get_nodes():
698 node.sliver.read_from_tags()
700 linktag = slice.get_tag('topo_rspec', node)
702 l = eval(linktag.value)
703 for (id, realip, bw, lvip, rvip, vnet) in l:
705 bps = get_tc_rate(bw)
706 remote = self.lookupNode(id)
707 sitelink = self.lookupSiteLink(node, remote)
708 self.nodelinks.append(Link(node,remote,bps,sitelink))
710 def updateSliceTags(self, slice):
711 if not self.nodelinks:
714 """ Comment this out for right now
715 slice.update_tag('vini_topo', 'manual', self.tags)
716 slice.assign_egre_key(self.tags)
717 slice.turn_on_netns(self.tags)
718 slice.add_cap_net_admin(self.tags)
720 for node in slice.get_nodes(self.nodes):
722 for link in node.links:
723 linkdesc.append(node.get_topo_rspec(link))
725 topo_str = "%s" % linkdesc
726 slice.update_tag('topo_rspec', topo_str, self.tags, node)
728 # Update slice tags in database
729 for tag in self.getSliceTags():
730 if tag.slice_id == slice.id:
731 if tag.tagname == 'topo_rspec' and not tag.updated:
738 Check the requested topology against the available topology and capacity
740 def verifyNodeNetwork(self, hrn, topo):
741 for link in self.nodelinks:
743 raise GeniInvalidArgument(bw, "BW")
747 sitelink = self.lookupSiteLink(n1, n2)
749 raise PermissionError("%s: nodes %s and %s not adjacent" % (hrn, n1.tag, n2.tag))
750 if sitelink.bps < link.bps:
751 raise PermissionError("%s: insufficient capacity between %s and %s" % (hrn, n1.tag, n2.tag))
754 Produce XML directly from the topology specification.
757 xml = XMLBuilder(format = True, tab_step = " ")
758 with xml.RSpec(type=self.type):
759 name = "Public_" + self.type
761 element = xml.network(name=name, slice=self.slice.hrn)
763 element = xml.network(name=name)
766 for site in self.getSites():
768 for link in self.sitelinks:
771 header = '<?xml version="1.0"?>\n'
772 return header + str(xml)
775 Create a dictionary of site objects keyed by site ID
777 def get_sites(self, api):
779 for site in api.plshell.GetSites(api.plauth):
780 t = site['site_id'], Site(self, site)
786 Create a dictionary of node objects keyed by node ID
788 def get_nodes(self, api):
790 for node in api.plshell.GetNodes(api.plauth):
791 t = node['node_id'], Node(self, node)
796 Create a dictionary of node objects keyed by node ID
798 def get_ifaces(self, api):
800 for iface in api.plshell.GetInterfaces(api.plauth):
801 t = iface['interface_id'], Iface(self, iface)
806 Create a dictionary of slicetag objects keyed by slice tag ID
808 def get_slice_tags(self, api):
810 for tag in api.plshell.GetSliceTags(api.plauth):
811 t = tag['slice_tag_id'], Slicetag(tag)
816 Return a Slice object for a single slice
818 def get_slice(self, api, hrn):
819 slicename = hrn_to_pl_slicename(hrn)
820 slice = api.plshell.GetSlices(api.plauth, [slicename])
822 self.slice = Slice(self, slicename, slice[0])