1 from __future__ import with_statement
4 from sfa.util.namespace import *
5 from sfa.util.faults import *
6 from xmlbuilder import XMLBuilder
7 #from lxml import etree
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
73 def __init__(self, network, iface):
74 self.network = network
75 self.id = iface['interface_id']
76 self.idtag = "i%s" % self.id
77 self.ipv4 = iface['ip']
78 self.bwlimit = iface['bwlimit']
79 self.hostname = iface['hostname']
82 Just print out bwlimit right now
87 xml << format_tc_rate(self.bwlimit)
91 def __init__(self, network, node, bps = 1000 * 1000000):
92 self.network = network
93 self.id = node['node_id']
94 self.idtag = "n%s" % self.id
95 self.hostname = node['hostname']
96 self.name = self.shortname = self.hostname.replace('.vini-veritas.net', '')
97 self.site_id = node['site_id']
98 #self.ipaddr = socket.gethostbyname(self.hostname)
101 self.iface_ids = node['interface_ids']
102 self.iface_ids.sort()
104 self.whitelist = node['slice_ids_whitelist']
106 def get_link_id(self, remote):
107 if self.id < remote.id:
108 link = (self.id<<7) + remote.id
110 link = (remote.id<<7) + self.id
113 def get_iface_id(self, remote):
114 if self.id < remote.id:
120 def get_ifaces(self):
122 for id in self.iface_ids:
123 i.append(self.network.lookupIface(id))
124 # Only return the first interface
128 def get_virt_ip(self, remote):
129 link = self.get_link_id(remote)
130 iface = self.get_iface_id(remote)
132 second = ((link & 0x3f)<<2) + iface
133 return "192.168.%d.%d" % (first, second)
135 def get_virt_net(self, remote):
136 link = self.get_link_id(remote)
138 second = (link & 0x3f)<<2
139 return "192.168.%d.%d/30" % (first, second)
142 return self.network.lookupSite(self.site_id)
144 def get_topo_rspec(self, link):
145 if link.end1 == self:
147 elif link.end2 == self:
150 raise Error("Link does not connect to Node")
152 my_ip = self.get_virt_ip(remote)
153 remote_ip = remote.get_virt_ip(self)
154 net = self.get_virt_net(remote)
155 bw = format_tc_rate(link.bps)
156 return (remote.id, remote.ipaddr, bw, my_ip, remote_ip, net)
158 def add_link(self, link):
161 # Assumes there is at most one Link between two sites
162 def get_sitelink(self, node, sites):
163 site1 = sites[self.site_id]
164 site2 = sites[node.site_id]
165 sl = site1.links.intersection(site2.links)
170 def add_sliver(self):
173 def toxml(self, xml):
174 slice = self.network.slice
175 if self.whitelist and not self.sliver:
176 if not slice or slice.id not in self.whitelist:
179 with xml.node(id = self.idtag):
182 if self.network.type == "VINI":
184 xml << str(int(self.bps/1000))
185 for iface in self.get_ifaces():
193 def __init__(self, end1, end2, bps = 1000000000, parent = None):
204 self.parent.children.append(self)
206 def toxml(self, xml):
207 end_ids = "%s %s" % (self.end1.idtag, self.end2.idtag)
210 element = xml.vlink(endpoints=end_ids)
212 element = xml.link(endpoints=end_ids)
215 with xml.description:
216 xml << "%s -- %s" % (self.end1.name, self.end2.name)
218 xml << str(int(self.bps/1000))
219 for child in self.children:
224 def __init__(self, network, site):
225 self.network = network
226 self.id = site['site_id']
227 self.idtag = "s%s" % self.id
228 self.node_ids = site['node_ids']
230 self.name = site['abbreviated_name']
231 self.tag = site['login_base']
232 self.public = site['is_public']
233 self.enabled = site['enabled']
235 self.whitelist = False
237 def get_sitenodes(self):
239 for i in self.node_ids:
240 n.append(self.network.lookupNode(i))
243 def add_link(self, link):
246 def toxml(self, xml):
247 if not (self.public and self.enabled and self.node_ids):
249 with xml.site(id = self.idtag):
253 for node in self.get_sitenodes():
258 def __init__(self, network, hrn, slice):
260 self.network = network
261 self.id = slice['slice_id']
262 self.name = slice['name']
263 self.node_ids = set(slice['node_ids'])
264 self.slice_tag_ids = slice['slice_tag_ids']
266 def get_tag(self, tagname, node = None):
267 for i in self.slice_tag_ids:
268 tag = self.network.lookupSliceTag(i)
269 if tag.tagname == tagname:
270 if (not node) or (node.id == tag.node_id):
275 def get_nodes(self, nodes):
277 for id in self.node_ids:
281 # Add a new slice tag
282 def add_tag(self, tagname, value, node = None):
283 record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
285 record['node_id'] = node.id
287 record['node_id'] = None
288 tag = Slicetag(record)
289 self.network.slicetags[tag.id] = tag
290 self.slice_tag_ids.append(tag.id)
295 # Update a slice tag if it exists, else add it
296 def update_tag(self, tagname, value, node = None):
297 tag = self.get_tag(tagname, node)
298 if tag and tag.value == value:
304 tag = self.add_tag(tagname, value, node)
311 slicetags = self.network.slicetags
315 if tag.tagname == 'egre_key':
316 used.add(int(tag.value))
318 for i in range(1, 256):
323 raise KeyError("No more EGRE keys available")
328 def assign_egre_key(self):
329 if not self.get_tag('egre_key'):
331 key = self.new_egre_key()
332 self.update_tag('egre_key', key)
334 # Should handle this case...
338 def turn_on_netns(self):
339 tag = self.get_tag('netns')
340 if (not tag) or (tag.value != '1'):
341 self.update_tag('netns', '1')
344 def turn_off_netns(self):
345 tag = self.get_tag('netns')
346 if tag and (tag.value != '0'):
350 def add_cap_net_admin(self):
351 tag = self.get_tag('capabilities')
353 caps = tag.value.split(',')
355 if cap == "CAP_NET_ADMIN":
358 newcaps = "CAP_NET_ADMIN," + tag.value
359 self.update_tag('capabilities', newcaps)
361 self.add_tag('capabilities', 'CAP_NET_ADMIN')
364 def remove_cap_net_admin(self):
365 tag = self.get_tag('capabilities')
367 caps = tag.value.split(',')
370 if cap != "CAP_NET_ADMIN":
373 value = ','.join(newcaps)
374 self.update_tag('capabilities', value)
379 # Update the vsys/setup-link and vsys/setup-nat slice tags.
380 def add_vsys_tags(self):
382 for i in self.slice_tag_ids:
383 tag = self.network.lookupSliceTag(i)
384 if tag.tagname == 'vsys':
385 if tag.value == 'setup-link':
387 elif tag.value == 'setup-nat':
390 self.add_tag('vsys', 'setup-link')
392 self.add_tag('vsys', 'setup-nat')
398 def __init__(self, tag):
399 self.id = tag['slice_tag_id']
401 # Make one up for the time being...
402 self.id = Slicetag.newid
404 self.slice_id = tag['slice_id']
405 self.tagname = tag['tagname']
406 self.value = tag['value']
407 self.node_id = tag['node_id']
412 # Mark a tag as deleted
417 def write(self, api):
420 api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
422 api.plshell.AddSliceTag(api.plauth, self.slice_id,
423 self.tagname, self.value, self.node_id)
424 elif self.deleted and int(self.id) > 0:
425 api.plshell.DeleteSliceTag(api.plauth, self.id)
429 A Network is a compound object consisting of:
430 * a dictionary mapping site IDs to Site objects
431 * a dictionary mapping node IDs to Node objects
432 * a dictionary mapping interface IDs to Iface objects
433 * the Site objects are connected via Link objects representing
434 the physical topology and available bandwidth
435 * the Node objects are connected via Link objects representing
436 the requested or assigned virtual topology of a slice
439 def __init__(self, api, type = "PlanetLab", physical_links = [],
443 self.sites = self.get_sites(api)
444 self.nodes = self.get_nodes(api)
445 self.ifaces = self.get_ifaces(api)
446 self.tags = self.get_slice_tags(api)
452 for (s1, s2) in physical_links:
453 self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
457 if tag.tagname == 'topo_rspec':
458 node1 = self.nodes[tag.node_id]
460 for (id, realip, bw, lvip, rvip, vnet) in l:
461 allocbps = get_tc_rate(bw)
462 node1.bps -= allocbps
464 node2 = self.nodes[id]
465 if node1.id < node2.id:
466 sl = node1.get_sitelink(node2, self.sites)
472 """ Lookup site based on id or idtag value """
473 def lookupSite(self, id):
475 if isinstance(id, basestring):
476 id = int(id.lstrip('s'))
480 raise KeyError("site ID %s not found" % id)
486 sites.append(self.sites[s])
489 """ Lookup node based on id or idtag value """
490 def lookupNode(self, id):
492 if isinstance(id, basestring):
493 id = int(id.lstrip('n'))
497 raise KeyError("node ID %s not found" % id)
503 nodes.append(self.nodes[n])
506 """ Lookup iface based on id or idtag value """
507 def lookupIface(self, id):
509 if isinstance(id, basestring):
510 id = int(id.lstrip('i'))
512 val = self.ifaces[id]
514 raise KeyError("interface ID %s not found" % id)
519 for i in self.ifaces:
520 ifaces.append(self.ifaces[i])
523 def nodesInNetwork(self):
531 def lookupSliceTag(self, id):
536 raise KeyError("slicetag ID %s not found" % id)
539 def getSliceTags(self):
542 tags.append(self.tags[t])
545 def lookupSiteLink(self, node1, node2):
546 site1 = self.sites[node1.site_id]
547 site2 = self.sites[node2.site_id]
548 for link in self.sitelinks:
549 if site1 == link.end1 and site2 == link.end2:
551 if site2 == link.end1 and site1 == link.end2:
556 def __add_vlink(self, vlink, slicenodes, parent = None):
558 endpoints = vlink.get("endpoints")
560 (end1, end2) = endpoints.split()
561 n1 = self.lookupNode(end1)
562 n2 = self.lookupNode(end2)
564 """ Try to infer the endpoints for the virtual link """
565 site_endpoints = parent.get("endpoints")
566 (n1, n2) = self.__infer_endpoints(site_endpoints, slicenodes)
568 raise Error("no endpoints given")
570 #print "Added virtual link: %s -- %s" % (n1.tag, n2.tag)
571 bps = int(vlink.findtext("kbps")) * 1000
572 sitelink = self.lookupSiteLink(n1, n2)
574 raise PermissionError("nodes %s and %s not adjacent" %
575 (n1.idtag, n2.idtag))
576 self.nodelinks.append(Link(n1, n2, bps, sitelink))
580 Infer the endpoints of the virtual link. If the slice exists on
581 only a single node at each end of the physical link, we'll assume that
582 the user wants the virtual link to terminate at these nodes.
584 def __infer_endpoints(self, endpoints, slicenodes):
586 ends = endpoints.split()
589 site = self.lookupSite(end)
590 for id in site.node_ids:
592 n.append(slicenodes[id])
595 raise Error("could not infer endpoint for site %s" % site.id)
596 #print "Inferred endpoints: %s %s" % (n[0].idtag, n[1].idtag)
599 def annotateFromRSpec(self, xml):
601 raise Error("virtual topology already present")
604 for node in self.getNodes():
605 nodedict[node.idtag] = node
609 tree = etree.parse(StringIO(xml))
612 # Validate the incoming request against the RelaxNG schema
613 relaxng_doc = etree.parse(self.schema)
614 relaxng = etree.RelaxNG(relaxng_doc)
616 if not relaxng(tree):
617 error = relaxng.error_log.last_error
618 message = "%s (line %s)" % (error.message, error.line)
619 raise InvalidRSpec(message)
621 rspec = tree.getroot()
624 Handle requests where the user has annotated a description of the
625 physical resources (nodes and links) with virtual ones (slivers
628 # Find slivers under node elements
629 for sliver in rspec.iterfind("./network/site/node/sliver"):
630 elem = sliver.getparent()
631 node = nodedict[elem.get("id")]
632 slicenodes[node.id] = node
635 # Find vlinks under link elements
636 for vlink in rspec.iterfind("./network/link/vlink"):
637 link = vlink.getparent()
638 self.__add_vlink(vlink, slicenodes, link)
641 Handle requests where the user has listed the virtual resources only
643 # Find slivers that specify nodeid
644 for sliver in rspec.iterfind("./request/sliver[@nodeid]"):
645 node = nodedict[sliver.get("nodeid")]
646 slicenodes[node.id] = node
649 # Find vlinks that specify endpoints
650 for vlink in rspec.iterfind("./request/vlink[@endpoints]"):
651 self.__add_vlink(vlink, slicenodes)
655 def annotateFromSliceTags(self):
658 raise Error("no slice associated with network")
661 raise Error("virtual topology already present")
663 for node in slice.get_nodes(self.nodes):
665 linktag = slice.get_tag('topo_rspec', node)
667 l = eval(linktag.value)
668 for (id, realip, bw, lvip, rvip, vnet) in l:
670 bps = get_tc_rate(bw)
671 remote = self.lookupNode(id)
672 sitelink = self.lookupSiteLink(node, remote)
673 self.nodelinks.append(Link(node,remote,bps,sitelink))
675 def updateSliceTags(self, slice):
676 if not self.nodelinks:
679 slice.update_tag('vini_topo', 'manual', self.tags)
680 slice.assign_egre_key(self.tags)
681 slice.turn_on_netns(self.tags)
682 slice.add_cap_net_admin(self.tags)
684 for node in slice.get_nodes(self.nodes):
686 for link in node.links:
687 linkdesc.append(node.get_topo_rspec(link))
689 topo_str = "%s" % linkdesc
690 slice.update_tag('topo_rspec', topo_str, self.tags, node)
692 # Update slice tags in database
693 for tag in self.getSliceTags():
694 if tag.slice_id == slice.id:
695 if tag.tagname == 'topo_rspec' and not tag.updated:
700 Check the requested topology against the available topology and capacity
702 def verifyNodeNetwork(self, hrn, topo):
703 for link in self.nodelinks:
705 raise GeniInvalidArgument(bw, "BW")
709 sitelink = self.lookupSiteLink(n1, n2)
711 raise PermissionError("%s: nodes %s and %s not adjacent" % (hrn, n1.tag, n2.tag))
712 if sitelink.bps < link.bps:
713 raise PermissionError("%s: insufficient capacity between %s and %s" % (hrn, n1.tag, n2.tag))
716 Produce XML directly from the topology specification.
719 xml = XMLBuilder(format = True, tab_step = " ")
720 with xml.RSpec(type=self.type):
721 name = "Public_" + self.type
723 element = xml.network(name=name, slice=self.slice.hrn)
725 element = xml.network(name=name)
728 for site in self.getSites():
730 for link in self.sitelinks:
733 header = '<?xml version="1.0"?>\n'
734 return header + str(xml)
737 Create a dictionary of site objects keyed by site ID
739 def get_sites(self, api):
741 for site in api.plshell.GetSites(api.plauth):
742 t = site['site_id'], Site(self, site)
748 Create a dictionary of node objects keyed by node ID
750 def get_nodes(self, api):
752 for node in api.plshell.GetNodes(api.plauth):
753 t = node['node_id'], Node(self, node)
758 Create a dictionary of node objects keyed by node ID
760 def get_ifaces(self, api):
762 for iface in api.plshell.GetInterfaces(api.plauth):
763 t = iface['interface_id'], Iface(self, iface)
768 Create a dictionary of slicetag objects keyed by slice tag ID
770 def get_slice_tags(self, api):
772 for tag in api.plshell.GetSliceTags(api.plauth):
773 t = tag['slice_tag_id'], Slicetag(tag)
778 Return a Slice object for a single slice
780 def get_slice(self, api, hrn):
781 slicename = hrn_to_pl_slicename(hrn)
782 slice = api.plshell.GetSlices(api.plauth, [slicename])
784 self.slice = Slice(self, slicename, slice[0])