1 from __future__ import with_statement
4 from sfa.util.faults import *
5 from sfa.rspecs.aggregates.vini.topology import *
6 from xmlbuilder import XMLBuilder
9 # Taken from bwlimit.py
11 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
12 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
13 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
14 # "kibit" and that if an older version is installed, all rates will
15 # be off by a small fraction.
23 "gibit": 1024*1024*1024,
25 "tibit": 1024*1024*1024*1024,
26 "tbit": 1000000000000,
32 "gibps": 8*1024*1024*1024,
34 "tibps": 8*1024*1024*1024*1024,
41 Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
46 m = re.match(r"([0-9.]+)(\D*)", s)
49 suffix = m.group(2).lower()
50 if suffixes.has_key(suffix):
51 return int(float(m.group(1)) * suffixes[suffix])
55 def format_tc_rate(rate):
57 Formats a bits/second rate into a tc rate string
60 if rate >= 1000000000 and (rate % 1000000000) == 0:
61 return "%.0fgbit" % (rate / 1000000000.)
62 elif rate >= 1000000 and (rate % 1000000) == 0:
63 return "%.0fmbit" % (rate / 1000000.)
65 return "%.0fkbit" % (rate / 1000.)
67 return "%.0fbit" % rate
71 def __init__(self, node, bps = 1000 * 1000000):
72 self.id = node['node_id']
73 self.idtag = "n%s" % self.id
74 self.hostname = node['hostname']
75 self.name = self.shortname = self.hostname.replace('.vini-veritas.net', '')
76 self.site_id = node['site_id']
77 self.ipaddr = socket.gethostbyname(self.hostname)
82 def get_link_id(self, remote):
83 if self.id < remote.id:
84 link = (self.id<<7) + remote.id
86 link = (remote.id<<7) + self.id
89 def get_iface_id(self, remote):
90 if self.id < remote.id:
96 def get_virt_ip(self, remote):
97 link = self.get_link_id(remote)
98 iface = self.get_iface_id(remote)
100 second = ((link & 0x3f)<<2) + iface
101 return "192.168.%d.%d" % (first, second)
103 def get_virt_net(self, remote):
104 link = self.get_link_id(remote)
106 second = (link & 0x3f)<<2
107 return "192.168.%d.%d/30" % (first, second)
109 def get_site(self, sites):
110 return sites[self.site_id]
112 def get_topo_rspec(self, link):
113 if link.end1 == self:
115 elif link.end2 == self:
118 raise Error("Link does not connect to Node")
120 my_ip = self.get_virt_ip(remote)
121 remote_ip = remote.get_virt_ip(self)
122 net = self.get_virt_net(remote)
123 bw = format_tc_rate(link.bps)
124 return (remote.id, remote.ipaddr, bw, my_ip, remote_ip, net)
126 def add_link(self, link):
129 def add_tag(self, sites):
130 s = self.get_site(sites)
131 words = self.hostname.split(".")
132 index = words[0].replace("node", "")
134 self.tag = s.tag + index
138 # Assumes there is at most one Link between two sites
139 def get_sitelink(self, node, sites):
140 site1 = sites[self.site_id]
141 site2 = sites[node.site_id]
142 sl = site1.links.intersection(site2.links)
147 def add_sliver(self):
150 def toxml(self, xml, hrn):
153 with xml.node(id = self.idtag):
157 xml << str(int(self.bps/1000))
164 def __init__(self, end1, end2, bps = 1000 * 1000000, parent = None):
175 self.parent.children.append(self)
177 def toxml(self, xml):
178 end_ids = "%s %s" % (self.end1.idtag, self.end2.idtag)
181 element = xml.vlink(endpoints=end_ids)
183 element = xml.link(endpoints=end_ids)
186 with xml.description:
187 xml << "%s -- %s" % (self.end1.name, self.end2.name)
189 xml << str(int(self.bps/1000))
190 for child in self.children:
195 def __init__(self, site):
196 self.id = site['site_id']
197 self.idtag = "s%s" % self.id
198 self.node_ids = site['node_ids']
199 self.name = site['abbreviated_name'].replace(" ", "_")
200 self.tag = site['login_base']
201 self.public = site['is_public']
202 self.enabled = site['enabled']
205 def get_sitenodes(self, nodes):
207 for i in self.node_ids:
211 def add_link(self, link):
214 def toxml(self, xml, hrn, nodes):
215 if not (self.public and self.enabled and self.node_ids):
217 with xml.site(id = self.idtag):
221 for node in self.get_sitenodes(nodes):
226 def __init__(self, slice):
227 self.id = slice['slice_id']
228 self.name = slice['name']
229 self.node_ids = set(slice['node_ids'])
230 self.slice_tag_ids = slice['slice_tag_ids']
232 def get_tag(self, tagname, slicetags, node = None):
233 for i in self.slice_tag_ids:
235 if tag.tagname == tagname:
236 if (not node) or (node.id == tag.node_id):
241 def get_nodes(self, nodes):
243 for id in self.node_ids:
248 # Add a new slice tag
249 def add_tag(self, tagname, value, slicetags, node = None):
250 record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
252 record['node_id'] = node.id
254 record['node_id'] = None
255 tag = Slicetag(record)
256 slicetags[tag.id] = tag
257 self.slice_tag_ids.append(tag.id)
262 # Update a slice tag if it exists, else add it
263 def update_tag(self, tagname, value, slicetags, node = None):
264 tag = self.get_tag(tagname, slicetags, node)
265 if tag and tag.value == value:
271 tag = self.add_tag(tagname, value, slicetags, node)
274 def assign_egre_key(self, slicetags):
275 if not self.get_tag('egre_key', slicetags):
277 key = free_egre_key(slicetags)
278 self.update_tag('egre_key', key, slicetags)
280 # Should handle this case...
284 def turn_on_netns(self, slicetags):
285 tag = self.get_tag('netns', slicetags)
286 if (not tag) or (tag.value != '1'):
287 self.update_tag('netns', '1', slicetags)
290 def turn_off_netns(self, slicetags):
291 tag = self.get_tag('netns', slicetags)
292 if tag and (tag.value != '0'):
296 def add_cap_net_admin(self, slicetags):
297 tag = self.get_tag('capabilities', slicetags)
299 caps = tag.value.split(',')
301 if cap == "CAP_NET_ADMIN":
304 newcaps = "CAP_NET_ADMIN," + tag.value
305 self.update_tag('capabilities', newcaps, slicetags)
307 self.add_tag('capabilities', 'CAP_NET_ADMIN', slicetags)
310 def remove_cap_net_admin(self, slicetags):
311 tag = self.get_tag('capabilities', slicetags)
313 caps = tag.value.split(',')
316 if cap != "CAP_NET_ADMIN":
319 value = ','.join(newcaps)
320 self.update_tag('capabilities', value, slicetags)
325 # Update the vsys/setup-link and vsys/setup-nat slice tags.
326 def add_vsys_tags(self, slicetags):
328 for i in self.slice_tag_ids:
330 if tag.tagname == 'vsys':
331 if tag.value == 'setup-link':
333 elif tag.value == 'setup-nat':
336 self.add_tag('vsys', 'setup-link', slicetags)
338 self.add_tag('vsys', 'setup-nat', slicetags)
344 def __init__(self, tag):
345 self.id = tag['slice_tag_id']
347 # Make one up for the time being...
348 self.id = Slicetag.newid
350 self.slice_id = tag['slice_id']
351 self.tagname = tag['tagname']
352 self.value = tag['value']
353 self.node_id = tag['node_id']
358 # Mark a tag as deleted
363 def write(self, api):
366 api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
368 api.plshell.AddSliceTag(api.plauth, self.slice_id,
369 self.tagname, self.value, self.node_id)
370 elif self.deleted and int(self.id) > 0:
371 api.plshell.DeleteSliceTag(api.plauth, self.id)
375 A topology is a compound object consisting of:
376 * a dictionary mapping site IDs to Site objects
377 * a dictionary mapping node IDs to Node objects
378 * the Site objects are connected via SiteLink objects representing
379 the physical topology and available bandwidth
380 * the Node objects are connected via Link objects representing
381 the requested or assigned virtual topology of a slice
384 def __init__(self, api):
386 self.sites = get_sites(api)
387 self.nodes = get_nodes(api)
388 self.tags = get_slice_tags(api)
392 for (s1, s2) in PhysicalLinks:
393 self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
395 for id in self.nodes:
396 self.nodes[id].add_tag(self.sites)
400 if tag.tagname == 'topo_rspec':
401 node1 = self.nodes[tag.node_id]
403 for (id, realip, bw, lvip, rvip, vnet) in l:
404 allocbps = get_tc_rate(bw)
405 node1.bps -= allocbps
407 node2 = self.nodes[id]
408 if node1.id < node2.id:
409 sl = node1.get_sitelink(node2, self.sites)
415 """ Lookup site based on id or idtag value """
416 def lookupSite(self, id):
418 if isinstance(id, basestring):
419 id = int(id.lstrip('s'))
423 raise KeyError("site ID %s not found" % id)
429 sites.append(self.sites[s])
432 """ Lookup node based on id or idtag value """
433 def lookupNode(self, id):
435 if isinstance(id, basestring):
436 id = int(id.lstrip('n'))
440 raise KeyError("node ID %s not found" % id)
446 nodes.append(self.nodes[n])
449 def nodesInTopo(self):
457 def lookupSliceTag(self, id):
462 raise KeyError("slicetag ID %s not found" % id)
465 def getSliceTags(self):
468 tags.append(self.tags[t])
471 def lookupSiteLink(self, node1, node2):
472 site1 = self.sites[node1.site_id]
473 site2 = self.sites[node2.site_id]
474 for link in self.sitelinks:
475 if site1 == link.end1 and site2 == link.end2:
477 if site2 == link.end1 and site1 == link.end2:
482 def __add_vlink(self, vlink, slicenodes, parent = None):
484 if 'endpoints' in vlink:
485 end = vlink['endpoints'].split()
486 n1 = self.lookupNode(end[0])
487 n2 = self.lookupNode(end[1])
489 """ Try to infer the endpoints """
490 (n1, n2) = self.__infer_endpoints(parent['endpoints'],slicenodes)
492 raise Error("no endpoints given")
494 #print "Added virtual link: %s -- %s" % (n1.tag, n2.tag)
495 bps = int(vlink['kbps'][0]) * 1000
496 sitelink = self.lookupSiteLink(n1, n2)
498 raise PermissionError("nodes %s and %s not adjacent" %
499 (n1.idtag, n2.idtag))
500 self.nodelinks.append(Link(n1, n2, bps, sitelink))
504 Infer the endpoints of the virtual link. If the slice exists on
505 only a single node at each end of the physical link, we'll assume that
506 the user wants the virtual link to terminate at these nodes.
508 def __infer_endpoints(self, endpoints, slicenodes):
510 ends = endpoints.split()
513 site = self.lookupSite(end)
514 for id in site.node_ids:
516 n.append(slicenodes[id])
519 raise Error("could not infer endpoint for site %s" % site.id)
520 #print "Inferred endpoints: %s %s" % (n[0].idtag, n[1].idtag)
523 def nodeTopoFromRSpec(self, rspec):
525 raise Error("virtual topology already present")
527 rspecdict = rspec.toDict()
529 for node in self.getNodes():
530 nodedict[node.idtag] = node
534 top = rspecdict['rspec']
535 if ('network' in top):
536 sites = top['network'][0]['site']
538 for node in site['node']:
540 n = nodedict[node['id']]
543 links = top['network'][0]['link']
546 for vlink in link['vlink']:
547 self.__add_vlink(vlink, slicenodes, link)
548 elif ('request' in top):
549 for sliver in top['request'][0]['sliver']:
550 n = nodedict[sliver['nodeid']]
553 for vlink in top['request'][0]['vlink']:
554 self.__add_vlink(vlink, slicenodes)
557 def nodeTopoFromSliceTags(self, slice):
559 raise Error("virtual topology already present")
561 for node in slice.get_nodes(self.nodes):
563 linktag = slice.get_tag('topo_rspec', self.tags, node)
565 l = eval(linktag.value)
566 for (id, realip, bw, lvip, rvip, vnet) in l:
568 bps = get_tc_rate(bw)
569 remote = self.lookupNode(id)
570 sitelink = self.lookupSiteLink(node, remote)
571 self.nodelinks.append(Link(node,remote,bps,sitelink))
573 def updateSliceTags(self, slice):
574 if not self.nodelinks:
577 slice.update_tag('vini_topo', 'manual', self.tags)
578 slice.assign_egre_key(self.tags)
579 slice.turn_on_netns(self.tags)
580 slice.add_cap_net_admin(self.tags)
582 for node in slice.get_nodes(self.nodes):
584 for link in node.links:
585 linkdesc.append(node.get_topo_rspec(link))
587 topo_str = "%s" % linkdesc
588 slice.update_tag('topo_rspec', topo_str, self.tags, node)
590 # Update slice tags in database
591 for tag in self.getSliceTags():
592 if tag.slice_id == slice.id:
593 if tag.tagname == 'topo_rspec' and not tag.updated:
598 Check the requested topology against the available topology and capacity
600 def verifyNodeTopo(self, hrn, topo):
601 for link in self.nodelinks:
603 raise GeniInvalidArgument(bw, "BW")
607 sitelink = self.lookupSiteLink(n1, n2)
609 raise PermissionError("%s: nodes %s and %s not adjacent" % (hrn, n1.tag, n2.tag))
610 if sitelink.bps < link.bps:
611 raise PermissionError("%s: insufficient capacity between %s and %s" % (hrn, n1.tag, n2.tag))
614 Produce XML directly from the topology specification.
616 def toxml(self, hrn = None):
618 with xml.rspec(type="VINI"):
620 element = xml.network(name="Public VINI", slice=hrn)
622 element = xml.network(name="Public VINI")
625 for site in self.getSites():
626 site.toxml(xml, hrn, self.nodes)
627 for link in self.sitelinks:
633 Create a dictionary of site objects keyed by site ID
637 for site in api.plshell.GetSites(api.plauth):
638 t = site['site_id'], Site(site)
644 Create a dictionary of node objects keyed by node ID
648 for node in api.plshell.GetNodes(api.plauth):
649 t = node['node_id'], Node(node)
654 Create a dictionary of slice objects keyed by slice ID
656 def get_slice(api, slicename):
657 slice = api.plshell.GetSlices(api.plauth, [slicename])
659 return Slice(slice[0])
664 Create a dictionary of slicetag objects keyed by slice tag ID
666 def get_slice_tags(api):
668 for tag in api.plshell.GetSliceTags(api.plauth):
669 t = tag['slice_tag_id'], Slicetag(tag)
676 def free_egre_key(slicetags):
680 if tag.tagname == 'egre_key':
681 used.add(int(tag.value))
683 for i in range(1, 256):
688 raise KeyError("No more EGRE keys available")