1 from __future__ import with_statement
2 from sfa.util.faults import *
3 from xmlbuilder import XMLBuilder
6 from sfa.plc.network import *
7 from sfa.managers.vini.topology import PhysicalLinks
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, network, node, bps = 1000 * 1000000):
72 Node.__init__(self, network, node)
75 self.name = self.hostname.replace('.vini-veritas.net', '')
77 def get_link_id(self, remote):
78 if self.id < remote.id:
79 link = (self.id<<7) + remote.id
81 link = (remote.id<<7) + self.id
84 def get_iface_id(self, remote):
85 if self.id < remote.id:
91 def get_virt_ip(self, remote):
92 link = self.get_link_id(remote)
93 iface = self.get_iface_id(remote)
95 second = ((link & 0x3f)<<2) + iface
96 return "192.168.%d.%d" % (first, second)
98 def get_virt_net(self, remote):
99 link = self.get_link_id(remote)
101 second = (link & 0x3f)<<2
102 return "192.168.%d.%d/30" % (first, second)
104 def get_topo_rspec(self, link):
105 if link.end1 == self:
107 elif link.end2 == self:
110 raise Error("Link does not connect to Node")
112 my_ip = self.get_virt_ip(remote)
113 remote_ip = remote.get_virt_ip(self)
114 net = self.get_virt_net(remote)
115 bw = format_tc_rate(link.bps)
116 ipaddr = remote.get_primary_iface().ipv4
117 return (remote.id, ipaddr, bw, my_ip, remote_ip, net)
119 def add_link(self, link):
122 # Assumes there is at most one Link between two sites
123 def get_sitelink(self, node):
124 site1 = self.network.sites[self.site_id]
125 site2 = self.network.sites[node.site_id]
126 sl = site1.links.intersection(site2.links)
131 def toxml(self, xml):
132 slice = self.network.slice
133 if self.whitelist and not self.sliver:
134 if not slice or slice.id not in self.whitelist:
137 with xml.node(id = self.idtag):
140 with xml.bw_unallocated(units="kbps"):
141 xml << str(int(self.bps/1000))
142 self.get_primary_iface().toxml(xml)
144 self.sliver.toxml(xml)
147 class ViniSite(Site):
148 def __init__(self, network, site):
149 Site.__init__(self, network, site)
152 def add_link(self, link):
155 class ViniSlice(Slice):
156 def assign_egre_key(self):
157 tag = self.get_tag('egre_key')
160 key = free_egre_key()
162 # Should handle this case...
163 raise Error("ran out of EGRE keys!")
164 tag = self.update_tag('egre_key', key, None, 10)
167 def turn_on_netns(self):
168 tag = self.get_tag('netns')
169 if (not tag) or (tag.value != '1'):
170 tag = self.update_tag('netns', '1', None, 10)
173 def turn_off_netns(self):
174 tag = self.get_tag('netns')
175 if tag and (tag.value != '0'):
179 def add_cap_net_admin(self):
180 tag = self.get_tag('capabilities')
182 caps = tag.value.split(',')
184 if cap == "CAP_NET_ADMIN":
188 newcaps = "CAP_NET_ADMIN," + tag.value
189 self.update_tag('capabilities', newcaps, None, 10)
191 tag = self.add_tag('capabilities', 'CAP_NET_ADMIN', None, 10)
194 def remove_cap_net_admin(self):
195 tag = self.get_tag('capabilities')
197 caps = tag.value.split(',')
200 if cap != "CAP_NET_ADMIN":
203 value = ','.join(newcaps)
204 self.update_tag('capabilities', value, None, 10)
210 def __init__(self, end1, end2, bps = 1000 * 1000000, parent = None):
221 self.parent.children.append(self)
223 def toxml(self, xml):
224 end_ids = "%s %s" % (self.end1.idtag, self.end2.idtag)
227 with xml.vlink(endpoints=end_ids):
228 with xml.description:
229 xml << "%s -- %s" % (self.end1.name, self.end2.name)
231 xml << str(int(self.bps/1000))
233 with xml.link(endpoints=end_ids):
234 with xml.description:
235 xml << "%s -- %s" % (self.end1.name, self.end2.name)
236 with xml.bw_unallocated(units="kbps"):
237 xml << str(int(self.bps/1000))
238 for child in self.children:
243 class ViniNetwork(Network):
244 def __init__(self, api, type = "VINI"):
245 Network.__init__(self, api, type)
249 for (s1, s2) in PhysicalLinks:
250 self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
254 if tag.tagname == 'topo_rspec':
255 node1 = self.nodes[tag.node_id]
257 for (id, realip, bw, lvip, rvip, vnet) in l:
258 allocbps = get_tc_rate(bw)
259 node1.bps -= allocbps
261 node2 = self.nodes[id]
262 if node1.id < node2.id:
263 sl = node1.get_sitelink(node2)
268 def lookupSiteLink(self, node1, node2):
269 site1 = self.sites[node1.site_id]
270 site2 = self.sites[node2.site_id]
271 for link in self.sitelinks:
272 if site1 == link.end1 and site2 == link.end2:
274 if site2 == link.end1 and site1 == link.end2:
280 Check the requested topology against the available topology and capacity
282 def verifyTopology(self):
283 for link in self.nodelinks:
285 raise InvalidRSpec("must request positive bandwidth")
289 sitelink = self.lookupSiteLink(n1, n2)
291 raise InvalidRSpec("nodes %s and %s are not adjacent" %
292 (n1.idtag, n2.idtag))
293 if sitelink.bps < link.bps:
294 raise InvalidRSpec("not enough capacity between %s and %s" %
295 (n1.idtag, n2.idtag))
297 def __add_vlink(self, vlink, parent = None):
299 endpoints = vlink.get("endpoints")
301 (end1, end2) = endpoints.split()
302 n1 = self.lookupNode(end1)
303 n2 = self.lookupNode(end2)
305 """ Try to infer the endpoints for the virtual link """
306 site_endpoints = parent.get("endpoints")
307 (n1, n2) = self.__infer_endpoints(site_endpoints)
309 raise InvalidRSpec("no endpoints given")
311 #print "Added virtual link: %s -- %s" % (n1.tag, n2.tag)
312 bps = int(vlink.findtext("kbps")) * 1000
313 sitelink = self.lookupSiteLink(n1, n2)
315 raise InvalidRSpec("nodes %s and %s are not adjacent" %
316 (n1.idtag, n2.idtag))
317 self.nodelinks.append(Link(n1, n2, bps, sitelink))
321 Infer the endpoints of the virtual link. If the slice exists on
322 only a single node at each end of the physical link, we'll assume that
323 the user wants the virtual link to terminate at these nodes.
325 def __infer_endpoints(self, endpoints):
327 ends = endpoints.split()
330 site = self.lookupSite(end)
331 for id in site.node_ids:
332 if id in self.nodedict:
333 n.append(self.nodedict[id])
336 raise InvalidRSpec("could not infer endpoint for site %s" %
338 #print "Inferred endpoints: %s %s" % (n[0].idtag, n[1].idtag)
341 def addRSpec(self, xml, schema = None):
342 Network.addRSpec(self, xml, schema)
344 for node in self.nodesWithSlivers():
345 self.nodedict[node.id] = node
347 # Find vlinks under link elements
348 for vlink in self.rspec.iterfind("./network/link/vlink"):
349 link = vlink.getparent()
350 self.__add_vlink(vlink, link)
352 # Find vlinks that specify endpoints
353 for vlink in self.rspec.iterfind("./request/vlink[@endpoints]"):
354 self.__add_vlink(vlink)
358 Network.addSlice(self)
360 for node in self.slice.get_nodes():
361 linktag = self.slice.get_tag('topo_rspec', node)
363 l = eval(linktag.value)
364 for (id, realip, bw, lvip, rvip, vnet) in l:
366 bps = get_tc_rate(bw)
367 remote = self.lookupNode(id)
368 sitelink = self.lookupSiteLink(node, remote)
369 self.nodelinks.append(Link(node,remote,bps,sitelink))
372 def updateSliceTags(self):
375 tag = slice.update_tag('vini_topo', 'manual')
376 slice.assign_egre_key()
377 slice.turn_on_netns()
378 slice.add_cap_net_admin()
380 for node in self.nodesWithSlivers():
382 for link in node.links:
383 linkdesc.append(node.get_topo_rspec(link))
385 topo_str = "%s" % linkdesc
386 tag = slice.update_tag('topo_rspec', topo_str, node, 10)
388 # Update or expire the topo_rspec tags
389 for tag in self.getSliceTags():
390 if tag.tagname in ['topo_rspec']:
393 Network.updateSliceTags(self)
396 Produce XML directly from the topology specification.
399 xml = XMLBuilder(format = True, tab_step = " ")
400 with xml.RSpec(type=self.type):
401 name = "Public_" + self.type
403 element = xml.network(name=name, slice=self.slice.hrn)
405 element = xml.network(name=name)
409 self.slice.toxml(xml)
410 for site in self.getSites():
412 for link in self.sitelinks:
415 header = '<?xml version="1.0"?>\n'
416 return header + str(xml)
419 Create a dictionary of ViniSite objects keyed by site ID
421 def get_sites(self, api):
423 for site in api.plshell.GetSites(api.plauth, {'peer_id': None}):
424 t = site['site_id'], ViniSite(self, site)
430 Create a dictionary of ViniNode objects keyed by node ID
432 def get_nodes(self, api):
434 for node in api.plshell.GetNodes(api.plauth, {'peer_id': None}):
435 t = node['node_id'], ViniNode(self, node)
440 Return a ViniSlice object for a single slice
442 def get_slice(self, api, hrn):
443 slicename = hrn_to_pl_slicename(hrn)
444 slice = api.plshell.GetSlices(api.plauth, [slicename])
446 self.slice = ViniSlice(self, slicename, slice[0])