3 from sfa.util.faults import *
4 from sfa.rspecs.aggregates.vini.topology import *
8 <endpoint>i2atla1</endpoint>
9 <endpoint>i2chic1</endpoint>
13 <endpoint>i2atla1</endpoint>
14 <endpoint>i2hous1</endpoint>
18 <endpoint>i2atla1</endpoint>
19 <endpoint>i2wash1</endpoint>
23 <endpoint>i2chic1</endpoint>
24 <endpoint>i2kans1</endpoint>
28 <endpoint>i2chic1</endpoint>
29 <endpoint>i2wash1</endpoint>
33 <endpoint>i2hous1</endpoint>
34 <endpoint>i2kans1</endpoint>
38 <endpoint>i2hous1</endpoint>
39 <endpoint>i2losa1</endpoint>
43 <endpoint>i2kans1</endpoint>
44 <endpoint>i2salt1</endpoint>
48 <endpoint>i2losa1</endpoint>
49 <endpoint>i2salt1</endpoint>
53 <endpoint>i2losa1</endpoint>
54 <endpoint>i2seat1</endpoint>
58 <endpoint>i2newy1</endpoint>
59 <endpoint>i2wash1</endpoint>
63 <endpoint>i2salt1</endpoint>
64 <endpoint>i2seat1</endpoint>
68 # Taken from bwlimit.py
70 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
71 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
72 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
73 # "kibit" and that if an older version is installed, all rates will
74 # be off by a small fraction.
82 "gibit": 1024*1024*1024,
84 "tibit": 1024*1024*1024*1024,
85 "tbit": 1000000000000,
91 "gibps": 8*1024*1024*1024,
93 "tibps": 8*1024*1024*1024*1024,
100 Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
105 m = re.match(r"([0-9.]+)(\D*)", s)
108 suffix = m.group(2).lower()
109 if suffixes.has_key(suffix):
110 return int(float(m.group(1)) * suffixes[suffix])
114 def format_tc_rate(rate):
116 Formats a bits/second rate into a tc rate string
119 if rate >= 1000000000 and (rate % 1000000000) == 0:
120 return "%.0fgbit" % (rate / 1000000000.)
121 elif rate >= 1000000 and (rate % 1000000) == 0:
122 return "%.0fmbit" % (rate / 1000000.)
124 return "%.0fkbit" % (rate / 1000.)
126 return "%.0fbit" % rate
130 def __init__(self, node, bps = 1000 * 1000000):
131 self.id = node['node_id']
132 self.hostname = node['hostname']
133 self.shortname = self.hostname.replace('.vini-veritas.net', '')
134 self.site_id = node['site_id']
135 self.ipaddr = socket.gethostbyname(self.hostname)
139 def get_link_id(self, remote):
140 if self.id < remote.id:
141 link = (self.id<<7) + remote.id
143 link = (remote.id<<7) + self.id
146 def get_iface_id(self, remote):
147 if self.id < remote.id:
153 def get_virt_ip(self, remote):
154 link = self.get_link_id(remote)
155 iface = self.get_iface_id(remote)
157 second = ((link & 0x3f)<<2) + iface
158 return "192.168.%d.%d" % (first, second)
160 def get_virt_net(self, remote):
161 link = self.get_link_id(remote)
163 second = (link & 0x3f)<<2
164 return "192.168.%d.%d/30" % (first, second)
166 def get_site(self, sites):
167 return sites[self.site_id]
169 def get_topo_rspec(self, link):
170 if link.end1 == self:
172 elif link.end2 == self:
175 raise Error("Link does not connect to Node")
177 my_ip = self.get_virt_ip(remote)
178 remote_ip = remote.get_virt_ip(self)
179 net = self.get_virt_net(remote)
180 bw = format_tc_rate(link.bps)
181 return (remote.id, remote.ipaddr, bw, my_ip, remote_ip, net)
183 def add_link(self, link):
186 def add_tag(self, sites):
187 s = self.get_site(sites)
188 words = self.hostname.split(".")
189 index = words[0].replace("node", "")
191 self.tag = s.tag + index
195 # Assumes there is at most one Link between two sites
196 def get_sitelink(self, node, sites):
197 site1 = sites[self.site_id]
198 site2 = sites[node.site_id]
199 sl = site1.links.intersection(site2.links)
206 def __init__(self, end1, end2, bps = 1000 * 1000000):
216 def __init__(self, site):
217 self.id = site['site_id']
218 self.node_ids = site['node_ids']
219 self.name = site['abbreviated_name'].replace(" ", "_")
220 self.tag = site['login_base']
221 self.public = site['is_public']
222 self.enabled = site['enabled']
225 def get_sitenodes(self, nodes):
227 for i in self.node_ids:
231 def add_link(self, link):
236 def __init__(self, slice):
237 self.id = slice['slice_id']
238 self.name = slice['name']
239 self.node_ids = set(slice['node_ids'])
240 self.slice_tag_ids = slice['slice_tag_ids']
242 def get_tag(self, tagname, slicetags, node = None):
243 for i in self.slice_tag_ids:
245 if tag.tagname == tagname:
246 if (not node) or (node.id == tag.node_id):
251 def get_nodes(self, nodes):
253 for id in self.node_ids:
258 # Add a new slice tag
259 def add_tag(self, tagname, value, slicetags, node = None):
260 record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
262 record['node_id'] = node.id
264 record['node_id'] = None
265 tag = Slicetag(record)
266 slicetags[tag.id] = tag
267 self.slice_tag_ids.append(tag.id)
272 # Update a slice tag if it exists, else add it
273 def update_tag(self, tagname, value, slicetags, node = None):
274 tag = self.get_tag(tagname, slicetags, node)
275 if tag and tag.value == value:
281 tag = self.add_tag(tagname, value, slicetags, node)
284 def assign_egre_key(self, slicetags):
285 if not self.get_tag('egre_key', slicetags):
287 key = free_egre_key(slicetags)
288 self.update_tag('egre_key', key, slicetags)
290 # Should handle this case...
294 def turn_on_netns(self, slicetags):
295 tag = self.get_tag('netns', slicetags)
296 if (not tag) or (tag.value != '1'):
297 self.update_tag('netns', '1', slicetags)
300 def turn_off_netns(self, slicetags):
301 tag = self.get_tag('netns', slicetags)
302 if tag and (tag.value != '0'):
306 def add_cap_net_admin(self, slicetags):
307 tag = self.get_tag('capabilities', slicetags)
309 caps = tag.value.split(',')
311 if cap == "CAP_NET_ADMIN":
314 newcaps = "CAP_NET_ADMIN," + tag.value
315 self.update_tag('capabilities', newcaps, slicetags)
317 self.add_tag('capabilities', 'CAP_NET_ADMIN', slicetags)
320 def remove_cap_net_admin(self, slicetags):
321 tag = self.get_tag('capabilities', slicetags)
323 caps = tag.value.split(',')
326 if cap != "CAP_NET_ADMIN":
329 value = ','.join(newcaps)
330 self.update_tag('capabilities', value, slicetags)
335 # Update the vsys/setup-link and vsys/setup-nat slice tags.
336 def add_vsys_tags(self, slicetags):
338 for i in self.slice_tag_ids:
340 if tag.tagname == 'vsys':
341 if tag.value == 'setup-link':
343 elif tag.value == 'setup-nat':
346 self.add_tag('vsys', 'setup-link', slicetags)
348 self.add_tag('vsys', 'setup-nat', slicetags)
354 def __init__(self, tag):
355 self.id = tag['slice_tag_id']
357 # Make one up for the time being...
358 self.id = Slicetag.newid
360 self.slice_id = tag['slice_id']
361 self.tagname = tag['tagname']
362 self.value = tag['value']
363 self.node_id = tag['node_id']
368 # Mark a tag as deleted
373 def write(self, api):
376 api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
378 api.plshell.AddSliceTag(api.plauth, self.slice_id,
379 self.tagname, self.value, self.node_id)
380 elif self.deleted and int(self.id) > 0:
381 api.plshell.DeleteSliceTag(api.plauth, self.id)
385 A topology is a compound object consisting of:
386 * a dictionary mapping site IDs to Site objects
387 * a dictionary mapping node IDs to Node objects
388 * the Site objects are connected via SiteLink objects representing
389 the physical topology and available bandwidth
390 * the Node objects are connected via Link objects representing
391 the requested or assigned virtual topology of a slice
394 def __init__(self, api):
396 self.sites = get_sites(api)
397 self.nodes = get_nodes(api)
398 self.tags = get_slice_tags(api)
402 for (s1, s2) in PhysicalLinks:
403 self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
405 for id in self.nodes:
406 self.nodes[id].add_tag(self.sites)
410 if tag.tagname == 'topo_rspec':
411 node1 = self.nodes[tag.node_id]
413 for (id, realip, bw, lvip, rvip, vnet) in l:
414 allocbps = get_tc_rate(bw)
415 node1.bps -= allocbps
417 node2 = self.nodes[id]
418 if node1.id < node2.id:
419 sl = node1.get_sitelink(node2, self.sites)
425 def lookupSite(self, id):
430 raise KeyError("site ID %s not found" % id)
436 sites.append(self.sites[s])
439 def lookupNode(self, id):
444 raise KeyError("node ID %s not found" % id)
450 nodes.append(self.nodes[n])
453 def nodesInTopo(self):
456 if self.nodes[n].links:
457 nodes.append(self.nodes[n])
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:
484 def nodeTopoFromRSpec(self, rspec):
486 raise Error("virtual topology already present")
488 rspecdict = rspec.toDict()
490 for node in self.getNodes():
491 nodedict[node.tag] = node
493 linkspecs = rspecdict['RSpec']['Request'][0]['NetSpec'][0]['LinkSpec']
495 n1 = nodedict[l['endpoint'][0]]
496 n2 = nodedict[l['endpoint'][1]]
497 bps = get_tc_rate(l['bw'][0])
498 self.nodelinks.append(Link(n1, n2, bps))
500 def nodeTopoFromSliceTags(self, slice):
502 raise Error("virtual topology already present")
504 for node in slice.get_nodes(self.nodes):
505 linktag = slice.get_tag('topo_rspec', self.tags, node)
507 l = eval(linktag.value)
508 for (id, realip, bw, lvip, rvip, vnet) in l:
510 bps = get_tc_rate(bw)
511 remote = self.lookupNode(id)
512 self.nodelinks.append(Link(node, remote, bps))
514 def updateSliceTags(self, slice):
515 if not self.nodelinks:
518 slice.update_tag('vini_topo', 'manual', self.tags)
519 slice.assign_egre_key(self.tags)
520 slice.turn_on_netns(self.tags)
521 slice.add_cap_net_admin(self.tags)
523 for node in slice.get_nodes(self.nodes):
525 for link in node.links:
526 linkdesc.append(node.get_topo_rspec(link))
528 topo_str = "%s" % linkdesc
529 slice.update_tag('topo_rspec', topo_str, self.tags, node)
531 # Update slice tags in database
532 for tag in self.getSliceTags():
533 if tag.slice_id == slice.id:
534 if tag.tagname == 'topo_rspec' and not tag.updated:
539 Check the requested topology against the available topology and capacity
541 def verifyNodeTopo(self, hrn, topo, maxbw):
542 maxbps = get_tc_rate(maxbw)
543 for link in self.nodelinks:
545 raise GeniInvalidArgument(bw, "BW")
546 if link.bps > maxbps:
547 raise PermissionError(" %s requested %s but max BW is %s" %
548 (hrn, format_tc_rate(link.bps), maxbw))
552 sitelink = self.lookupSiteLink(n1, n2)
554 raise PermissionError("%s: nodes %s and %s not adjacent" % (hrn, n1.tag, n2.tag))
555 if sitelink.bps < link.bps:
556 raise PermissionError("%s: insufficient capacity between %s and %s" % (hrn, n1.tag, n2.tag))
559 Produce XML directly from the topology specification.
561 def toxml(self, hrn = None):
562 xml = """<?xml version="1.0"?>
563 <RSpec xmlns="http://www.planet-lab.org/sfa/rspec/" name="vini">
565 <NetSpec name="physical_topology">"""
567 for site in self.getSites():
568 if not (site.public and site.enabled):
572 <SiteSpec name="%s"> """ % site.name
574 for node in site.get_sitenodes(self.nodes):
580 <hostname>%s</hostname>
582 </NodeSpec>""" % (node.tag, node.hostname, format_tc_rate(node.bps))
586 for link in self.sitelinks:
589 <endpoint>%s</endpoint>
590 <endpoint>%s</endpoint>
592 </SiteLinkSpec>""" % (link.end1.name, link.end2.name, format_tc_rate(link.bps))
598 name = 'default_topology'
603 <NetSpec name="%s">""" % name
606 for link in self.nodelinks:
609 <endpoint>%s</endpoint>
610 <endpoint>%s</endpoint>
612 </LinkSpec>""" % (link.end1.tag, link.end2.tag, format_tc_rate(link.bps))
614 xml += default_topo_xml
621 # Remove all leading whitespace and newlines
622 lines = xml.split("\n")
625 noblanks += line.strip()
630 Create a dictionary of site objects keyed by site ID
634 for site in api.plshell.GetSites(api.plauth):
635 t = site['site_id'], Site(site)
641 Create a dictionary of node objects keyed by node ID
645 for node in api.plshell.GetNodes(api.plauth):
646 t = node['node_id'], Node(node)
651 Create a dictionary of slice objects keyed by slice ID
653 def get_slice(api, slicename):
654 slice = api.plshell.GetSlices(api.plauth, [slicename])
656 return Slice(slice[0])
661 Create a dictionary of slicetag objects keyed by slice tag ID
663 def get_slice_tags(api):
665 for tag in api.plshell.GetSliceTags(api.plauth):
666 t = tag['slice_tag_id'], Slicetag(tag)
673 def free_egre_key(slicetags):
677 if tag.tagname == 'egre_key':
678 used.add(int(tag.value))
680 for i in range(1, 256):
685 raise KeyError("No more EGRE keys available")