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