1 from __future__ import with_statement
4 from sfa.util.faults import *
5 from xmlbuilder import XMLBuilder
6 #from lxml import etree
8 from StringIO import StringIO
10 # Taken from bwlimit.py
12 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
13 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
14 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
15 # "kibit" and that if an older version is installed, all rates will
16 # be off by a small fraction.
24 "gibit": 1024*1024*1024,
26 "tibit": 1024*1024*1024*1024,
27 "tbit": 1000000000000,
33 "gibps": 8*1024*1024*1024,
35 "tibps": 8*1024*1024*1024*1024,
42 Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
47 m = re.match(r"([0-9.]+)(\D*)", s)
50 suffix = m.group(2).lower()
51 if suffixes.has_key(suffix):
52 return int(float(m.group(1)) * suffixes[suffix])
56 def format_tc_rate(rate):
58 Formats a bits/second rate into a tc rate string
61 if rate >= 1000000000 and (rate % 1000000000) == 0:
62 return "%.0fgbit" % (rate / 1000000000.)
63 elif rate >= 1000000 and (rate % 1000000) == 0:
64 return "%.0fmbit" % (rate / 1000000.)
66 return "%.0fkbit" % (rate / 1000.)
68 return "%.0fbit" % rate
72 def __init__(self, network, iface):
73 self.network = network
74 self.id = iface['interface_id']
75 self.idtag = "i%s" % self.id
76 self.ipv4 = iface['ip']
77 self.bwlimit = iface['bwlimit']
78 self.hostname = iface['hostname']
81 with xml.interface(id = self.idtag):
89 xml << format_tc_rate(self.bwlimit)
92 def __init__(self, network, node, bps = 1000 * 1000000):
93 self.network = network
94 self.id = node['node_id']
95 self.idtag = "n%s" % self.id
96 self.hostname = node['hostname']
97 self.name = self.shortname = self.hostname.replace('.vini-veritas.net', '')
98 self.site_id = node['site_id']
99 #self.ipaddr = socket.gethostbyname(self.hostname)
102 self.iface_ids = node['interface_ids']
103 self.iface_ids.sort()
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))
126 def get_virt_ip(self, remote):
127 link = self.get_link_id(remote)
128 iface = self.get_iface_id(remote)
130 second = ((link & 0x3f)<<2) + iface
131 return "192.168.%d.%d" % (first, second)
133 def get_virt_net(self, remote):
134 link = self.get_link_id(remote)
136 second = (link & 0x3f)<<2
137 return "192.168.%d.%d/30" % (first, second)
140 return self.network.lookupSite(self.site_id)
142 def get_topo_rspec(self, link):
143 if link.end1 == self:
145 elif link.end2 == self:
148 raise Error("Link does not connect to Node")
150 my_ip = self.get_virt_ip(remote)
151 remote_ip = remote.get_virt_ip(self)
152 net = self.get_virt_net(remote)
153 bw = format_tc_rate(link.bps)
154 return (remote.id, remote.ipaddr, bw, my_ip, remote_ip, net)
156 def add_link(self, link):
159 # Assumes there is at most one Link between two sites
160 def get_sitelink(self, node, sites):
161 site1 = sites[self.site_id]
162 site2 = sites[node.site_id]
163 sl = site1.links.intersection(site2.links)
168 def add_sliver(self):
171 def toxml(self, xml, hrn):
172 with xml.node(id = self.idtag):
175 if self.network.type == "VINI":
177 xml << str(int(self.bps/1000))
178 for iface in self.get_ifaces():
186 def __init__(self, end1, end2, bps = 1000000000, parent = None):
197 self.parent.children.append(self)
199 def toxml(self, xml):
200 end_ids = "%s %s" % (self.end1.idtag, self.end2.idtag)
203 element = xml.vlink(endpoints=end_ids)
205 element = xml.link(endpoints=end_ids)
208 with xml.description:
209 xml << "%s -- %s" % (self.end1.name, self.end2.name)
211 xml << str(int(self.bps/1000))
212 for child in self.children:
217 def __init__(self, network, site):
218 self.network = network
219 self.id = site['site_id']
220 self.idtag = "s%s" % self.id
221 self.node_ids = site['node_ids']
223 self.name = site['abbreviated_name']
224 self.tag = site['login_base']
225 self.public = site['is_public']
226 self.enabled = site['enabled']
229 def get_sitenodes(self):
231 for i in self.node_ids:
232 n.append(self.network.lookupNode(i))
235 def add_link(self, link):
238 def toxml(self, xml, hrn, nodes):
239 if not (self.public and self.enabled and self.node_ids):
241 with xml.site(id = self.idtag):
245 for node in self.get_sitenodes():
250 def __init__(self, network, slice):
251 self.network = network
252 self.id = slice['slice_id']
253 self.name = slice['name']
254 self.node_ids = set(slice['node_ids'])
255 self.slice_tag_ids = slice['slice_tag_ids']
257 def get_tag(self, tagname, node = None):
258 for i in self.slice_tag_ids:
259 tag = self.network.lookupSliceTag(i)
260 if tag.tagname == tagname:
261 if (not node) or (node.id == tag.node_id):
266 def get_nodes(self, nodes):
268 for id in self.node_ids:
272 # Add a new slice tag
273 def add_tag(self, tagname, value, node = None):
274 record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
276 record['node_id'] = node.id
278 record['node_id'] = None
279 tag = Slicetag(record)
280 self.network.slicetags[tag.id] = tag
281 self.slice_tag_ids.append(tag.id)
286 # Update a slice tag if it exists, else add it
287 def update_tag(self, tagname, value, node = None):
288 tag = self.get_tag(tagname, node)
289 if tag and tag.value == value:
295 tag = self.add_tag(tagname, value, node)
302 slicetags = self.network.slicetags
306 if tag.tagname == 'egre_key':
307 used.add(int(tag.value))
309 for i in range(1, 256):
314 raise KeyError("No more EGRE keys available")
319 def assign_egre_key(self):
320 if not self.get_tag('egre_key'):
322 key = self.new_egre_key()
323 self.update_tag('egre_key', key)
325 # Should handle this case...
329 def turn_on_netns(self):
330 tag = self.get_tag('netns')
331 if (not tag) or (tag.value != '1'):
332 self.update_tag('netns', '1')
335 def turn_off_netns(self):
336 tag = self.get_tag('netns')
337 if tag and (tag.value != '0'):
341 def add_cap_net_admin(self):
342 tag = self.get_tag('capabilities')
344 caps = tag.value.split(',')
346 if cap == "CAP_NET_ADMIN":
349 newcaps = "CAP_NET_ADMIN," + tag.value
350 self.update_tag('capabilities', newcaps)
352 self.add_tag('capabilities', 'CAP_NET_ADMIN')
355 def remove_cap_net_admin(self):
356 tag = self.get_tag('capabilities')
358 caps = tag.value.split(',')
361 if cap != "CAP_NET_ADMIN":
364 value = ','.join(newcaps)
365 self.update_tag('capabilities', value)
370 # Update the vsys/setup-link and vsys/setup-nat slice tags.
371 def add_vsys_tags(self):
373 for i in self.slice_tag_ids:
374 tag = self.network.lookupSliceTag(i)
375 if tag.tagname == 'vsys':
376 if tag.value == 'setup-link':
378 elif tag.value == 'setup-nat':
381 self.add_tag('vsys', 'setup-link')
383 self.add_tag('vsys', 'setup-nat')
389 def __init__(self, tag):
390 self.id = tag['slice_tag_id']
392 # Make one up for the time being...
393 self.id = Slicetag.newid
395 self.slice_id = tag['slice_id']
396 self.tagname = tag['tagname']
397 self.value = tag['value']
398 self.node_id = tag['node_id']
403 # Mark a tag as deleted
408 def write(self, api):
411 api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
413 api.plshell.AddSliceTag(api.plauth, self.slice_id,
414 self.tagname, self.value, self.node_id)
415 elif self.deleted and int(self.id) > 0:
416 api.plshell.DeleteSliceTag(api.plauth, self.id)
420 A Network is a compound object consisting of:
421 * a dictionary mapping site IDs to Site objects
422 * a dictionary mapping node IDs to Node objects
423 * a dictionary mapping interface IDs to Iface objects
424 * the Site objects are connected via Link objects representing
425 the physical topology and available bandwidth
426 * the Node objects are connected via Link objects representing
427 the requested or assigned virtual topology of a slice
430 def __init__(self, api, type = "PlanetLab", physical_links = [],
434 self.sites = self.get_sites(api)
435 self.nodes = self.get_nodes(api)
436 self.ifaces = self.get_ifaces(api)
437 self.tags = self.get_slice_tags(api)
443 for (s1, s2) in physical_links:
444 self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
448 if tag.tagname == 'topo_rspec':
449 node1 = self.nodes[tag.node_id]
451 for (id, realip, bw, lvip, rvip, vnet) in l:
452 allocbps = get_tc_rate(bw)
453 node1.bps -= allocbps
455 node2 = self.nodes[id]
456 if node1.id < node2.id:
457 sl = node1.get_sitelink(node2, self.sites)
463 """ Lookup site based on id or idtag value """
464 def lookupSite(self, id):
466 if isinstance(id, basestring):
467 id = int(id.lstrip('s'))
471 raise KeyError("site ID %s not found" % id)
477 sites.append(self.sites[s])
480 """ Lookup node based on id or idtag value """
481 def lookupNode(self, id):
483 if isinstance(id, basestring):
484 id = int(id.lstrip('n'))
488 raise KeyError("node ID %s not found" % id)
494 nodes.append(self.nodes[n])
497 """ Lookup iface based on id or idtag value """
498 def lookupIface(self, id):
500 if isinstance(id, basestring):
501 id = int(id.lstrip('i'))
503 val = self.ifaces[id]
505 raise KeyError("interface ID %s not found" % id)
510 for i in self.ifaces:
511 ifaces.append(self.ifaces[i])
514 def nodesInNetwork(self):
522 def lookupSliceTag(self, id):
527 raise KeyError("slicetag ID %s not found" % id)
530 def getSliceTags(self):
533 tags.append(self.tags[t])
536 def lookupSiteLink(self, node1, node2):
537 site1 = self.sites[node1.site_id]
538 site2 = self.sites[node2.site_id]
539 for link in self.sitelinks:
540 if site1 == link.end1 and site2 == link.end2:
542 if site2 == link.end1 and site1 == link.end2:
547 def __add_vlink(self, vlink, slicenodes, parent = None):
549 endpoints = vlink.get("endpoints")
551 (end1, end2) = endpoints.split()
552 n1 = self.lookupNode(end1)
553 n2 = self.lookupNode(end2)
555 """ Try to infer the endpoints for the virtual link """
556 site_endpoints = parent.get("endpoints")
557 (n1, n2) = self.__infer_endpoints(site_endpoints, slicenodes)
559 raise Error("no endpoints given")
561 #print "Added virtual link: %s -- %s" % (n1.tag, n2.tag)
562 bps = int(vlink.findtext("kbps")) * 1000
563 sitelink = self.lookupSiteLink(n1, n2)
565 raise PermissionError("nodes %s and %s not adjacent" %
566 (n1.idtag, n2.idtag))
567 self.nodelinks.append(Link(n1, n2, bps, sitelink))
571 Infer the endpoints of the virtual link. If the slice exists on
572 only a single node at each end of the physical link, we'll assume that
573 the user wants the virtual link to terminate at these nodes.
575 def __infer_endpoints(self, endpoints, slicenodes):
577 ends = endpoints.split()
580 site = self.lookupSite(end)
581 for id in site.node_ids:
583 n.append(slicenodes[id])
586 raise Error("could not infer endpoint for site %s" % site.id)
587 #print "Inferred endpoints: %s %s" % (n[0].idtag, n[1].idtag)
590 def annotateFromRSpec(self, xml):
592 raise Error("virtual topology already present")
595 for node in self.getNodes():
596 nodedict[node.idtag] = node
600 tree = etree.parse(StringIO(xml))
603 # Validate the incoming request against the RelaxNG schema
604 relaxng_doc = etree.parse(self.schema)
605 relaxng = etree.RelaxNG(relaxng_doc)
607 if not relaxng(tree):
608 error = relaxng.error_log.last_error
609 message = "%s (line %s)" % (error.message, error.line)
610 raise InvalidRSpec(message)
612 rspec = tree.getroot()
615 Handle requests where the user has annotated a description of the
616 physical resources (nodes and links) with virtual ones (slivers
619 # Find slivers under node elements
620 for sliver in rspec.iterfind("./network/site/node/sliver"):
621 elem = sliver.getparent()
622 node = nodedict[elem.get("id")]
623 slicenodes[node.id] = node
626 # Find vlinks under link elements
627 for vlink in rspec.iterfind("./network/link/vlink"):
628 link = vlink.getparent()
629 self.__add_vlink(vlink, slicenodes, link)
632 Handle requests where the user has listed the virtual resources only
634 # Find slivers that specify nodeid
635 for sliver in rspec.iterfind("./request/sliver[@nodeid]"):
636 node = nodedict[sliver.get("nodeid")]
637 slicenodes[node.id] = node
640 # Find vlinks that specify endpoints
641 for vlink in rspec.iterfind("./request/vlink[@endpoints]"):
642 self.__add_vlink(vlink, slicenodes)
646 def annotateFromSliceTags(self, slice):
648 raise Error("virtual topology already present")
650 for node in slice.get_nodes(self.nodes):
652 linktag = slice.get_tag('topo_rspec', self.tags, node)
654 l = eval(linktag.value)
655 for (id, realip, bw, lvip, rvip, vnet) in l:
657 bps = get_tc_rate(bw)
658 remote = self.lookupNode(id)
659 sitelink = self.lookupSiteLink(node, remote)
660 self.nodelinks.append(Link(node,remote,bps,sitelink))
662 def updateSliceTags(self, slice):
663 if not self.nodelinks:
666 slice.update_tag('vini_topo', 'manual', self.tags)
667 slice.assign_egre_key(self.tags)
668 slice.turn_on_netns(self.tags)
669 slice.add_cap_net_admin(self.tags)
671 for node in slice.get_nodes(self.nodes):
673 for link in node.links:
674 linkdesc.append(node.get_topo_rspec(link))
676 topo_str = "%s" % linkdesc
677 slice.update_tag('topo_rspec', topo_str, self.tags, node)
679 # Update slice tags in database
680 for tag in self.getSliceTags():
681 if tag.slice_id == slice.id:
682 if tag.tagname == 'topo_rspec' and not tag.updated:
687 Check the requested topology against the available topology and capacity
689 def verifyNodeNetwork(self, hrn, topo):
690 for link in self.nodelinks:
692 raise GeniInvalidArgument(bw, "BW")
696 sitelink = self.lookupSiteLink(n1, n2)
698 raise PermissionError("%s: nodes %s and %s not adjacent" % (hrn, n1.tag, n2.tag))
699 if sitelink.bps < link.bps:
700 raise PermissionError("%s: insufficient capacity between %s and %s" % (hrn, n1.tag, n2.tag))
703 Produce XML directly from the topology specification.
705 def toxml(self, hrn = None):
706 xml = XMLBuilder(format = True, tab_step = " ")
707 with xml.RSpec(type="VINI"):
709 element = xml.network(name="Public_VINI", slice=hrn)
711 element = xml.network(name="Public_VINI")
714 for site in self.getSites():
715 site.toxml(xml, hrn, self.nodes)
716 for link in self.sitelinks:
719 header = '<?xml version="1.0"?>\n'
720 return header + str(xml)
723 Create a dictionary of site objects keyed by site ID
725 def get_sites(self, api):
727 for site in api.plshell.GetSites(api.plauth):
728 t = site['site_id'], Site(self, site)
734 Create a dictionary of node objects keyed by node ID
736 def get_nodes(self, api):
738 for node in api.plshell.GetNodes(api.plauth):
739 t = node['node_id'], Node(self, node)
744 Create a dictionary of node objects keyed by node ID
746 def get_ifaces(self, api):
748 for iface in api.plshell.GetInterfaces(api.plauth):
749 t = iface['interface_id'], Iface(self, iface)
754 Create a dictionary of slicetag objects keyed by slice tag ID
756 def get_slice_tags(self, api):
758 for tag in api.plshell.GetSliceTags(api.plauth):
759 t = tag['slice_tag_id'], Slicetag(tag)
764 Return a Slice object for a single slice
766 def get_slice(self, api, slicename):
767 slice = api.plshell.GetSlices(api.plauth, [slicename])
769 return Slice(self, slice[0])