d164540fde17a2830fc021b9f17a30d7607e1e1c
[nepi.git] / src / nepi / testbeds / netns / 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 AF_INET, STATUS_NOT_STARTED, STATUS_RUNNING, \
9         STATUS_FINISHED
10
11 NODE = "Node"
12 P2PIFACE = "P2PNodeInterface"
13 TAPIFACE = "TapNodeInterface"
14 NODEIFACE = "NodeInterface"
15 SWITCH = "Switch"
16 APPLICATION = "Application"
17
18 NS3_TESTBED_ID = "ns3"
19 FDNETDEV = "ns3::FileDescriptorNetDevice"
20
21 ### Connection functions ####
22
23 def connect_switch(switch, interface):
24     switch.connect(interface)
25    
26 #XXX: This connection function cannot be use to transfer a file descriptor
27 # to a remote tap device
28 def connect_fd_local(tap, fdnd):
29     import passfd
30     import socket
31     fd = tap.file_descriptor
32     address = fdnd.socket_address
33     sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
34     sock.connect(address)
35     passfd.sendfd(sock, fd, '0')
36     # TODO: after succesful transfer, the tap device should close the fd
37
38 ### Creation functions ###
39
40 def create_node(testbed_instance, guid, parameters):
41     forward_X11 = False
42     if "forward_X11" in parameters:
43         forward_X11 = parameters["forward_X11"]
44         del parameters["forward_X11"]
45     element = testbed_instance.netns.Node(forward_X11 = forward_X11)
46     testbed_instance.elements[guid] = element
47
48 def create_p2piface(testbed_instance, guid, parameters):
49     if guid in testbed_instance.elements:
50         # The interface pair was already instantiated
51         return
52     # search for the node asociated with the p2piface
53     node1_guid = testbed_instance.get_connected(guid, "node", "devs")
54     if len(node1_guid) == 0:
55         raise RuntimeError("Can't instantiate interface %d outside netns \
56                 node" % guid)
57     node1 = testbed_instance.elements[node1_guid[0]]
58     # search for the pair p2piface
59     p2p_guid = testbed_instance.get_connected(guid, "p2p","p2p")
60     if len(p2p_guid) == 0:
61         raise RuntimeError("Can't instantiate p2p interface %d. \
62                 Missing interface pair" % guid)
63     guid2 = p2p_guid[0]
64     node2_guid = testbed_instance.get_connected(guid2, "node", "devs")
65     if len(node2_guid) == 0:
66         raise RuntimeError("Can't instantiate interface %d outside netns \
67                 node" % guid2)
68     node2 = testbed_instance.elements[node2_guid[0]]
69     element1, element2 = testbed_instance.netns.P2PInterface.create_pair(
70         node1, node2)
71     testbed_instance.elements[guid] = element1
72     testbed_instance.elements[guid2] = element2
73
74 def create_tapiface(testbed_instance, guid, parameters):
75     node_guid = testbed_instance.get_connected(guid, "node", "devs")
76     if len(node_guid) == 0:
77         raise RuntimeError("Can't instantiate interface %d outside netns \
78                 node" % guid)
79     node = testbed_instance.elements[node_guid[0]]
80     element = node.add_tap()
81     testbed_instance.elements[guid] = element
82
83 def create_nodeiface(testbed_instance, guid, parameters):
84     node_guid = testbed_instance.get_connected(guid, "node", "devs")
85     if len(node_guid) == 0:
86         raise RuntimeError("Can't instantiate interface %d outside netns \
87                 node" % guid)
88     node = testbed_instance.elements[node_guid[0]]
89     element = node.add_if()
90     testbed_instance.elements[guid] = element
91
92 def create_switch(testbed_instance, guid, parameters):
93     element = testbed_instance.netns.Switch()
94     testbed_instance.elements[guid] = element
95
96 def create_application(testbed_instance, guid, parameters):
97     testbed_instance.elements[guid] = None # Delayed construction 
98
99 ### Start/Stop functions ###
100
101 def start_application(testbed_instance, guid, parameters, traces):
102     user = parameters["user"]
103     command = parameters["command"]
104     stdout = stderr = None
105     if "stdout" in traces:
106         filename = testbed_instance.trace_filename(guid, "stdout")
107         stdout = open(filename, "wb")
108         testbed_instance.follow_trace("stdout", stdout)
109     if "stderr" in traces:
110         filename = testbed_instance.trace_filename(guid, "stderr")
111         stderr = open(filename, "wb")
112         testbed_instance.follow_trace("stderr", stderr)
113
114     node_guid = testbed_instance.get_connected(guid, "node", "apps")
115     if len(node_guid) == 0:
116         raise RuntimeError("Can't instantiate interface %d outside netns \
117                 node" % guid)
118     node = testbed_instance.elements[node_guid[0]]
119     element  = node.Popen(command, shell = True, stdout = stdout, 
120             stderr = stderr, user = user)
121     testbed_instance.elements[guid] = element
122
123 ### Status functions ###
124
125 def status_application(testbed_instance, guid):
126     if guid not in testbed_instance.elements.keys():
127         return STATUS_NOT_STARTED
128     app = testbed_instance.elements[guid]
129     if app.poll() == None:
130         return STATUS_RUNNING
131     return STATUS_FINISHED
132
133 ### Factory information ###
134
135 connector_types = dict({
136     "apps": dict({
137                 "help": "Connector from node to applications", 
138                 "name": "apps",
139                 "max": -1, 
140                 "min": 0
141             }),
142     "devs": dict({
143                 "help": "Connector from node to network interfaces", 
144                 "name": "devs",
145                 "max": -1, 
146                 "min": 0
147             }),
148     "node": dict({
149                 "help": "Connector to a Node", 
150                 "name": "node",
151                 "max": 1, 
152                 "min": 1
153             }),
154     "p2p": dict({
155                 "help": "Connector to a P2PInterface", 
156                 "name": "p2p",
157                 "max": 1, 
158                 "min": 0
159             }),
160     "fd": dict({
161                 "help": "Connector to a network interface that can receive a file descriptor", 
162                 "name": "fd",
163                 "max": 1, 
164                 "min": 0
165             }),
166     "switch": dict({
167                 "help": "Connector to a switch", 
168                 "name": "switch",
169                 "max": 1, 
170                 "min": 0
171             })
172    })
173
174 connections = [
175     dict({
176         "from": (TESTBED_ID, NODE, "devs"),
177         "to":   (TESTBED_ID, P2PIFACE, "node"),
178         "code": None,
179         "can_cross": False
180     }),
181     dict({
182         "from": (TESTBED_ID, NODE, "devs"),
183         "to":   (TESTBED_ID, TAPIFACE, "node"),
184         "code": None,
185         "can_cross": False
186     }),
187     dict({
188         "from": (TESTBED_ID, NODE, "devs"),
189         "to":   (TESTBED_ID, NODEIFACE, "node"),
190         "code": None,
191         "can_cross": False
192     }),
193     dict({
194         "from": (TESTBED_ID, P2PIFACE, "p2p"),
195         "to":   (TESTBED_ID, P2PIFACE, "p2p"),
196         "code": None,
197         "can_cross": False
198     }),
199     dict({
200         "from": (TESTBED_ID, TAPIFACE, "fd"),
201         "to":   (NS3_TESTBED_ID, FDNETDEV, "fd"),
202         "code": connect_fd_local,
203         "can_cross": True
204     }),
205      dict({
206         "from": (TESTBED_ID, SWITCH, "devs"),
207         "to":   (TESTBED_ID, NODEIFACE, "switch"),
208         "code": connect_switch,
209         "can_cross": False
210     }),
211     dict({
212         "from": (TESTBED_ID, NODE, "apps"),
213         "to":   (TESTBED_ID, APPLICATION, "node"),
214         "code": None,
215         "can_cross": False
216     })
217 ]
218
219 attributes = dict({
220     "forward_X11": dict({      
221                 "name": "forward_X11",
222                 "help": "Forward x11 from main namespace to the node",
223                 "type": Attribute.BOOL, 
224                 "value": False,
225                 "range": None,
226                 "allowed": None,
227                 "flags": Attribute.DesignOnly,
228                 "validation_function": validation.is_bool
229             }),
230     "lladdr": dict({      
231                 "name": "lladdr", 
232                 "help": "Mac address", 
233                 "type": Attribute.STRING,
234                 "value": None,
235                 "range": None,
236                 "allowed": None,
237                 "flags": Attribute.DesignOnly,
238                 "validation_function": validation.is_mac_address
239             }),
240     "up": dict({
241                 "name": "up",
242                 "help": "Link up",
243                 "type": Attribute.BOOL,
244                 "value": False,
245                 "range": None,
246                 "allowed": None,
247                 "validation_function": validation.is_bool
248             }),
249     "device_name": dict({
250                 "name": "name",
251                 "help": "Device name",
252                 "type": Attribute.STRING,
253                 "value": None,
254                 "range": None,
255                 "allowed": None,
256                 "flags": Attribute.DesignOnly,
257                 "validation_function": validation.is_string
258             }),
259     "mtu":  dict({
260                 "name": "mtu", 
261                 "help": "Maximum transmition unit for device",
262                 "type": Attribute.INTEGER,
263                 "value": None, 
264                 "range": None, 
265                 "allowed": None, 
266                 "validation_function": validation.is_integer
267             }),
268     "broadcast": dict({ 
269                 "name": "broadcast",
270                 "help": "Broadcast address",
271                 "type": Attribute.STRING,
272                 "value": None,
273                 "range": None,
274                 "allowed": None,
275                 "validation_function": validation.is_string # TODO: should be is address!
276             }),
277     "multicast": dict({      
278                 "name": "multicast",
279                 "help": "Multicast enabled",
280                 "type": Attribute.BOOL,
281                 "value": False,
282                 "range": None,
283                 "allowed": None,
284                 "validation_function": validation.is_bool
285             }),
286     "arp": dict({
287                 "name": "arp",
288                 "help": "ARP enabled",
289                 "type": Attribute.BOOL,
290                 "value": False,
291                 "range": None,
292                 "allowed": None,
293                 "validation_function": validation.is_bool
294             }),
295     "command": dict({
296                 "name": "command",
297                 "help": "Command line string",
298                 "type": Attribute.STRING,
299                 "value": None,
300                 "range": None,
301                 "allowed": None,
302                 "flags": Attribute.DesignOnly,
303                 "validation_function": validation.is_string
304             }),
305     "user": dict({
306                 "name": "user",
307                 "help": "System user",
308                 "type": Attribute.STRING,
309                 "value": None,
310                 "range": None,
311                 "allowed": None,
312                 "flags": Attribute.DesignOnly,
313                 "validation_function": validation.is_string
314             }),
315     "stdin": dict({
316                 "name": "stdin",
317                 "help": "Standard input",
318                 "type": Attribute.STRING,
319                 "value": None,
320                 "range": None,
321                 "allowed": None,
322                 "flags": Attribute.DesignOnly,
323                 "validation_function": validation.is_string
324             }),
325      "max_addresses": dict({
326                 "name": "MaxAddresses",
327                 "help": "Maximum number of addresses allowed by the device",
328                 "type": Attribute.INTEGER,
329                 "value": None,
330                 "range": None,
331                 "allowed": None,
332                 "flags": Attribute.Invisible,
333                 "validation_function": validation.is_integer
334             }),
335      "family": dict({
336                 "name": "Family",
337                 "help": "IP address family",
338                 "type": Attribute.INTEGER,
339                 "value": AF_INET,
340                 "range": None,
341                 "allowed": None,
342                 "flags": Attribute.Invisible,
343                 "validation_function": validation.is_integer
344             }),
345     })
346
347 traces = dict({
348     "stdout": dict({
349                 "name": "stdout",
350                 "help": "Standard output stream"
351               }),
352     "stderr": dict({
353                 "name": "stderr",
354                 "help": "Application standard error",
355         }) 
356     })
357
358 factories_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, SWITCH,
359         APPLICATION ]
360
361 factories_info = dict({
362     NODE: dict({
363             "allow_routes": True,
364             "help": "Emulated Node with virtualized network stack",
365             "category": "topology",
366             "create_function": create_node,
367             "start_function": None,
368             "stop_function": None,
369             "status_function": None,
370             "box_attributes": ["forward_X11"],
371             "connector_types": ["devs", "apps"]
372        }),
373     P2PIFACE: dict({
374             "allow_addresses": True,
375             "help": "Point to point network interface",
376             "category": "devices",
377             "create_function": create_p2piface,
378             "start_function": None,
379             "stop_function": None,
380             "status_function": None,
381             "factory_attributes": ["family", "max_addresses"],
382             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
383                 "multicast", "broadcast", "arp"],
384             "connector_types": ["node", "p2p"]
385        }),
386     TAPIFACE: dict({
387             "allow_addresses": True,
388             "help": "Tap device network interface",
389             "category": "devices",
390             "create_function": create_tapiface,
391             "start_function": None,
392             "stop_function": None,
393             "status_function": None,
394             "factory_attributes": ["family", "max_addresses"],
395             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
396                 "multicast", "broadcast", "arp"],
397             "connector_types": ["node", "fd"]
398         }),
399     NODEIFACE: dict({
400             "allow_addresses": True,
401             "help": "Node network interface",
402             "category": "devices",
403             "create_function": create_nodeiface,
404             "start_function": None,
405             "stop_function": None,
406             "status_function": None,
407             "factory_attributes": ["family", "max_addresses"],
408             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
409                 "multicast", "broadcast", "arp"],
410             "connector_types": ["node", "switch"]
411         }),
412     SWITCH: dict({
413             "display_name": "Switch",
414             "help": "Switch interface",
415             "category": "devices",
416             "create_function": create_switch,
417             "start_function": None,
418             "stop_function": None,
419             "status_function": None,
420             "box_attributes": ["up", "device_name", "mtu", "multicast"],
421              #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function),
422              #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function),
423              #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function),
424              #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function),
425              #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function)
426            "connector_types": ["devs"]
427         }),
428     APPLICATION: dict({
429             "help": "Generic executable command line application",
430             "category": "applications",
431             "create_function": create_application,
432             "start_function": start_application,
433             "stop_function": None,
434             "status_function": status_application,
435             "box_attributes": ["command", "user"],
436             "connector_types": ["node"],
437             "traces": ["stdout", "stderr"]
438         }),
439 })
440
441 testbed_attributes = dict({
442         "enable_debug": dict({
443                 "name": "enableDebug",
444                 "help": "Enable netns debug output",
445                 "type": Attribute.BOOL,
446                 "value": False,
447                 "range": None,
448                 "allowed": None,
449                 "validation_function": validation.is_bool
450             }),
451          "home_directory": dict({
452                 "name": "homeDirectory",
453                 "help": "Path to the directory where traces and other files \
454                         will be stored",
455                 "type": Attribute.STRING,
456                 "value": False,
457                 "range": None,
458                 "allowed": None,
459                 "flags": Attribute.DesignOnly,
460                 "validation_function": validation.is_string
461             })
462     })
463
464 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
465     @property
466     def connections_types(self):
467         return connection_types
468
469     @property
470     def connections(self):
471         return connections
472
473     @property
474     def attributes(self):
475         return attributes
476
477     @property
478     def traces(self):
479         return traces
480
481     @property
482     def factories_order(self):
483         return factories_order
484
485     @property
486     def factories_info(self):
487         return factories_info
488
489     @property
490     def testbed_attributes(self):
491         return testbed_attributes
492