Several execution fixes:
[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, iface, inet):
25     iface.has_internet = True
26
27 def connect_tun_iface_node(testbed_instance, node, iface):
28     iface.node = node
29
30 def connect_app(testbed_instance, node, app):
31     app.node = node
32
33 ### Creation functions ###
34
35 def create_node(testbed_instance, guid):
36     parameters = testbed_instance._get_parameters(guid)
37     
38     # create element with basic attributes
39     element = testbed_instance._make_node(parameters)
40     
41     # add constraint on number of (real) interfaces
42     # by counting connected devices
43     dev_guids = testbed_instance.get_connected(guid, "node", "devs")
44     num_open_ifaces = sum( # count True values
45         TUNEIFACE == testbed_instance._get_factory_id(guid)
46         for guid in dev_guids )
47     element.min_num_external_ifaces = num_open_ifaces
48     
49     testbed_instance.elements[guid] = element
50
51 def create_nodeiface(testbed_instance, guid):
52     parameters = testbed_instance._get_parameters(guid)
53     element = testbed_instance._make_node_iface(parameters)
54     testbed_instance.elements[guid] = element
55
56 def create_tuniface(testbed_instance, guid):
57     parameters = testbed_instance._get_parameters(guid)
58     element = testbed_instance._make_tun_iface(parameters)
59     testbed_instance.elements[guid] = element
60
61 def create_application(testbed_instance, guid):
62     parameters = testbed_instance._get_parameters(guid)
63     element = testbed_instance._make_application(parameters)
64     testbed_instance.elements[guid] = element
65
66 def create_internet(testbed_instance, guid):
67     parameters = testbed_instance._get_parameters(guid)
68     element = testbed_instance._make_internet(parameters)
69     testbed_instance.elements[guid] = element
70
71 ### Start/Stop functions ###
72
73 def start_application(testbed_instance, guid):
74     parameters = testbed_instance._get_parameters(guid)
75     traces = testbed_instance._get_traces(guid)
76     app = testbed_instance.elements[guid]
77     
78     app.stdout = testbed_instance.trace_filename(guid, "stdout")
79     app.stderr = testbed_instance.trace_filename(guid, "stderr")
80     
81     # TODO
82     pass
83
84 ### Status functions ###
85
86 def status_application(testbed_instance, guid):
87     if guid not in testbed_instance.elements.keys():
88         return STATUS_NOT_STARTED
89     app = testbed_instance.elements[guid]
90     # TODO
91     return STATUS_FINISHED
92
93 ### Configure functions ###
94
95 def configure_nodeiface(testbed_instance, guid):
96     element = testbed_instance._elements[guid]
97     
98     # Cannot explicitly configure addresses
99     if guid in testbed_instance._add_address:
100         del testbed_instance._add_address[guid]
101     
102     # Get siblings
103     node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
104     dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
105     siblings = [ self._element[dev_guid] 
106                  for dev_guid in dev_guids
107                  if dev_guid != guid ]
108     
109     # Fetch address from PLC api
110     element.pick_iface(siblings)
111     
112     # Do some validations
113     element.validate()
114
115 def configure_tuniface(testbed_instance, guid):
116     element = testbed_instance._elements[guid]
117     if not guid in testbed_instance._add_address:
118         return
119     
120     addresses = testbed_instance._add_address[guid]
121     for address in addresses:
122         (address, netprefix, broadcast) = address
123         # TODO
124     
125     # Do some validations
126     element.validate()
127
128 def configure_node(testbed_instance, guid):
129     element = testbed_instance._elements[guid]
130     
131     # If we have only one candidate, simply use it
132     candidates = element.find_candidates(
133         filter_slice_id = testbed_instance.slice_id)
134     if len(candidates) == 1:
135         element.assign_node_id(iter(candidates).next())
136     
137     # Do some validations
138     element.validate()
139
140 ### Factory information ###
141
142 connector_types = dict({
143     "apps": dict({
144                 "help": "Connector from node to applications", 
145                 "name": "apps",
146                 "max": -1, 
147                 "min": 0
148             }),
149     "devs": dict({
150                 "help": "Connector from node to network interfaces", 
151                 "name": "devs",
152                 "max": -1, 
153                 "min": 0
154             }),
155     "inet": dict({
156                 "help": "Connector from network interfaces to the internet", 
157                 "name": "inet",
158                 "max": 1, 
159                 "min": 1
160             }),
161     "node": dict({
162                 "help": "Connector to a Node", 
163                 "name": "node",
164                 "max": 1, 
165                 "min": 1
166             }),
167    })
168
169 connections = [
170     dict({
171         "from": (TESTBED_ID, NODE, "devs"),
172         "to":   (TESTBED_ID, NODEIFACE, "node"),
173         "code": connect_node_iface_node,
174         "can_cross": False
175     }),
176     dict({
177         "from": (TESTBED_ID, NODE, "devs"),
178         "to":   (TESTBED_ID, TUNIFACE, "node"),
179         "code": connect_tun_iface_node,
180         "can_cross": False
181     }),
182     dict({
183         "from": (TESTBED_ID, NODEIFACE, "inet"),
184         "to":   (TESTBED_ID, INTERNET, "devs"),
185         "code": connect_node_iface_inet,
186         "can_cross": False
187     }),
188     dict({
189         "from": (TESTBED_ID, NODE, "apps"),
190         "to":   (TESTBED_ID, APPLICATION, "node"),
191         "code": connect_app,
192         "can_cross": False
193     })
194 ]
195
196 attributes = dict({
197     "forward_X11": dict({      
198                 "name": "forward_X11",
199                 "help": "Forward x11 from main namespace to the node",
200                 "type": Attribute.BOOL, 
201                 "value": False,
202                 "flags": Attribute.DesignOnly,
203                 "validation_function": validation.is_bool,
204             }),
205     "hostname": dict({      
206                 "name": "hostname",
207                 "help": "Constrain hostname during resource discovery. May use wildcards.",
208                 "type": Attribute.STRING, 
209                 "flags": Attribute.DesignOnly,
210                 "validation_function": validation.is_string,
211             }),
212     "architecture": dict({      
213                 "name": "architecture",
214                 "help": "Constrain architexture during resource discovery.",
215                 "type": Attribute.ENUM, 
216                 "flags": Attribute.DesignOnly,
217                 "allowed": ["x86_64",
218                             "i386"],
219                 "validation_function": validation.is_enum,
220             }),
221     "operating_system": dict({      
222                 "name": "operatingSystem",
223                 "help": "Constrain operating system during resource discovery.",
224                 "type": Attribute.ENUM, 
225                 "flags": Attribute.DesignOnly,
226                 "allowed": ["f8",
227                             "f12",
228                             "f14",
229                             "centos",
230                             "other"],
231                 "validation_function": validation.is_enum,
232             }),
233     "site": dict({      
234                 "name": "site",
235                 "help": "Constrain the PlanetLab site this node should reside on.",
236                 "type": Attribute.ENUM, 
237                 "flags": Attribute.DesignOnly,
238                 "allowed": ["PLE",
239                             "PLC",
240                             "PLJ"],
241                 "validation_function": validation.is_enum,
242             }),
243     "emulation": dict({      
244                 "name": "emulation",
245                 "help": "Enable emulation on this node. Enables NetfilterRoutes, bridges, and a host of other functionality.",
246                 "type": Attribute.BOOL,
247                 "value": False, 
248                 "flags": Attribute.DesignOnly,
249                 "validation_function": validation.is_bool,
250             }),
251     "min_reliability": dict({
252                 "name": "minReliability",
253                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
254                 "type": Attribute.DOUBLE,
255                 "range": (0,100),
256                 "flags": Attribute.DesignOnly,
257                 "validation_function": validation.is_double,
258             }),
259     "max_reliability": dict({
260                 "name": "maxReliability",
261                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
262                 "type": Attribute.DOUBLE,
263                 "range": (0,100),
264                 "flags": Attribute.DesignOnly,
265                 "validation_function": validation.is_double,
266             }),
267     "min_bandwidth": dict({
268                 "name": "minBandwidth",
269                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
270                 "type": Attribute.DOUBLE,
271                 "range": (0,2**31),
272                 "flags": Attribute.DesignOnly,
273                 "validation_function": validation.is_double,
274             }),
275     "max_bandwidth": dict({
276                 "name": "maxBandwidth",
277                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
278                 "type": Attribute.DOUBLE,
279                 "range": (0,2**31),
280                 "flags": Attribute.DesignOnly,
281                 "validation_function": validation.is_double,
282             }),
283             
284     "up": dict({
285                 "name": "up",
286                 "help": "Link up",
287                 "type": Attribute.BOOL,
288                 "value": False,
289                 "validation_function": validation.is_bool
290             }),
291     "primary": dict({
292                 "name": "primary",
293                 "help": "This is the primary interface for the attached node",
294                 "type": Attribute.BOOL,
295                 "value": True,
296                 "validation_function": validation.is_bool
297             }),
298     "device_name": dict({
299                 "name": "name",
300                 "help": "Device name",
301                 "type": Attribute.STRING,
302                 "flags": Attribute.DesignOnly,
303                 "validation_function": validation.is_string
304             }),
305     "mtu":  dict({
306                 "name": "mtu", 
307                 "help": "Maximum transmition unit for device",
308                 "type": Attribute.INTEGER,
309                 "range": (0,1500),
310                 "validation_function": validation.is_integer_range(0,1500)
311             }),
312     "mask":  dict({
313                 "name": "mask", 
314                 "help": "Network mask for the device (eg: 24 for /24 network)",
315                 "type": Attribute.INTEGER,
316                 "validation_function": validation.is_integer_range(8,24)
317             }),
318     "snat":  dict({
319                 "name": "snat", 
320                 "help": "Enable SNAT (source NAT to the internet) no this device",
321                 "type": Attribute.BOOL,
322                 "value": False,
323                 "validation_function": validation.is_bool
324             }),
325             
326     "command": dict({
327                 "name": "command",
328                 "help": "Command line string",
329                 "type": Attribute.STRING,
330                 "flags": Attribute.DesignOnly,
331                 "validation_function": validation.is_string
332             }),
333     "sudo": dict({
334                 "name": "user",
335                 "help": "System user",
336                 "type": Attribute.BOOL,
337                 "flags": Attribute.DesignOnly,
338                 "value": False,
339                 "validation_function": validation.is_bool
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 = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
362
363 configure_order = [ INTERNET, 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", "sudo"],
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         "auth_user": dict({
433             "name": "authUser",
434             "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
435             "type": Attribute.STRING,
436             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
437             "validation_function": validation.is_string
438         }),
439         "auth_pass": dict({
440             "name": "authPass",
441             "help": "The PlanetLab user's password.",
442             "type": Attribute.STRING,
443             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
444             "validation_function": validation.is_string
445         }),
446     })
447
448 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
449     @property
450     def connector_types(self):
451         return connector_types
452
453     @property
454     def connections(self):
455         return connections
456
457     @property
458     def attributes(self):
459         return attributes
460
461     @property
462     def traces(self):
463         return traces
464
465     @property
466     def create_order(self):
467         return create_order
468
469     @property
470     def configure_order(self):
471         return configure_order
472
473     @property
474     def factories_info(self):
475         return factories_info
476
477     @property
478     def testbed_attributes(self):
479         return testbed_attributes
480