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