PlanetLab API functions for slice autoconfig
[raven.git] / apps / gacks / bridgeconfig.py
1 try:
2    # 2.6 and above
3    from ast import literal_eval
4 except ImportError:
5    # below 2.6, unsafe
6    from __builtin__ import eval as literal_eval
7
8 class WizardHelper:
9     """ Base class for wizards, defines several helper methods for getting
10         at slice tags, accessing the wizardconfig tag, etc.
11     """
12
13     def __init__(self, plc, slice):
14         self.plc = plc
15
16         if isinstance(slice, dict):
17             # do nothing; caller passed us a slice record
18             pass
19         else:
20             slices = self.plc.GetSlices(slice, ["name", "slice_id", "node_ids", "slice_tag_ids"])
21             if not slices:
22                 raise SliceNotFound(slice)
23             slice = slices[0]
24
25         self.slice = slice
26         self.slice_id = slice['slice_id']
27         self.wizard_config = None
28
29     def get_tag(self, tagname, node_id=None):
30         if node_id is not None:
31             tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id, "node_id": node_id})
32         else:
33             tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id})
34
35         if len(tags) == 0:
36             return None
37
38         return tags[0]
39
40     def get_tag_value(self, tagname, node_id=None):
41         tag = self.get_tag(tagname, node_id)
42         if tag is None:
43             return None
44         return tag["value"]
45
46     def set_tag(self, tagname, node_id, value):
47         if node_id is not None:
48             tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id, "node_id": node_id})
49         else:
50             tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id})
51
52         if tags:
53             if (value == None):
54                 self.plc.DeleteSliceTag(tags[0]["slice_tag_id"])
55             elif tags[0]["value"] != value:
56                 self.plc.UpdateSliceTag(tags[0]['slice_tag_id'], value)\r
57         elif (value != None):\r
58             self.plc.AddSliceTag(self.slice_id, tagname, value, node_id)
59
60     def clear_tags(self, tagname):
61         tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id})
62         for tag in tags:
63             self.plc.DeleteSliceTag(tag["slice_tag_id"])
64
65     def get_wizard_config(self, name=None, default=None):
66         if self.wizard_config is not None:
67             return self.wizard_config
68
69         self.wizard_config = {}
70
71         tag = self.get_tag("slice_wizardconfig", None)
72         if tag is not None:
73             try:
74                 self.wizard_config = literal_eval(tags[0]["value"])
75             except:
76                 self.wizard_config = {}
77
78         if name:
79             return self.wizard_config.get("name", default)
80
81         return self.wizard_config
82
83     def update_wizard_config(self, name=None, value=None):
84         if self.wizard_config is None:
85             self.get_wizard_config()
86
87         if name:
88             self.wizard_config[name] = value
89
90         self.set_tag("slice_wizardconfig", None, str(self.wizard_config))
91
92     def get_master_node(self):
93         return self.get_wizard_config("master_node", None)
94
95     def set_master_node(self, master_node):
96         self.update_wizard_config("master_node", master_node)
97
98     def get_network_method(self):
99         return self.get_wizard_config("network_method", None)
100
101     def set_network_method(self, network_method):
102         self.update_wizard_config("network_method", network_method)
103
104     def pick_headnode(self, nodes):
105         """ Picks a headnode from the list of nodes, if a headnode hasn't
106             already been picked.
107
108             Orders the list of nodes so that the headnode is always at the
109             top.
110         """
111
112         if not nodes:
113             # there are no nodes, we can't pick anything
114             return (nodes, None)
115
116         headnode = None
117         headnode_hostname = self.get_master_node()
118         for node in nodes:
119             if headnode_hostname == node["hostname"]:
120                 headnode = node
121
122         if headnode:
123             # move headnode up to the front
124             nodes = [headnode] + [x for x in nodes if x!=headnode]
125         else:
126             headnode = nodes[0]
127             self.set_master_node(headnode["hostname"])
128
129         return (nodes, headnode)
130
131 class NetworkBuilder(WizardHelper):
132     def __init__(self, plc, slice):
133         WizardHelper.__init__(self, plc, slice)
134         self.want_default_interface = True
135
136     def add_host(self, hostmap, node, address):
137         hostmap.append("%s %s" % (address, node["hostname"]))
138
139     def configure_slice(self):
140         raise "Not Implemented"
141
142 class NetworkBuilder_gre(NetworkBuilder):
143     """ This sets up nodes using private bridges and GRE tunnels.
144
145         There can be up to 65535 node_ids.
146
147         Each bridge gets the address 10.1.<node_id_hi>.<node_id_lo>
148         Each sliver gets the address 10.2.<node_id_hi>.<node_id_lo>
149
150         GRE tunnels are setup between nodes. Nodes can talk to each other across
151         sites. The current topology that is used is a star, with all nodes talking
152         to a head node. This represents a single point of failure, as well as a
153         bottleneck.
154     """
155
156     def __init__(self, plc, slice):
157         NetworkBuilder.__init__(self, plc, slice)
158
159     def configure_slice(self):
160         slice = self.slice
161         slice_id = slice["slice_id"]
162
163         nodes = self.plc.GetNodes(slice["node_ids"], ["node_id", "hostname"])
164         interfaces = self.plc.GetInterfaces({"node_id": slice["node_ids"]}, ["node_id", "ip", "is_primary"])
165         topology = self.generate_star_topology(nodes)
166
167         node_ips = {}
168         for interface in interfaces:
169             if interface["is_primary"]:
170                 node_ips[interface["node_id"]] = interface["ip"]
171
172         # filter out any nodes where we do not know the ip address
173         nodes = [x for x in nodes if node_ips.get(x["node_id"], None)]
174
175         bridge_name = "br-slice-" + str(slice_id)
176         self.set_tag("slice_bridge_name", None, bridge_name)
177
178         hostmap = []
179         for (node_id, neighbor_list) in topology.items():
180             sliver_ip_addr = "10.2.%d.%d" % (node_id/254, node_id%254+1)
181             bridge_ip_addr = "10.1.%d.%d" % (node_id/254, node_id%254+1)
182
183             neighbors = []
184             for neighbor_id in neighbor_list:
185                 neighbors.append("%d/%s" % (neighbor_id, node_ips[neighbor_id]))
186
187             interface_tag = []
188             interface_tag.append({'bridge':bridge_name, 'DEVICE':'eth0', 'BOOTPROTO':'static', 'ONBOOT':'yes', 'PRIMARY':'yes', 'IPADDR': sliver_ip_addr, 'NETMASK': '255.0.0.0'})
189             if self.want_default_interface:
190                 interface_tag.append({'DEVICE':'eth1', 'BOOTPROTO':'dhcp', 'ONBOOT':'yes'})
191
192             self.set_tag("slice_bridge_addr", node_id, bridge_ip_addr)
193             self.set_tag("slice_bridge_neighbors", node_id, ",".join(neighbors))
194             self.set_tag("interface", node_id, str(interface_tag))
195
196             node = [x for x in nodes if x["node_id"] == node_id][0]
197
198             self.add_host(hostmap, node, sliver_ip_addr)
199
200         self.set_tag("slice_hostmap", None, "\n".join(hostmap))
201         self.set_network_method("gre")
202
203     def generate_star_topology(self, nodes):
204         (nodes, headnode) = self.pick_headnode(nodes)
205         if not headnode:
206             # if there are no nodes, then there is no topology
207             return {}
208
209         topology = {}
210         headnode_id = headnode["node_id"]
211         for node in nodes[1:]:
212             node_id = node["node_id"]
213             topology[headnode_id] = topology.get(headnode_id, []) + [node_id]
214             topology[node_id] = [headnode_id]
215
216         return topology
217
218 class NetworkBuilder_IPv4_shared(NetworkBuilder):
219     """ This sets up each slice as a class A private network.
220
221         10.<slice_id_hi>.<slice_id_lo>.<node_id>
222
223         There can be at most 65535 slice_ids. There can be at most 255 nodes
224         per site.
225
226         Nodes in one site cannot talk to nodes in another site
227     """
228
229     def __init__(self, plc, slice):
230         NetworkBuilder.__init__(self, plc, slice)
231
232     def configure_slice(self):
233         slice = self.slice
234         slice_id = slice["slice_id"]
235
236         nodes = self.plc.GetNodes(slice["node_ids"], ["node_id", "hostname", "site_id"])
237         (nodes, headnode) = self.pick_headnode(nodes)
238
239         site_ids = []
240         for node in nodes:
241            if not (node["site_id"] in site_ids):
242                site_ids.append(node["site_id"])
243
244         node_indices = {}
245         sites = self.plc.GetSites(site_ids)
246         for site in sites:
247             for (i, node_id) in enumerate(site["node_ids"]):
248                 node_indices[node_id] = i
249
250         hostmap = []
251         for node in nodes:
252             node_id = node["node_id"]
253             node_index = node_indices.get(node_id, 0)
254             sliver_ip_addr = "10.%d.%d.%d" % (slice_id>>8, slice_id&0xFF, node_index + 1)
255             interface_tag = []
256             interface_tag.append({'bridge':'public0', 'DEVICE':'eth0', 'BOOTPROTO':'static', 'ONBOOT':'yes', 'PRIMARY':'yes', 'IPADDR': sliver_ip_addr, 'NETMASK': '255.255.255.0'})
257             if self.want_default_interface:
258                 interface_tag.append({'DEVICE':'eth1', 'BOOTPROTO':'dhcp', 'ONBOOT':'yes'})
259             self.set_tag("interface", node_id, str(interface_tag))
260
261             self.add_host(hostmap, node, sliver_ip_addr)
262
263         self.set_tag("slice_hostmap", None, "\n".join(hostmap))
264         self.set_network_method("ipv4_shared")
265         self.clear_tags("slice_bridge_name")
266         self.clear_tags("slice_bridge_addr")
267         self.clear_tags("slice_bridge_neighbors")
268
269 class NetworkBuilder_IPv6_shared(NetworkBuilder):
270     """ This sets up each slice as a class A private network.
271
272         10.<slice_id_hi>.<slice_id_lo>.<node_id>
273
274         There can be at most 65535 slice_ids. There can be at most 255 nodes
275         per site.
276
277         Nodes in one site cannot talk to nodes in another site
278     """
279
280     def __init__(self, plc, slice):
281         NetworkBuilder.__init__(self, plc, slice)
282
283     def configure_slice(self):
284         slice = self.slice
285         slice_id = slice["slice_id"]
286
287         nodes = self.plc.GetNodes(slice["node_ids"], ["node_id", "hostname", "site_id"])
288         (nodes, headnode) = self.pick_headnode(nodes)
289
290         hostmap = []
291         for node in nodes:
292             node_id = node["node_id"]
293             ipv6addr = "fec0::%04x:%04x" % (slice_id, node_id)
294             interface_tag = []
295             interface_tag.append({'bridge':'public0', 'DEVICE':'eth0', 'BOOTPROTO':'static', 'ONBOOT':'yes', 'PRIMARY':'yes', 'IPV6ADDR':ipv6addr, 'IPV6INIT': 'yes'})
296             if self.want_default_interface:
297                 interface_tag.append({'DEVICE':'eth1', 'BOOTPROTO':'dhcp', 'ONBOOT':'yes'})
298             self.set_tag("interface", node_id, str(interface_tag))
299
300             self.add_host(hostmap, node, ipv6addr)
301
302         self.set_tag("slice_hostmap", None, "\n".join(hostmap))
303         self.set_network_method("ipv6_shared")
304         self.clear_tags("slice_bridge_name")
305         self.clear_tags("slice_bridge_addr")
306         self.clear_tags("slice_bridge_neighbors")
307
308 class NetworkBuilder_default(NetworkBuilder):
309     def __init__(self, plc, slice):
310         NetworkBuilder.__init__(self, plc, slice)
311
312     def configure_slice(self):
313         self.set_network_method(None)
314         self.clear_tags("slice_hostmap")
315         self.clear_tags("slice_bridge_name")
316         self.clear_tags("slice_bridge_addr")
317         self.clear_tags("slice_bridge_neighbors")
318         self.clear_tags("interface")
319
320 def AutoConfig(plc, slice, options={}):
321     wizard = WizardHelper(plc, slice)
322     slice = wizard.slice # wizardhelper will resolve this into a slice object
323
324     network_method = options.get("network_method", None)
325     if network_method is None:
326         network_method = wizard.get_network_method()
327
328     if network_method == "gre":
329         NetworkBuilder_gre(plc, slice).configure_slice()
330     elif network_method == "ipv4_shared":
331         NetworkBuilder_IPv4_shared(plc, slice).configure_slice()
332     elif network_method == "ipv6_shared":
333         NetworkBuilder_IPv6_shared(plc, slice).configure_slice()
334     else:
335         NetworkBuilder_default(plc, slice).configure_slice()
336
337
338