1 from __future__ import with_statement
4 from sfa.util.faults import *
5 from sfa.managers.vini.topology import PhysicalLinks
6 from xmlbuilder import XMLBuilder
9 from StringIO import StringIO
11 VINI_RELAXNG_SCHEMA = "/var/www/html/schemas/vini.rng"
13 # Taken from bwlimit.py
15 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
16 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
17 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
18 # "kibit" and that if an older version is installed, all rates will
19 # be off by a small fraction.
27 "gibit": 1024*1024*1024,
29 "tibit": 1024*1024*1024*1024,
30 "tbit": 1000000000000,
36 "gibps": 8*1024*1024*1024,
38 "tibps": 8*1024*1024*1024*1024,
45 Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
50 m = re.match(r"([0-9.]+)(\D*)", s)
53 suffix = m.group(2).lower()
54 if suffixes.has_key(suffix):
55 return int(float(m.group(1)) * suffixes[suffix])
59 def format_tc_rate(rate):
61 Formats a bits/second rate into a tc rate string
64 if rate >= 1000000000 and (rate % 1000000000) == 0:
65 return "%.0fgbit" % (rate / 1000000000.)
66 elif rate >= 1000000 and (rate % 1000000) == 0:
67 return "%.0fmbit" % (rate / 1000000.)
69 return "%.0fkbit" % (rate / 1000.)
71 return "%.0fbit" % rate
75 def __init__(self, node, bps = 1000 * 1000000):
76 self.id = node['node_id']
77 self.idtag = "n%s" % self.id
78 self.hostname = node['hostname']
79 self.name = self.shortname = self.hostname.replace('.vini-veritas.net', '')
80 self.site_id = node['site_id']
81 self.ipaddr = socket.gethostbyname(self.hostname)
86 def get_link_id(self, remote):
87 if self.id < remote.id:
88 link = (self.id<<7) + remote.id
90 link = (remote.id<<7) + self.id
93 def get_iface_id(self, remote):
94 if self.id < remote.id:
100 def get_virt_ip(self, remote):
101 link = self.get_link_id(remote)
102 iface = self.get_iface_id(remote)
104 second = ((link & 0x3f)<<2) + iface
105 return "192.168.%d.%d" % (first, second)
107 def get_virt_net(self, remote):
108 link = self.get_link_id(remote)
110 second = (link & 0x3f)<<2
111 return "192.168.%d.%d/30" % (first, second)
113 def get_site(self, sites):
114 return sites[self.site_id]
116 def get_topo_rspec(self, link):
117 if link.end1 == self:
119 elif link.end2 == self:
122 raise Error("Link does not connect to Node")
124 my_ip = self.get_virt_ip(remote)
125 remote_ip = remote.get_virt_ip(self)
126 net = self.get_virt_net(remote)
127 bw = format_tc_rate(link.bps)
128 return (remote.id, remote.ipaddr, bw, my_ip, remote_ip, net)
130 def add_link(self, link):
133 def add_tag(self, sites):
134 s = self.get_site(sites)
135 words = self.hostname.split(".")
136 index = words[0].replace("node", "")
138 self.tag = s.tag + index
142 # Assumes there is at most one Link between two sites
143 def get_sitelink(self, node, sites):
144 site1 = sites[self.site_id]
145 site2 = sites[node.site_id]
146 sl = site1.links.intersection(site2.links)
151 def add_sliver(self):
154 def toxml(self, xml, hrn):
157 with xml.node(id = self.idtag):
161 xml << str(int(self.bps/1000))
168 def __init__(self, end1, end2, bps = 1000 * 1000000, parent = None):
179 self.parent.children.append(self)
181 def toxml(self, xml):
182 end_ids = "%s %s" % (self.end1.idtag, self.end2.idtag)
185 element = xml.vlink(endpoints=end_ids)
187 element = xml.link(endpoints=end_ids)
190 with xml.description:
191 xml << "%s -- %s" % (self.end1.name, self.end2.name)
193 xml << str(int(self.bps/1000))
194 for child in self.children:
199 def __init__(self, site):
200 self.id = site['site_id']
201 self.idtag = "s%s" % self.id
202 self.node_ids = site['node_ids']
203 self.name = site['abbreviated_name'].replace(" ", "_")
204 self.tag = site['login_base']
205 self.public = site['is_public']
206 self.enabled = site['enabled']
209 def get_sitenodes(self, nodes):
211 for i in self.node_ids:
215 def add_link(self, link):
218 def toxml(self, xml, hrn, nodes):
219 if not (self.public and self.enabled and self.node_ids):
221 with xml.site(id = self.idtag):
225 for node in self.get_sitenodes(nodes):
230 def __init__(self, slice):
231 self.id = slice['slice_id']
232 self.name = slice['name']
233 self.node_ids = set(slice['node_ids'])
234 self.slice_tag_ids = slice['slice_tag_ids']
236 def get_tag(self, tagname, slicetags, node = None):
237 for i in self.slice_tag_ids:
239 if tag.tagname == tagname:
240 if (not node) or (node.id == tag.node_id):
245 def get_nodes(self, nodes):
247 for id in self.node_ids:
252 # Add a new slice tag
253 def add_tag(self, tagname, value, slicetags, node = None):
254 record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
256 record['node_id'] = node.id
258 record['node_id'] = None
259 tag = Slicetag(record)
260 slicetags[tag.id] = tag
261 self.slice_tag_ids.append(tag.id)
266 # Update a slice tag if it exists, else add it
267 def update_tag(self, tagname, value, slicetags, node = None):
268 tag = self.get_tag(tagname, slicetags, node)
269 if tag and tag.value == value:
275 tag = self.add_tag(tagname, value, slicetags, node)
278 def assign_egre_key(self, slicetags):
279 if not self.get_tag('egre_key', slicetags):
281 key = free_egre_key(slicetags)
282 self.update_tag('egre_key', key, slicetags)
284 # Should handle this case...
288 def turn_on_netns(self, slicetags):
289 tag = self.get_tag('netns', slicetags)
290 if (not tag) or (tag.value != '1'):
291 self.update_tag('netns', '1', slicetags)
294 def turn_off_netns(self, slicetags):
295 tag = self.get_tag('netns', slicetags)
296 if tag and (tag.value != '0'):
300 def add_cap_net_admin(self, slicetags):
301 tag = self.get_tag('capabilities', slicetags)
303 caps = tag.value.split(',')
305 if cap == "CAP_NET_ADMIN":
308 newcaps = "CAP_NET_ADMIN," + tag.value
309 self.update_tag('capabilities', newcaps, slicetags)
311 self.add_tag('capabilities', 'CAP_NET_ADMIN', slicetags)
314 def remove_cap_net_admin(self, slicetags):
315 tag = self.get_tag('capabilities', slicetags)
317 caps = tag.value.split(',')
320 if cap != "CAP_NET_ADMIN":
323 value = ','.join(newcaps)
324 self.update_tag('capabilities', value, slicetags)
329 # Update the vsys/setup-link and vsys/setup-nat slice tags.
330 def add_vsys_tags(self, slicetags):
332 for i in self.slice_tag_ids:
334 if tag.tagname == 'vsys':
335 if tag.value == 'setup-link':
337 elif tag.value == 'setup-nat':
340 self.add_tag('vsys', 'setup-link', slicetags)
342 self.add_tag('vsys', 'setup-nat', slicetags)
348 def __init__(self, tag):
349 self.id = tag['slice_tag_id']
351 # Make one up for the time being...
352 self.id = Slicetag.newid
354 self.slice_id = tag['slice_id']
355 self.tagname = tag['tagname']
356 self.value = tag['value']
357 self.node_id = tag['node_id']
362 # Mark a tag as deleted
367 def write(self, api):
370 api.driver.UpdateSliceTag(self.id, self.value)
372 api.driver.AddSliceTag(self.slice_id, self.tagname, self.value, self.node_id)
373 elif self.deleted and int(self.id) > 0:
374 api.driver.DeleteSliceTag(self.id)
378 A topology is a compound object consisting of:
379 * a dictionary mapping site IDs to Site objects
380 * a dictionary mapping node IDs to Node objects
381 * the Site objects are connected via SiteLink objects representing
382 the physical topology and available bandwidth
383 * the Node objects are connected via Link objects representing
384 the requested or assigned virtual topology of a slice
387 def __init__(self, api):
389 self.sites = get_sites(api)
390 self.nodes = get_nodes(api)
391 self.tags = get_slice_tags(api)
395 for (s1, s2) in PhysicalLinks:
396 self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
398 for id in self.nodes:
399 self.nodes[id].add_tag(self.sites)
403 if tag.tagname == 'topo_rspec':
404 node1 = self.nodes[tag.node_id]
406 for (id, realip, bw, lvip, rvip, vnet) in l:
407 allocbps = get_tc_rate(bw)
408 node1.bps -= allocbps
410 node2 = self.nodes[id]
411 if node1.id < node2.id:
412 sl = node1.get_sitelink(node2, self.sites)
418 """ Lookup site based on id or idtag value """
419 def lookupSite(self, id):
421 if isinstance(id, basestring):
422 id = int(id.lstrip('s'))
426 raise KeyError("site ID %s not found" % id)
432 sites.append(self.sites[s])
435 """ Lookup node based on id or idtag value """
436 def lookupNode(self, id):
438 if isinstance(id, basestring):
439 id = int(id.lstrip('n'))
443 raise KeyError("node ID %s not found" % id)
449 nodes.append(self.nodes[n])
452 def nodesInTopo(self):
460 def lookupSliceTag(self, id):
465 raise KeyError("slicetag ID %s not found" % id)
468 def getSliceTags(self):
471 tags.append(self.tags[t])
474 def lookupSiteLink(self, node1, node2):
475 site1 = self.sites[node1.site_id]
476 site2 = self.sites[node2.site_id]
477 for link in self.sitelinks:
478 if site1 == link.end1 and site2 == link.end2:
480 if site2 == link.end1 and site1 == link.end2:
485 def __add_vlink(self, vlink, slicenodes, parent = None):
487 endpoints = vlink.get("endpoints")
489 (end1, end2) = endpoints.split()
490 n1 = self.lookupNode(end1)
491 n2 = self.lookupNode(end2)
493 """ Try to infer the endpoints for the virtual link """
494 site_endpoints = parent.get("endpoints")
495 (n1, n2) = self.__infer_endpoints(site_endpoints, slicenodes)
497 raise Error("no endpoints given")
499 #print "Added virtual link: %s -- %s" % (n1.tag, n2.tag)
500 bps = int(vlink.findtext("kbps")) * 1000
501 sitelink = self.lookupSiteLink(n1, n2)
503 raise PermissionError("nodes %s and %s not adjacent" %
504 (n1.idtag, n2.idtag))
505 self.nodelinks.append(Link(n1, n2, bps, sitelink))
509 Infer the endpoints of the virtual link. If the slice exists on
510 only a single node at each end of the physical link, we'll assume that
511 the user wants the virtual link to terminate at these nodes.
513 def __infer_endpoints(self, endpoints, slicenodes):
515 ends = endpoints.split()
518 site = self.lookupSite(end)
519 for id in site.node_ids:
521 n.append(slicenodes[id])
524 raise Error("could not infer endpoint for site %s" % site.id)
525 #print "Inferred endpoints: %s %s" % (n[0].idtag, n[1].idtag)
528 def nodeTopoFromRSpec(self, xml):
530 raise Error("virtual topology already present")
533 for node in self.getNodes():
534 nodedict[node.idtag] = node
538 tree = etree.parse(StringIO(xml))
540 # Validate the incoming request against the RelaxNG schema
541 relaxng_doc = etree.parse(VINI_RELAXNG_SCHEMA)
542 relaxng = etree.RelaxNG(relaxng_doc)
544 if not relaxng(tree):
545 error = relaxng.error_log.last_error
546 message = "%s (line %s)" % (error.message, error.line)
547 raise InvalidRSpec(message)
549 rspec = tree.getroot()
552 Handle requests where the user has annotated a description of the
553 physical resources (nodes and links) with virtual ones (slivers
556 # Find slivers under node elements
557 for sliver in rspec.iterfind("./network/site/node/sliver"):
558 elem = sliver.getparent()
559 node = nodedict[elem.get("id")]
560 slicenodes[node.id] = node
563 # Find vlinks under link elements
564 for vlink in rspec.iterfind("./network/link/vlink"):
565 link = vlink.getparent()
566 self.__add_vlink(vlink, slicenodes, link)
569 Handle requests where the user has listed the virtual resources only
571 # Find slivers that specify nodeid
572 for sliver in rspec.iterfind("./request/sliver[@nodeid]"):
573 node = nodedict[sliver.get("nodeid")]
574 slicenodes[node.id] = node
577 # Find vlinks that specify endpoints
578 for vlink in rspec.iterfind("./request/vlink[@endpoints]"):
579 self.__add_vlink(vlink, slicenodes)
583 def nodeTopoFromSliceTags(self, slice):
585 raise Error("virtual topology already present")
587 for node in slice.get_nodes(self.nodes):
589 linktag = slice.get_tag('topo_rspec', self.tags, node)
591 l = eval(linktag.value)
592 for (id, realip, bw, lvip, rvip, vnet) in l:
594 bps = get_tc_rate(bw)
595 remote = self.lookupNode(id)
596 sitelink = self.lookupSiteLink(node, remote)
597 self.nodelinks.append(Link(node,remote,bps,sitelink))
599 def updateSliceTags(self, slice):
600 if not self.nodelinks:
603 slice.update_tag('vini_topo', 'manual', self.tags)
604 slice.assign_egre_key(self.tags)
605 slice.turn_on_netns(self.tags)
606 slice.add_cap_net_admin(self.tags)
608 for node in slice.get_nodes(self.nodes):
610 for link in node.links:
611 linkdesc.append(node.get_topo_rspec(link))
613 topo_str = "%s" % linkdesc
614 slice.update_tag('topo_rspec', topo_str, self.tags, node)
616 # Update slice tags in database
617 for tag in self.getSliceTags():
618 if tag.slice_id == slice.id:
619 if tag.tagname == 'topo_rspec' and not tag.updated:
624 Check the requested topology against the available topology and capacity
626 def verifyNodeTopo(self, hrn, topo):
627 for link in self.nodelinks:
629 raise GeniInvalidArgument(bw, "BW")
633 sitelink = self.lookupSiteLink(n1, n2)
635 raise PermissionError("%s: nodes %s and %s not adjacent" % (hrn, n1.tag, n2.tag))
636 if sitelink.bps < link.bps:
637 raise PermissionError("%s: insufficient capacity between %s and %s" % (hrn, n1.tag, n2.tag))
640 Produce XML directly from the topology specification.
642 def toxml(self, hrn = None):
643 xml = XMLBuilder(format = True, tab_step = " ")
644 with xml.RSpec(type="VINI"):
646 element = xml.network(name="Public_VINI", slice=hrn)
648 element = xml.network(name="Public_VINI")
651 for site in self.getSites():
652 site.toxml(xml, hrn, self.nodes)
653 for link in self.sitelinks:
656 header = '<?xml version="1.0"?>\n'
657 return header + str(xml)
660 Create a dictionary of site objects keyed by site ID
664 for site in api.driver.GetSites():
665 t = site['site_id'], Site(site)
671 Create a dictionary of node objects keyed by node ID
675 for node in api.driver.GetNodes():
676 t = node['node_id'], Node(node)
681 Create a dictionary of slice objects keyed by slice ID
683 def get_slice(api, slicename):
684 slice = api.driver.GetSlices([slicename])
686 return Slice(slice[0])
691 Create a dictionary of slicetag objects keyed by slice tag ID
693 def get_slice_tags(api):
695 for tag in api.driver.GetSliceTags():
696 t = tag['slice_tag_id'], Slicetag(tag)
703 def free_egre_key(slicetags):
707 if tag.tagname == 'egre_key':
708 used.add(int(tag.value))
710 for i in range(1, 256):
715 raise KeyError("No more EGRE keys available")