Implementing PlanetLab testbed
[nepi.git] / src / nepi / testbeds / planetlab / metadata_v01.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from constants import TESTBED_ID
5 from nepi.core import metadata
6 from nepi.core.attributes import Attribute
7 from nepi.util import validation
8 from nepi.util.constants import STATUS_NOT_STARTED, STATUS_RUNNING, \
9         STATUS_FINISHED
10
11 NODE = "Node"
12 NODEIFACE = "NodeInterface"
13 TUNIFACE = "TunInterface"
14 APPLICATION = "Application"
15 INTERNET = "Internet"
16
17 PL_TESTBED_ID = "planetlab"
18
19 ### Connection functions ####
20
21 def connect_node_iface_node(testbed_instance, node, iface):
22     iface.node = node
23
24 def connect_node_iface_inet(testbed_instance, node, internet):
25     iface.has_internet = True
26
27 def connect_tun_iface_node(testbed_instance, node, iface):
28     iface.node = node
29
30 ### Creation functions ###
31
32 def create_node(testbed_instance, guid):
33     parameters = testbed_instance._get_parameters(guid)
34     
35     # create element with basic attributes
36     element = testbed_instance._make_node(parameters)
37     
38     # add constraint on number of (real) interfaces
39     # by counting connected devices
40     dev_guids = testbed_instance.get_connected(guid, "node", "devs")
41     num_open_ifaces = sum( # count True values
42         TUNEIFACE == testbed_instance._get_factory_id(guid)
43         for guid in dev_guids )
44     element.min_num_external_ifaces = num_open_ifaces
45     
46     testbed_instance.elements[guid] = element
47
48 def create_nodeiface(testbed_instance, guid):
49     parameters = testbed_instance._get_parameters(guid)
50     element = testbed_instance.create_node_iface(parameters)
51     testbed_instance.elements[guid] = element
52
53 def create_tuniface(testbed_instance, guid):
54     parameters = testbed_instance._get_parameters(guid)
55     element = testbed_instance._make_tun_iface(parameters)
56     testbed_instance.elements[guid] = element
57
58 def create_application(testbed_instance, guid):
59     parameters = testbed_instance._get_parameters(guid)
60     element = testbed_instance._make_internet(parameters)
61     testbed_instance.elements[guid] = element
62
63 def create_internet(testbed_instance, guid):
64     parameters = testbed_instance._get_parameters(guid)
65     element = None #TODO
66     testbed_instance.elements[guid] = element
67
68 ### Start/Stop functions ###
69
70 def start_application(testbed_instance, guid):
71     parameters = testbed_instance._get_parameters(guid)
72     traces = testbed_instance._get_traces(guid)
73     user = parameters["user"]
74     command = parameters["command"]
75     stdout = stderr = None
76     if "stdout" in traces:
77         # TODO
78         pass
79     if "stderr" in traces:
80         # TODO
81         pass
82
83     node_guids = testbed_instance.get_connected(guid, "node", "apps")
84     if not node_guid:
85         raise RuntimeError, "Can't instantiate interface %d outside planetlab node" % (guid,)
86     
87     node = testbed_instance.elements[node_guids[0]]
88     # TODO
89     pass
90
91 ### Status functions ###
92
93 def status_application(testbed_instance, guid):
94     if guid not in testbed_instance.elements.keys():
95         return STATUS_NOT_STARTED
96     app = testbed_instance.elements[guid]
97     # TODO
98     return STATUS_NOT_STARTED
99
100 ### Configure functions ###
101
102 def configure_nodeiface(testbed_instance, guid):
103     element = testbed_instance._elements[guid]
104     if not guid in testbed_instance._add_address:
105         return
106     
107     # Cannot explicitly configure addresses
108     del testbed_instance._add_address[guid]
109     
110     # Get siblings
111     node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
112     dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
113     siblings = [ self._element[dev_guid] 
114                  for dev_guid in dev_guids
115                  if dev_guid != guid ]
116     
117     # Fetch address from PLC api
118     element.pick_iface(siblings)
119     
120     # Do some validations
121     element.validate()
122
123 def configure_tuniface(testbed_instance, guid):
124     element = testbed_instance._elements[guid]
125     if not guid in testbed_instance._add_address:
126         return
127     addresses = testbed_instance._add_address[guid]
128     for address in addresses:
129         (address, netprefix, broadcast) = address
130         # TODO
131     
132     # Do some validations
133     element.validate()
134
135 def configure_node(testbed_instance, guid):
136     element = testbed_instance._elements[guid]
137     
138     # Do some validations
139     element.validate()
140
141 ### Factory information ###
142
143 connector_types = dict({
144     "apps": dict({
145                 "help": "Connector from node to applications", 
146                 "name": "apps",
147                 "max": -1, 
148                 "min": 0
149             }),
150     "devs": dict({
151                 "help": "Connector from node to network interfaces", 
152                 "name": "devs",
153                 "max": -1, 
154                 "min": 0
155             }),
156     "inet": dict({
157                 "help": "Connector from network interfaces to the internet", 
158                 "name": "inet",
159                 "max": 1, 
160                 "min": 1
161             }),
162     "node": dict({
163                 "help": "Connector to a Node", 
164                 "name": "node",
165                 "max": 1, 
166                 "min": 1
167             }),
168    })
169
170 connections = [
171     dict({
172         "from": (TESTBED_ID, NODE, "devs"),
173         "to":   (TESTBED_ID, NODEIFACE, "node"),
174         "code": connect_node_iface_node,
175         "can_cross": False
176     }),
177     dict({
178         "from": (TESTBED_ID, NODE, "devs"),
179         "to":   (TESTBED_ID, TUNIFACE, "node"),
180         "code": connect_tun_iface_node,
181         "can_cross": False
182     }),
183     dict({
184         "from": (TESTBED_ID, NODEIFACE, "inet"),
185         "to":   (TESTBED_ID, INTERNET, "devs"),
186         "code": connect_node_iface_inet,
187         "can_cross": False
188     }),
189     dict({
190         "from": (TESTBED_ID, NODE, "apps"),
191         "to":   (TESTBED_ID, APPLICATION, "node"),
192         "code": None,
193         "can_cross": False
194     })
195 ]
196
197 attributes = dict({
198     "forward_X11": dict({      
199                 "name": "forward_X11",
200                 "help": "Forward x11 from main namespace to the node",
201                 "type": Attribute.BOOL, 
202                 "value": False,
203                 "flags": Attribute.DesignOnly,
204                 "validation_function": validation.is_bool,
205             }),
206     "hostname": dict({      
207                 "name": "hosname",
208                 "help": "Constrain hostname during resource discovery. May use wildcards.",
209                 "type": Attribute.STRING, 
210                 "flags": Attribute.DesignOnly,
211                 "validation_function": validation.is_string,
212             }),
213     "architecture": dict({      
214                 "name": "architecture",
215                 "help": "Constrain architexture during resource discovery.",
216                 "type": Attribute.ENUM, 
217                 "flags": Attribute.DesignOnly,
218                 "allowed": ["x86_64",
219                             "i386"],
220                 "validation_function": validation.is_enum,
221             }),
222     "operating_system": dict({      
223                 "name": "operatingSystem",
224                 "help": "Constrain operating system during resource discovery.",
225                 "type": Attribute.ENUM, 
226                 "flags": Attribute.DesignOnly,
227                 "allowed": ["f8",
228                             "f12",
229                             "f14",
230                             "centos",
231                             "other"],
232                 "validation_function": validation.is_enum,
233             }),
234     "site": dict({      
235                 "name": "site",
236                 "help": "Constrain the PlanetLab site this node should reside on.",
237                 "type": Attribute.ENUM, 
238                 "flags": Attribute.DesignOnly,
239                 "allowed": ["PLE",
240                             "PLC",
241                             "PLJ"],
242                 "validation_function": validation.is_enum,
243             }),
244     "emulation": dict({      
245                 "name": "emulation",
246                 "help": "Enable emulation on this node. Enables NetfilterRoutes, bridges, and a host of other functionality.",
247                 "type": Attribute.BOOL,
248                 "value": False, 
249                 "flags": Attribute.DesignOnly,
250                 "validation_function": validation.is_bool,
251             }),
252     "min_reliability": dict({
253                 "name": "minReliability",
254                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
255                 "type": Attribute.DOUBLE,
256                 "range": (0,100),
257                 "flags": Attribute.DesignOnly,
258                 "validation_function": validation.is_double,
259             }),
260     "max_reliability": dict({
261                 "name": "maxReliability",
262                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
263                 "type": Attribute.DOUBLE,
264                 "range": (0,100),
265                 "flags": Attribute.DesignOnly,
266                 "validation_function": validation.is_double,
267             }),
268     "min_bandwidth": dict({
269                 "name": "minBandwidth",
270                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
271                 "type": Attribute.DOUBLE,
272                 "range": (0,2**31),
273                 "flags": Attribute.DesignOnly,
274                 "validation_function": validation.is_double,
275             }),
276     "max_bandwidth": dict({
277                 "name": "maxBandwidth",
278                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
279                 "type": Attribute.DOUBLE,
280                 "range": (0,2**31),
281                 "flags": Attribute.DesignOnly,
282                 "validation_function": validation.is_double,
283             }),
284             
285     "up": dict({
286                 "name": "up",
287                 "help": "Link up",
288                 "type": Attribute.BOOL,
289                 "value": False,
290                 "validation_function": validation.is_bool
291             }),
292     "primary": dict({
293                 "name": "primary",
294                 "help": "This is the primary interface for the attached node",
295                 "type": Attribute.BOOL,
296                 "value": True,
297                 "validation_function": validation.is_bool
298             }),
299     "device_name": dict({
300                 "name": "name",
301                 "help": "Device name",
302                 "type": Attribute.STRING,
303                 "flags": Attribute.DesignOnly,
304                 "validation_function": validation.is_string
305             }),
306     "mtu":  dict({
307                 "name": "mtu", 
308                 "help": "Maximum transmition unit for device",
309                 "type": Attribute.INTEGER,
310                 "range": (0,1500),
311                 "validation_function": validation.is_integer_range(0,1500)
312             }),
313     "mask":  dict({
314                 "name": "mask", 
315                 "help": "Network mask for the device (eg: 24 for /24 network)",
316                 "type": Attribute.INTEGER,
317                 "validation_function": validation.is_integer_range(8,24)
318             }),
319     "snat":  dict({
320                 "name": "snat", 
321                 "help": "Enable SNAT (source NAT to the internet) no this device",
322                 "type": Attribute.BOOL,
323                 "value": False,
324                 "validation_function": validation.is_bool
325             }),
326             
327     "command": dict({
328                 "name": "command",
329                 "help": "Command line string",
330                 "type": Attribute.STRING,
331                 "flags": Attribute.DesignOnly,
332                 "validation_function": validation.is_string
333             }),
334     "user": dict({
335                 "name": "user",
336                 "help": "System user",
337                 "type": Attribute.STRING,
338                 "flags": Attribute.DesignOnly,
339                 "validation_function": validation.is_string
340             }),
341     "stdin": dict({
342                 "name": "stdin",
343                 "help": "Standard input",
344                 "type": Attribute.STRING,
345                 "flags": Attribute.DesignOnly,
346                 "validation_function": validation.is_string
347             }),
348     })
349
350 traces = dict({
351     "stdout": dict({
352                 "name": "stdout",
353                 "help": "Standard output stream"
354               }),
355     "stderr": dict({
356                 "name": "stderr",
357                 "help": "Application standard error",
358         }) 
359     })
360
361 create_order = [ NODE, NODEIFACE, TUNIFACE, APPLICATION ]
362
363 configure_order = [ NODE, NODEIFACE, TUNIFACE, APPLICATION ]
364
365 factories_info = dict({
366     NODE: dict({
367             "allow_routes": False,
368             "help": "Virtualized Node (V-Server style)",
369             "category": "topology",
370             "create_function": create_node,
371             "configure_function": configure_node,
372             "box_attributes": [
373                 "forward_X11",
374                 "hostname",
375                 "architecture",
376                 "operating_system",
377                 "site",
378                 "emulation",
379                 "min_reliability",
380                 "max_reliability",
381                 "min_bandwidth",
382                 "max_bandwidth",
383             ],
384             "connector_types": ["devs", "apps"]
385        }),
386     NODEIFACE: dict({
387             "allow_addresses": True,
388             "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
389             "category": "devices",
390             "create_function": create_nodeiface,
391             "configure_function": configure_nodeiface,
392             "box_attributes": [ ],
393             "connector_types": ["node", "inet"]
394         }),
395     TUNIFACE: dict({
396             "allow_addresses": True,
397             "help": "Virtual TUN network interface",
398             "category": "devices",
399             "create_function": create_tuniface,
400             "configure_function": configure_tuniface,
401             "box_attributes": [
402                 "up", "device_name", "mtu", "snat",
403             ],
404             "connector_types": ["node"]
405         }),
406     APPLICATION: dict({
407             "help": "Generic executable command line application",
408             "category": "applications",
409             "create_function": create_application,
410             "start_function": start_application,
411             "status_function": status_application,
412             "box_attributes": ["command", "user"],
413             "connector_types": ["node"],
414             "traces": ["stdout", "stderr"]
415         }),
416     INTERNET: dict({
417             "help": "Internet routing",
418             "category": "topology",
419             "create_function": create_internet,
420             "connector_types": ["devs"],
421         }),
422 })
423
424 testbed_attributes = dict({
425         "slice": dict({
426             "name": "slice",
427             "help": "The name of the PlanetLab slice to use",
428             "type": Attribute.STRING,
429             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
430             "validation_function": validation.is_string
431         }),
432     })
433
434 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
435     @property
436     def connector_types(self):
437         return connector_types
438
439     @property
440     def connections(self):
441         return connections
442
443     @property
444     def attributes(self):
445         return attributes
446
447     @property
448     def traces(self):
449         return traces
450
451     @property
452     def create_order(self):
453         return create_order
454
455     @property
456     def configure_order(self):
457         return configure_order
458
459     @property
460     def factories_info(self):
461         return factories_info
462
463     @property
464     def testbed_attributes(self):
465         return testbed_attributes
466