Ticket #8: support for both box and testbed standard attributes.
[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 APPLICATION = "Application"
14
15 PL_TESTBED_ID = "planetlab"
16
17 ### Connection functions ####
18
19 ### Creation functions ###
20
21 def create_node(testbed_instance, guid):
22     parameters = testbed_instance._get_parameters(guid)
23     element = testbed_instance.pl.Node()
24     testbed_instance.elements[guid] = element
25
26 def create_nodeiface(testbed_instance, guid):
27     parameters = testbed_instance._get_parameters(guid)
28     element = testbed_instance.pl.Iface()
29     testbed_instance.elements[guid] = element
30
31 def create_application(testbed_instance, guid):
32     testbed_instance.elements[guid] = None # Delayed construction 
33
34 ### Start/Stop functions ###
35
36 def start_application(testbed_instance, guid):
37     parameters = testbed_instance._get_parameters(guid)
38     traces = testbed_instance._get_traces(guid)
39     user = parameters["user"]
40     command = parameters["command"]
41     stdout = stderr = None
42     if "stdout" in traces:
43         filename = testbed_instance.trace_filename(guid, "stdout")
44         stdout = open(filename, "wb")
45         testbed_instance.follow_trace("stdout", stdout)
46     if "stderr" in traces:
47         filename = testbed_instance.trace_filename(guid, "stderr")
48         stderr = open(filename, "wb")
49         testbed_instance.follow_trace("stderr", stderr)
50
51     node_guid = testbed_instance.get_connected(guid, "node", "apps")
52     if len(node_guid) == 0:
53         raise RuntimeError("Can't instantiate interface %d outside netns \
54                 node" % guid)
55     node = testbed_instance.elements[node_guid[0]]
56     element  = node.Popen(command, shell = True, stdout = stdout, 
57             stderr = stderr, user = user)
58     testbed_instance.elements[guid] = element
59
60 ### Status functions ###
61
62 def status_application(testbed_instance, guid):
63     if guid not in testbed_instance.elements.keys():
64         return STATUS_NOT_STARTED
65     app = testbed_instance.elements[guid]
66     if app.poll() == None:
67         return STATUS_RUNNING
68     return STATUS_FINISHED
69
70 ### Configure functions ###
71
72 def configure_device(testbed_instance, guid):
73     element = testbed_instance._elements[guid]
74     if not guid in testbed_instance._add_address:
75         return
76     addresses = testbed_instance._add_address[guid]
77     for address in addresses:
78         (address, netprefix, broadcast) = address
79         # TODO: Decide if we should add a ipv4 or ipv6 address
80         element.add_v4_address(address, netprefix)
81
82 def configure_node(testbed_instance, guid):
83     element = testbed_instance._elements[guid]
84     if not guid in testbed_instance._add_route:
85         return
86     routes = testbed_instance._add_route[guid]
87     for route in routes:
88         (destination, netprefix, nexthop) = route
89         element.add_route(prefix = destination, prefix_len = netprefix,
90             nexthop = nexthop)
91
92 ### Factory information ###
93
94 connector_types = dict({
95     "apps": dict({
96                 "help": "Connector from node to applications", 
97                 "name": "apps",
98                 "max": -1, 
99                 "min": 0
100             }),
101     "devs": dict({
102                 "help": "Connector from node to network interfaces", 
103                 "name": "devs",
104                 "max": -1, 
105                 "min": 0
106             }),
107     "node": dict({
108                 "help": "Connector to a Node", 
109                 "name": "node",
110                 "max": 1, 
111                 "min": 1
112             }),
113     "p2p": dict({
114                 "help": "Connector to a P2PInterface", 
115                 "name": "p2p",
116                 "max": 1, 
117                 "min": 0
118             }),
119     "fd": dict({
120                 "help": "Connector to a network interface that can receive a file descriptor", 
121                 "name": "fd",
122                 "max": 1, 
123                 "min": 0
124             }),
125     "switch": dict({
126                 "help": "Connector to a switch", 
127                 "name": "switch",
128                 "max": 1, 
129                 "min": 0
130             })
131    })
132
133 connections = [
134     dict({
135         "from": (TESTBED_ID, NODE, "devs"),
136         "to":   (TESTBED_ID, P2PIFACE, "node"),
137         "code": None,
138         "can_cross": False
139     }),
140     dict({
141         "from": (TESTBED_ID, NODE, "devs"),
142         "to":   (TESTBED_ID, TAPIFACE, "node"),
143         "code": None,
144         "can_cross": False
145     }),
146     dict({
147         "from": (TESTBED_ID, NODE, "devs"),
148         "to":   (TESTBED_ID, NODEIFACE, "node"),
149         "code": None,
150         "can_cross": False
151     }),
152     dict({
153         "from": (TESTBED_ID, P2PIFACE, "p2p"),
154         "to":   (TESTBED_ID, P2PIFACE, "p2p"),
155         "code": None,
156         "can_cross": False
157     }),
158     dict({
159         "from": (TESTBED_ID, TAPIFACE, "fd"),
160         "to":   (NS3_TESTBED_ID, FDNETDEV, "fd"),
161         "code": connect_fd_local,
162         "can_cross": True
163     }),
164      dict({
165         "from": (TESTBED_ID, SWITCH, "devs"),
166         "to":   (TESTBED_ID, NODEIFACE, "switch"),
167         "code": connect_switch,
168         "can_cross": False
169     }),
170     dict({
171         "from": (TESTBED_ID, NODE, "apps"),
172         "to":   (TESTBED_ID, APPLICATION, "node"),
173         "code": None,
174         "can_cross": False
175     })
176 ]
177
178 attributes = dict({
179     "forward_X11": dict({      
180                 "name": "forward_X11",
181                 "help": "Forward x11 from main namespace to the node",
182                 "type": Attribute.BOOL, 
183                 "value": False,
184                 "flags": Attribute.DesignOnly,
185                 "validation_function": validation.is_bool
186             }),
187     "lladdr": dict({      
188                 "name": "lladdr", 
189                 "help": "Mac address", 
190                 "type": Attribute.STRING,
191                 "flags": Attribute.DesignOnly,
192                 "validation_function": validation.is_mac_address
193             }),
194     "up": dict({
195                 "name": "up",
196                 "help": "Link up",
197                 "type": Attribute.BOOL,
198                 "value": False,
199                 "validation_function": validation.is_bool
200             }),
201     "device_name": dict({
202                 "name": "name",
203                 "help": "Device name",
204                 "type": Attribute.STRING,
205                 "flags": Attribute.DesignOnly,
206                 "validation_function": validation.is_string
207             }),
208     "mtu":  dict({
209                 "name": "mtu", 
210                 "help": "Maximum transmition unit for device",
211                 "type": Attribute.INTEGER,
212                 "validation_function": validation.is_integer
213             }),
214     "broadcast": dict({ 
215                 "name": "broadcast",
216                 "help": "Broadcast address",
217                 "type": Attribute.STRING,
218                 "validation_function": validation.is_string # TODO: should be is address!
219             }),
220     "multicast": dict({      
221                 "name": "multicast",
222                 "help": "Multicast enabled",
223                 "type": Attribute.BOOL,
224                 "value": False,
225                 "validation_function": validation.is_bool
226             }),
227     "arp": dict({
228                 "name": "arp",
229                 "help": "ARP enabled",
230                 "type": Attribute.BOOL,
231                 "value": False,
232                 "validation_function": validation.is_bool
233             }),
234     "command": dict({
235                 "name": "command",
236                 "help": "Command line string",
237                 "type": Attribute.STRING,
238                 "flags": Attribute.DesignOnly,
239                 "validation_function": validation.is_string
240             }),
241     "user": dict({
242                 "name": "user",
243                 "help": "System user",
244                 "type": Attribute.STRING,
245                 "flags": Attribute.DesignOnly,
246                 "validation_function": validation.is_string
247             }),
248     "stdin": dict({
249                 "name": "stdin",
250                 "help": "Standard input",
251                 "type": Attribute.STRING,
252                 "flags": Attribute.DesignOnly,
253                 "validation_function": validation.is_string
254             }),
255     })
256
257 traces = dict({
258     "stdout": dict({
259                 "name": "stdout",
260                 "help": "Standard output stream"
261               }),
262     "stderr": dict({
263                 "name": "stderr",
264                 "help": "Application standard error",
265         }) 
266     })
267
268 create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, SWITCH,
269         APPLICATION ]
270
271 configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE, SWITCH, NODE,
272         APPLICATION ]
273
274 factories_info = dict({
275     NODE: dict({
276             "allow_routes": True,
277             "help": "Emulated Node with virtualized network stack",
278             "category": "topology",
279             "create_function": create_node,
280             "configure_function": configure_node,
281             "box_attributes": ["forward_X11"],
282             "connector_types": ["devs", "apps"]
283        }),
284     P2PIFACE: dict({
285             "allow_addresses": True,
286             "help": "Point to point network interface",
287             "category": "devices",
288             "create_function": create_p2piface,
289             "configure_function": configure_device,
290             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
291                 "multicast", "broadcast", "arp"],
292             "connector_types": ["node", "p2p"]
293        }),
294     TAPIFACE: dict({
295             "allow_addresses": True,
296             "help": "Tap device network interface",
297             "category": "devices",
298             "create_function": create_tapiface,
299             "configure_function": configure_device,
300             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
301                 "multicast", "broadcast", "arp"],
302             "connector_types": ["node", "fd"]
303         }),
304     NODEIFACE: dict({
305             "allow_addresses": True,
306             "help": "Node network interface",
307             "category": "devices",
308             "create_function": create_nodeiface,
309             "configure_function": configure_device,
310             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
311                 "multicast", "broadcast", "arp"],
312             "connector_types": ["node", "switch"]
313         }),
314     SWITCH: dict({
315             "display_name": "Switch",
316             "help": "Switch interface",
317             "category": "devices",
318             "create_function": create_switch,
319             "box_attributes": ["up", "device_name", "mtu", "multicast"],
320              #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function),
321              #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function),
322              #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function),
323              #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function),
324              #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function)
325            "connector_types": ["devs"]
326         }),
327     APPLICATION: dict({
328             "help": "Generic executable command line application",
329             "category": "applications",
330             "create_function": create_application,
331             "start_function": start_application,
332             "status_function": status_application,
333             "box_attributes": ["command", "user"],
334             "connector_types": ["node"],
335             "traces": ["stdout", "stderr"]
336         }),
337 })
338
339 testbed_attributes = dict({
340         "enable_debug": dict({
341                 "name": "enableDebug",
342                 "help": "Enable netns debug output",
343                 "type": Attribute.BOOL,
344                 "value": False,
345                 "validation_function": validation.is_bool
346             }),
347     })
348
349 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
350     @property
351     def connector_types(self):
352         return connector_types
353
354     @property
355     def connections(self):
356         return connections
357
358     @property
359     def attributes(self):
360         return attributes
361
362     @property
363     def traces(self):
364         return traces
365
366     @property
367     def create_order(self):
368         return create_order
369
370     @property
371     def configure_order(self):
372         return configure_order
373
374     @property
375     def factories_info(self):
376         return factories_info
377
378     @property
379     def testbed_attributes(self):
380         return testbed_attributes
381