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 return (remote.id, remote.ipaddr, bw, my_ip, remote_ip, net)
118 def add_link(self, link):
121 # Assumes there is at most one Link between two sites
122 def get_sitelink(self, node):
123 site1 = self.network.sites[self.site_id]
124 site2 = self.network.sites[node.site_id]
125 sl = site1.links.intersection(site2.links)
130 def toxml(self, xml):
131 slice = self.network.slice
132 if self.whitelist and not self.sliver:
133 if not slice or slice.id not in self.whitelist:
136 with xml.node(id = self.idtag):
139 with xml.bw_unallocated(units="kbps"):
140 xml << str(int(self.bps/1000))
141 for iface in self.get_ifaces():
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 if not self.get_tag('egre_key'):
159 key = free_egre_key()
160 self.update_tag('egre_key', key)
162 # Should handle this case...
166 def turn_on_netns(self):
167 tag = self.get_tag('netns')
168 if (not tag) or (tag.value != '1'):
169 self.update_tag('netns', '1')
172 def turn_off_netns(self):
173 tag = self.get_tag('netns')
174 if tag and (tag.value != '0'):
178 def add_cap_net_admin(self):
179 tag = self.get_tag('capabilities')
181 caps = tag.value.split(',')
183 if cap == "CAP_NET_ADMIN":
186 newcaps = "CAP_NET_ADMIN," + tag.value
187 self.update_tag('capabilities', newcaps)
189 self.add_tag('capabilities', 'CAP_NET_ADMIN')
192 def remove_cap_net_admin(self):
193 tag = self.get_tag('capabilities')
195 caps = tag.value.split(',')
198 if cap != "CAP_NET_ADMIN":
201 value = ','.join(newcaps)
202 self.update_tag('capabilities', value)
207 # Update the vsys/setup-link and vsys/setup-nat slice tags.
208 def add_vsys_tags(self):
210 for tag in self.network.getSliceTags():
211 if tag.tagname == 'vsys':
212 if tag.value == 'setup-link':
214 elif tag.value == 'setup-nat':
217 self.add_tag('vsys', 'setup-link')
219 self.add_tag('vsys', 'setup-nat')
225 def __init__(self, end1, end2, bps = 1000 * 1000000, parent = None):
236 self.parent.children.append(self)
238 def toxml(self, xml):
239 end_ids = "%s %s" % (self.end1.idtag, self.end2.idtag)
242 with xml.vlink(endpoints=end_ids):
243 with xml.description:
244 xml << "%s -- %s" % (self.end1.name, self.end2.name)
245 with xml.bw(units="kbps"):
246 xml << str(int(self.bps/1000))
248 with xml.link(endpoints=end_ids):
249 with xml.description:
250 xml << "%s -- %s" % (self.end1.name, self.end2.name)
251 with xml.bw_unallocated(units="kbps"):
252 xml << str(int(self.bps/1000))
253 for child in self.children:
258 class ViniNetwork(Network):
259 def __init__(self, api, type = "VINI"):
260 Network.__init__(self, api, type)
264 for (s1, s2) in PhysicalLinks:
265 self.sitelinks.append(Link(self.sites[s1], self.sites[s2]))
269 if tag.tagname == 'topo_rspec':
270 node1 = self.nodes[tag.node_id]
272 for (id, realip, bw, lvip, rvip, vnet) in l:
273 allocbps = get_tc_rate(bw)
274 node1.bps -= allocbps
276 node2 = self.nodes[id]
277 if node1.id < node2.id:
278 sl = node1.get_sitelink(node2)
283 def lookupSiteLink(self, node1, node2):
284 site1 = self.sites[node1.site_id]
285 site2 = self.sites[node2.site_id]
286 for link in self.sitelinks:
287 if site1 == link.end1 and site2 == link.end2:
289 if site2 == link.end1 and site1 == link.end2:
295 Check the requested topology against the available topology and capacity
297 def verifyTopology(self):
298 for link in self.nodelinks:
300 raise InvalidRSpec("must request positive bandwidth")
304 sitelink = self.lookupSiteLink(n1, n2)
306 raise InvalidRSpec("nodes %s and %s are not adjacent" %
307 (n1.idtag, n2.idtag))
308 if sitelink.bps < link.bps:
309 raise InvalidRSpec("not enough capacity between %s and %s" %
310 (n1.idtag, n2.idtag))
312 def __add_vlink(self, vlink, parent = None):
314 endpoints = vlink.get("endpoints")
316 (end1, end2) = endpoints.split()
317 n1 = self.lookupNode(end1)
318 n2 = self.lookupNode(end2)
320 """ Try to infer the endpoints for the virtual link """
321 site_endpoints = parent.get("endpoints")
322 (n1, n2) = self.__infer_endpoints(site_endpoints)
324 raise InvalidRSpec("no endpoints given")
326 #print "Added virtual link: %s -- %s" % (n1.tag, n2.tag)
327 bps = int(vlink.findtext("kbps")) * 1000
328 sitelink = self.lookupSiteLink(n1, n2)
330 raise InvalidRSpec("nodes %s and %s are not adjacent" %
331 (n1.idtag, n2.idtag))
332 self.nodelinks.append(Link(n1, n2, bps, sitelink))
336 Infer the endpoints of the virtual link. If the slice exists on
337 only a single node at each end of the physical link, we'll assume that
338 the user wants the virtual link to terminate at these nodes.
340 def __infer_endpoints(self, endpoints):
342 ends = endpoints.split()
345 site = self.lookupSite(end)
346 for id in site.node_ids:
347 if id in self.nodedict:
348 n.append(self.nodedict[id])
351 raise InvalidRSpec("could not infer endpoint for site %s" %
353 #print "Inferred endpoints: %s %s" % (n[0].idtag, n[1].idtag)
356 def addRSpec(self, xml, schema = None):
357 Network.addRSpec(self, xml, schema)
359 for node in self.nodesWithSlivers():
360 self.nodedict[node.id] = node
362 # Find vlinks under link elements
363 for vlink in self.rspec.iterfind("./network/link/vlink"):
364 link = vlink.getparent()
365 self.__add_vlink(vlink, slicenodes, link)
367 # Find vlinks that specify endpoints
368 for vlink in rspec.iterfind("./request/vlink[@endpoints]"):
369 self.__add_vlink(vlink, slicenodes)
373 Network.addSlice(self)
375 for node in self.slice.get_nodes():
376 linktag = self.slice.get_tag('topo_rspec', node)
378 l = eval(linktag.value)
379 for (id, realip, bw, lvip, rvip, vnet) in l:
381 bps = get_tc_rate(bw)
382 remote = self.lookupNode(id)
383 sitelink = self.lookupSiteLink(node, remote)
384 self.nodelinks.append(Link(node,remote,bps,sitelink))
387 def updateSliceTags(self):
390 slice.update_tag('vini_topo', 'manual')
391 slice.assign_egre_key()
392 slice.turn_on_netns()
393 slice.add_cap_net_admin()
395 for node in slice.get_nodes():
397 for link in node.links:
398 linkdesc.append(node.get_topo_rspec(link))
400 topo_str = "%s" % linkdesc
401 slice.update_tag('topo_rspec', topo_str, node)
403 Network.updateSliceTags(self)
406 Produce XML directly from the topology specification.
409 xml = XMLBuilder(format = True, tab_step = " ")
410 with xml.RSpec(type=self.type):
411 name = "Public_" + self.type
413 element = xml.network(name=name, slice=self.slice.hrn)
415 element = xml.network(name=name)
419 self.slice.toxml(xml)
420 for site in self.getSites():
422 for link in self.sitelinks:
425 header = '<?xml version="1.0"?>\n'
426 return header + str(xml)
429 Create a dictionary of ViniSite objects keyed by site ID
431 def get_sites(self, api):
433 for site in api.plshell.GetSites(api.plauth, {'peer_id': None}):
434 t = site['site_id'], ViniSite(self, site)
440 Create a dictionary of ViniNode objects keyed by node ID
442 def get_nodes(self, api):
444 for node in api.plshell.GetNodes(api.plauth, {'peer_id': None}):
445 t = node['node_id'], ViniNode(self, node)
450 Return a ViniSlice object for a single slice
452 def get_slice(self, api, hrn):
453 slicename = hrn_to_pl_slicename(hrn)
454 slice = api.plshell.GetSlices(api.plauth, [slicename])
456 self.slice = ViniSlice(self, slicename, slice[0])