Some application execution boilerplate
[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 = "stdout" in traces
79     app.stderr = "stderr" in traces
80     
81     app.start()
82
83 def stop_application(testbed_instance, guid):
84     app = testbed_instance.elements[guid]
85     app.stop()
86
87 ### Status functions ###
88
89 def status_application(testbed_instance, guid):
90     if guid not in testbed_instance.elements.keys():
91         return STATUS_NOT_STARTED
92     
93     app = testbed_instance.elements[guid]
94     return app.status()
95
96 ### Configure functions ###
97
98 def configure_nodeiface(testbed_instance, guid):
99     element = testbed_instance._elements[guid]
100     
101     # Cannot explicitly configure addresses
102     if guid in testbed_instance._add_address:
103         del testbed_instance._add_address[guid]
104     
105     # Get siblings
106     node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
107     dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
108     siblings = [ self._element[dev_guid] 
109                  for dev_guid in dev_guids
110                  if dev_guid != guid ]
111     
112     # Fetch address from PLC api
113     element.pick_iface(siblings)
114     
115     # Do some validations
116     element.validate()
117
118 def configure_tuniface(testbed_instance, guid):
119     element = testbed_instance._elements[guid]
120     if not guid in testbed_instance._add_address:
121         return
122     
123     addresses = testbed_instance._add_address[guid]
124     for address in addresses:
125         (address, netprefix, broadcast) = address
126         raise NotImplementedError, "C'mon... TUNs are hard..."
127     
128     # Do some validations
129     element.validate()
130
131 def configure_node(testbed_instance, guid):
132     element = testbed_instance._elements[guid]
133     
134     # If we have only one candidate, simply use it
135     candidates = element.find_candidates(
136         filter_slice_id = testbed_instance.slice_id)
137     if len(candidates) == 1:
138         element.assign_node_id(iter(candidates).next())
139     
140     # Do some validations
141     element.validate()
142
143 ### Factory information ###
144
145 connector_types = dict({
146     "apps": dict({
147                 "help": "Connector from node to applications", 
148                 "name": "apps",
149                 "max": -1, 
150                 "min": 0
151             }),
152     "devs": dict({
153                 "help": "Connector from node to network interfaces", 
154                 "name": "devs",
155                 "max": -1, 
156                 "min": 0
157             }),
158     "inet": dict({
159                 "help": "Connector from network interfaces to the internet", 
160                 "name": "inet",
161                 "max": 1, 
162                 "min": 1
163             }),
164     "node": dict({
165                 "help": "Connector to a Node", 
166                 "name": "node",
167                 "max": 1, 
168                 "min": 1
169             }),
170    })
171
172 connections = [
173     dict({
174         "from": (TESTBED_ID, NODE, "devs"),
175         "to":   (TESTBED_ID, NODEIFACE, "node"),
176         "code": connect_node_iface_node,
177         "can_cross": False
178     }),
179     dict({
180         "from": (TESTBED_ID, NODE, "devs"),
181         "to":   (TESTBED_ID, TUNIFACE, "node"),
182         "code": connect_tun_iface_node,
183         "can_cross": False
184     }),
185     dict({
186         "from": (TESTBED_ID, NODEIFACE, "inet"),
187         "to":   (TESTBED_ID, INTERNET, "devs"),
188         "code": connect_node_iface_inet,
189         "can_cross": False
190     }),
191     dict({
192         "from": (TESTBED_ID, NODE, "apps"),
193         "to":   (TESTBED_ID, APPLICATION, "node"),
194         "code": connect_app,
195         "can_cross": False
196     })
197 ]
198
199 attributes = dict({
200     "forward_X11": dict({      
201                 "name": "forward_X11",
202                 "help": "Forward x11 from main namespace to the node",
203                 "type": Attribute.BOOL, 
204                 "value": False,
205                 "flags": Attribute.DesignOnly,
206                 "validation_function": validation.is_bool,
207             }),
208     "hostname": dict({      
209                 "name": "hostname",
210                 "help": "Constrain hostname during resource discovery. May use wildcards.",
211                 "type": Attribute.STRING, 
212                 "flags": Attribute.DesignOnly,
213                 "validation_function": validation.is_string,
214             }),
215     "architecture": dict({      
216                 "name": "architecture",
217                 "help": "Constrain architexture during resource discovery.",
218                 "type": Attribute.ENUM, 
219                 "flags": Attribute.DesignOnly,
220                 "allowed": ["x86_64",
221                             "i386"],
222                 "validation_function": validation.is_enum,
223             }),
224     "operating_system": dict({      
225                 "name": "operatingSystem",
226                 "help": "Constrain operating system during resource discovery.",
227                 "type": Attribute.ENUM, 
228                 "flags": Attribute.DesignOnly,
229                 "allowed": ["f8",
230                             "f12",
231                             "f14",
232                             "centos",
233                             "other"],
234                 "validation_function": validation.is_enum,
235             }),
236     "site": dict({      
237                 "name": "site",
238                 "help": "Constrain the PlanetLab site this node should reside on.",
239                 "type": Attribute.ENUM, 
240                 "flags": Attribute.DesignOnly,
241                 "allowed": ["PLE",
242                             "PLC",
243                             "PLJ"],
244                 "validation_function": validation.is_enum,
245             }),
246     "emulation": dict({      
247                 "name": "emulation",
248                 "help": "Enable emulation on this node. Enables NetfilterRoutes, bridges, and a host of other functionality.",
249                 "type": Attribute.BOOL,
250                 "value": False, 
251                 "flags": Attribute.DesignOnly,
252                 "validation_function": validation.is_bool,
253             }),
254     "min_reliability": dict({
255                 "name": "minReliability",
256                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
257                 "type": Attribute.DOUBLE,
258                 "range": (0,100),
259                 "flags": Attribute.DesignOnly,
260                 "validation_function": validation.is_double,
261             }),
262     "max_reliability": dict({
263                 "name": "maxReliability",
264                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
265                 "type": Attribute.DOUBLE,
266                 "range": (0,100),
267                 "flags": Attribute.DesignOnly,
268                 "validation_function": validation.is_double,
269             }),
270     "min_bandwidth": dict({
271                 "name": "minBandwidth",
272                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
273                 "type": Attribute.DOUBLE,
274                 "range": (0,2**31),
275                 "flags": Attribute.DesignOnly,
276                 "validation_function": validation.is_double,
277             }),
278     "max_bandwidth": dict({
279                 "name": "maxBandwidth",
280                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
281                 "type": Attribute.DOUBLE,
282                 "range": (0,2**31),
283                 "flags": Attribute.DesignOnly,
284                 "validation_function": validation.is_double,
285             }),
286             
287     "up": dict({
288                 "name": "up",
289                 "help": "Link up",
290                 "type": Attribute.BOOL,
291                 "value": False,
292                 "validation_function": validation.is_bool
293             }),
294     "primary": dict({
295                 "name": "primary",
296                 "help": "This is the primary interface for the attached node",
297                 "type": Attribute.BOOL,
298                 "value": True,
299                 "validation_function": validation.is_bool
300             }),
301     "device_name": dict({
302                 "name": "name",
303                 "help": "Device name",
304                 "type": Attribute.STRING,
305                 "flags": Attribute.DesignOnly,
306                 "validation_function": validation.is_string
307             }),
308     "mtu":  dict({
309                 "name": "mtu", 
310                 "help": "Maximum transmition unit for device",
311                 "type": Attribute.INTEGER,
312                 "range": (0,1500),
313                 "validation_function": validation.is_integer_range(0,1500)
314             }),
315     "mask":  dict({
316                 "name": "mask", 
317                 "help": "Network mask for the device (eg: 24 for /24 network)",
318                 "type": Attribute.INTEGER,
319                 "validation_function": validation.is_integer_range(8,24)
320             }),
321     "snat":  dict({
322                 "name": "snat", 
323                 "help": "Enable SNAT (source NAT to the internet) no this device",
324                 "type": Attribute.BOOL,
325                 "value": False,
326                 "validation_function": validation.is_bool
327             }),
328             
329     "command": dict({
330                 "name": "command",
331                 "help": "Command line string",
332                 "type": Attribute.STRING,
333                 "flags": Attribute.DesignOnly,
334                 "validation_function": validation.is_string
335             }),
336     "sudo": dict({
337                 "name": "user",
338                 "help": "System user",
339                 "type": Attribute.BOOL,
340                 "flags": Attribute.DesignOnly,
341                 "value": False,
342                 "validation_function": validation.is_bool
343             }),
344     "stdin": dict({
345                 "name": "stdin",
346                 "help": "Standard input",
347                 "type": Attribute.STRING,
348                 "flags": Attribute.DesignOnly,
349                 "validation_function": validation.is_string
350             }),
351     })
352
353 traces = dict({
354     "stdout": dict({
355                 "name": "stdout",
356                 "help": "Standard output stream"
357               }),
358     "stderr": dict({
359                 "name": "stderr",
360                 "help": "Application standard error",
361         }) 
362     })
363
364 create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
365
366 configure_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
367
368 factories_info = dict({
369     NODE: dict({
370             "allow_routes": False,
371             "help": "Virtualized Node (V-Server style)",
372             "category": "topology",
373             "create_function": create_node,
374             "configure_function": configure_node,
375             "box_attributes": [
376                 "forward_X11",
377                 "hostname",
378                 "architecture",
379                 "operating_system",
380                 "site",
381                 "emulation",
382                 "min_reliability",
383                 "max_reliability",
384                 "min_bandwidth",
385                 "max_bandwidth",
386             ],
387             "connector_types": ["devs", "apps"]
388        }),
389     NODEIFACE: dict({
390             "allow_addresses": True,
391             "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
392             "category": "devices",
393             "create_function": create_nodeiface,
394             "configure_function": configure_nodeiface,
395             "box_attributes": [ ],
396             "connector_types": ["node", "inet"]
397         }),
398     TUNIFACE: dict({
399             "allow_addresses": True,
400             "help": "Virtual TUN network interface",
401             "category": "devices",
402             "create_function": create_tuniface,
403             "configure_function": configure_tuniface,
404             "box_attributes": [
405                 "up", "device_name", "mtu", "snat",
406             ],
407             "connector_types": ["node"]
408         }),
409     APPLICATION: dict({
410             "help": "Generic executable command line application",
411             "category": "applications",
412             "create_function": create_application,
413             "start_function": start_application,
414             "status_function": status_application,
415             "stop_function": stop_application,
416             "box_attributes": ["command", "sudo"],
417             "connector_types": ["node"],
418             "traces": ["stdout", "stderr"]
419         }),
420     INTERNET: dict({
421             "help": "Internet routing",
422             "category": "topology",
423             "create_function": create_internet,
424             "connector_types": ["devs"],
425         }),
426 })
427
428 testbed_attributes = dict({
429         "slice": dict({
430             "name": "slice",
431             "help": "The name of the PlanetLab slice to use",
432             "type": Attribute.STRING,
433             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
434             "validation_function": validation.is_string
435         }),
436         "auth_user": dict({
437             "name": "authUser",
438             "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
439             "type": Attribute.STRING,
440             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
441             "validation_function": validation.is_string
442         }),
443         "auth_pass": dict({
444             "name": "authPass",
445             "help": "The PlanetLab user's password.",
446             "type": Attribute.STRING,
447             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
448             "validation_function": validation.is_string
449         }),
450     })
451
452 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
453     @property
454     def connector_types(self):
455         return connector_types
456
457     @property
458     def connections(self):
459         return connections
460
461     @property
462     def attributes(self):
463         return attributes
464
465     @property
466     def traces(self):
467         return traces
468
469     @property
470     def create_order(self):
471         return create_order
472
473     @property
474     def configure_order(self):
475         return configure_order
476
477     @property
478     def factories_info(self):
479         return factories_info
480
481     @property
482     def testbed_attributes(self):
483         return testbed_attributes
484