Account for already allocated bandwidth in Capacity
[sfa.git] / sfa / rspecs / aggregates / vini / utils.py
1 import re
2 import socket
3 from sfa.rspecs.aggregates.vini.topology import *
4
5 # Taken from bwlimit.py
6 #
7 # See tc_util.c and http://physics.nist.gov/cuu/Units/binary.html. Be
8 # warned that older versions of tc interpret "kbps", "mbps", "mbit",
9 # and "kbit" to mean (in this system) "kibps", "mibps", "mibit", and
10 # "kibit" and that if an older version is installed, all rates will
11 # be off by a small fraction.
12 suffixes = {
13     "":         1,
14     "bit":      1,
15     "kibit":    1024,
16     "kbit":     1000,
17     "mibit":    1024*1024,
18     "mbit":     1000000,
19     "gibit":    1024*1024*1024,
20     "gbit":     1000000000,
21     "tibit":    1024*1024*1024*1024,
22     "tbit":     1000000000000,
23     "bps":      8,
24     "kibps":    8*1024,
25     "kbps":     8000,
26     "mibps":    8*1024*1024,
27     "mbps":     8000000,
28     "gibps":    8*1024*1024*1024,
29     "gbps":     8000000000,
30     "tibps":    8*1024*1024*1024*1024,
31     "tbps":     8000000000000
32 }
33
34
35 def get_tc_rate(s):
36     """
37     Parses an integer or a tc rate string (e.g., 1.5mbit) into bits/second
38     """
39
40     if type(s) == int:
41         return s
42     m = re.match(r"([0-9.]+)(\D*)", s)
43     if m is None:
44         return -1
45     suffix = m.group(2).lower()
46     if suffixes.has_key(suffix):
47         return int(float(m.group(1)) * suffixes[suffix])
48     else:
49         return -1
50
51 def format_tc_rate(rate):
52     """
53     Formats a bits/second rate into a tc rate string
54     """
55
56     if rate >= 1000000000 and (rate % 1000000000) == 0:
57         return "%.0fgbit" % (rate / 1000000000.)
58     elif rate >= 1000000 and (rate % 1000000) == 0:
59         return "%.0fmbit" % (rate / 1000000.)
60     elif rate >= 1000:
61         return "%.0fkbit" % (rate / 1000.)
62     else:
63         return "%.0fbit" % rate
64
65
66 class Node:
67     def __init__(self, node, mbps = 1000):
68         self.id = node['node_id']
69         self.hostname = node['hostname']
70         self.shortname = self.hostname.replace('.vini-veritas.net', '')
71         self.site_id = node['site_id']
72         self.ipaddr = socket.gethostbyname(self.hostname)
73         self.bps = mbps * 1000000
74         self.links = []
75
76     def get_link_id(self, remote):
77         if self.id < remote.id:
78             link = (self.id<<7) + remote.id
79         else:
80             link = (remote.id<<7) + self.id
81         return link
82         
83     def get_iface_id(self, remote):
84         if self.id < remote.id:
85             iface = 1
86         else:
87             iface = 2
88         return iface
89     
90     def get_virt_ip(self, remote):
91         link = self.get_link_id(remote)
92         iface = self.get_iface_id(remote)
93         first = link >> 6
94         second = ((link & 0x3f)<<2) + iface
95         return "192.168.%d.%d" % (first, second)
96
97     def get_virt_net(self, remote):
98         link = self.get_link_id(remote)
99         first = link >> 6
100         second = (link & 0x3f)<<2
101         return "192.168.%d.%d/30" % (first, second)
102         
103     def get_site(self, sites):
104         return sites[self.site_id]
105     
106     def init_links(self):
107         self.links = []
108         
109     def add_link(self, remote, bw):
110         my_ip = self.get_virt_ip(remote)
111         remote_ip = remote.get_virt_ip(self)
112         net = self.get_virt_net(remote)
113         link = remote.id, remote.ipaddr, bw, my_ip, remote_ip, net
114         self.links.append(link)
115         
116     def add_tag(self, sites):
117         s = self.get_site(sites)
118         words = self.hostname.split(".")
119         index = words[0].replace("node", "")
120         if index.isdigit():
121             self.tag = s.tag + index
122         else:
123             self.tag = None
124
125     # Assumes there is at most one SiteLink between two sites
126     def get_sitelink(self, node, sites):
127         site1 = sites[self.site_id]
128         site2 = sites[node.site_id]
129         sl = site1.sitelinks.intersection(site2.sitelinks)
130         if len(sl):
131             return sl.pop()
132         return None
133     
134
135 class SiteLink:
136     def __init__(self, site1, site2, mbps = 1000):
137         self.site1 = site1
138         self.site2 = site2
139         self.bps = mbps * 1000000
140         
141         site1.add_sitelink(self)
142         site2.add_sitelink(self)
143         
144         
145 class Site:
146     def __init__(self, site):
147         self.id = site['site_id']
148         self.node_ids = site['node_ids']
149         self.name = site['abbreviated_name']
150         self.tag = site['login_base']
151         self.public = site['is_public']
152         self.sitelinks = set()
153
154     def get_sitenodes(self, nodes):
155         n = []
156         for i in self.node_ids:
157             n.append(nodes[i])
158         return n
159     
160     def add_sitelink(self, link):
161         self.sitelinks.add(link)
162     
163     
164 class Slice:
165     def __init__(self, slice):
166         self.id = slice['slice_id']
167         self.name = slice['name']
168         self.node_ids = set(slice['node_ids'])
169         self.slice_tag_ids = slice['slice_tag_ids']
170     
171     def get_tag(self, tagname, slicetags, node = None):
172         for i in self.slice_tag_ids:
173             tag = slicetags[i]
174             if tag.tagname == tagname:
175                 if (not node) or (node.id == tag.node_id):
176                     return tag
177         else:
178             return None
179         
180     def get_nodes(self, nodes):
181         n = []
182         for id in self.node_ids:
183             n.append(nodes[id])
184         return n
185              
186     
187     # Add a new slice tag   
188     def add_tag(self, tagname, value, slicetags, node = None):
189         record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
190         if node:
191             record['node_id'] = node.id
192         else:
193             record['node_id'] = None
194         tag = Slicetag(record)
195         slicetags[tag.id] = tag
196         self.slice_tag_ids.append(tag.id)
197         tag.changed = True       
198         tag.updated = True
199         return tag
200     
201     # Update a slice tag if it exists, else add it             
202     def update_tag(self, tagname, value, slicetags, node = None):
203         tag = self.get_tag(tagname, slicetags, node)
204         if tag and tag.value == value:
205             value = "no change"
206         elif tag:
207             tag.value = value
208             tag.changed = True
209         else:
210             tag = self.add_tag(tagname, value, slicetags, node)
211         tag.updated = True
212             
213     def assign_egre_key(self, slicetags):
214         if not self.get_tag('egre_key', slicetags):
215             try:
216                 key = free_egre_key(slicetags)
217                 self.update_tag('egre_key', key, slicetags)
218             except:
219                 # Should handle this case...
220                 pass
221         return
222             
223     def turn_on_netns(self, slicetags):
224         tag = self.get_tag('netns', slicetags)
225         if (not tag) or (tag.value != '1'):
226             self.update_tag('netns', '1', slicetags)
227         return
228    
229     def turn_off_netns(self, slicetags):
230         tag = self.get_tag('netns', slicetags)
231         if tag and (tag.value != '0'):
232             tag.delete()
233         return
234     
235     def add_cap_net_admin(self, slicetags):
236         tag = self.get_tag('capabilities', slicetags)
237         if tag:
238             caps = tag.value.split(',')
239             for cap in caps:
240                 if cap == "CAP_NET_ADMIN":
241                     return
242             else:
243                 newcaps = "CAP_NET_ADMIN," + tag.value
244                 self.update_tag('capabilities', newcaps, slicetags)
245         else:
246             self.add_tag('capabilities', 'CAP_NET_ADMIN', slicetags)
247         return
248     
249     def remove_cap_net_admin(self, slicetags):
250         tag = self.get_tag('capabilities', slicetags)
251         if tag:
252             caps = tag.value.split(',')
253             newcaps = []
254             for cap in caps:
255                 if cap != "CAP_NET_ADMIN":
256                     newcaps.append(cap)
257             if newcaps:
258                 value = ','.join(newcaps)
259                 self.update_tag('capabilities', value, slicetags)
260             else:
261                 tag.delete()
262         return
263
264     # Update the vsys/setup-link and vsys/setup-nat slice tags.
265     def add_vsys_tags(self, slicetags):
266         link = nat = False
267         for i in self.slice_tag_ids:
268             tag = slicetags[i]
269             if tag.tagname == 'vsys':
270                 if tag.value == 'setup-link':
271                     link = True
272                 elif tag.value == 'setup-nat':
273                     nat = True
274         if not link:
275             self.add_tag('vsys', 'setup-link', slicetags)
276         if not nat:
277             self.add_tag('vsys', 'setup-nat', slicetags)
278         return
279
280
281 class Slicetag:
282     newid = -1 
283     def __init__(self, tag):
284         self.id = tag['slice_tag_id']
285         if not self.id:
286             # Make one up for the time being...
287             self.id = Slicetag.newid
288             Slicetag.newid -= 1
289         self.slice_id = tag['slice_id']
290         self.tagname = tag['tagname']
291         self.value = tag['value']
292         self.node_id = tag['node_id']
293         self.updated = False
294         self.changed = False
295         self.deleted = False
296     
297     # Mark a tag as deleted
298     def delete(self):
299         self.deleted = True
300         self.updated = True
301     
302     def write(self, api):
303         if self.changed:
304             if int(self.id) > 0:
305                 api.plshell.UpdateSliceTag(api.plauth, self.id, self.value)
306             else:
307                 api.plshell.AddSliceTag(api.plauth, self.slice_id, 
308                                         self.tagname, self.value, self.node_id)
309         elif self.deleted and int(self.id) > 0:
310             api.plshell.DeleteSliceTag(api.plauth, self.id)
311
312
313 """
314 Create a dictionary of site objects keyed by site ID
315 """
316 def get_sites(api):
317     tmp = []
318     for site in api.plshell.GetSites(api.plauth):
319         t = site['site_id'], Site(site)
320         tmp.append(t)
321     return dict(tmp)
322
323
324 """
325 Create a dictionary of node objects keyed by node ID
326 """
327 def get_nodes(api):
328     tmp = []
329     for node in api.plshell.GetNodes(api.plauth):
330         t = node['node_id'], Node(node)
331         tmp.append(t)
332     return dict(tmp)
333
334 """
335 Create a dictionary of slice objects keyed by slice ID
336 """
337 def get_slice(api, slicename):
338     slice = api.plshell.GetSlices(api.plauth, [slicename])
339     if slice:
340         return Slice(slice[0])
341     else:
342         return None
343
344 """
345 Create a dictionary of slicetag objects keyed by slice tag ID
346 """
347 def get_slice_tags(api):
348     tmp = []
349     for tag in api.plshell.GetSliceTags(api.plauth):
350         t = tag['slice_tag_id'], Slicetag(tag)
351         tmp.append(t)
352     return dict(tmp)
353     
354 """
355 Find a free EGRE key
356 """
357 def free_egre_key(slicetags):
358     used = set()
359     for i in slicetags:
360         tag = slicetags[i]
361         if tag.tagname == 'egre_key':
362             used.add(int(tag.value))
363                 
364     for i in range(1, 256):
365         if i not in used:
366             key = i
367             break
368     else:
369         raise KeyError("No more EGRE keys available")
370         
371     return "%s" % key
372    
373 """
374 Return the network topology.
375 The topology consists 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 """
381 def get_topology(api):
382     sites = get_sites(api)
383     nodes = get_nodes(api)
384     tags = get_slice_tags(api)
385     
386     for (s1, s2) in PhysicalLinks:
387         SiteLink(sites[s1], sites[s2])
388         
389     for id in nodes:
390         nodes[id].add_tag(sites)
391         
392     for t in tags:
393         tag = tags[t]
394         if tag.tagname == 'topo_rspec':
395             node1 = nodes[tag.node_id]
396             l = eval(tag.value)
397             for (id, realip, bw, lvip, rvip, vnet) in l:
398                 allocbps = get_tc_rate(bw)
399                 node1.bps -= allocbps
400                 try:
401                     node2 = nodes[id]
402                     if node1.id < node2.id:
403                         sl = node1.get_sitelink(node2, sites)
404                         sl.bps -= allocbps
405                 except:
406                     pass
407         
408     return (sites, nodes, tags)