fixed shebangs in non executable .py files
[nepi.git] / src / nepi / testbeds / netns / metadata.py
1 # -*- coding: utf-8 -*-
2
3 from constants import TESTBED_ID, TESTBED_VERSION
4 from nepi.core import metadata
5 from nepi.core.attributes import Attribute
6 from nepi.util import tags, validation
7 from nepi.util.constants import ApplicationStatus as AS, \
8         FactoryCategories as FC, DeploymentConfiguration as DC
9
10 from nepi.util.tunchannel_impl import \
11     preconfigure_tunchannel, postconfigure_tunchannel, \
12     prestart_tunchannel, create_tunchannel, \
13     crossconnect_tunchannel_peer_init, \
14     crossconnect_tunchannel_peer_compl
15
16 import functools
17
18 # Factories
19 NODE = "Node"
20 P2PIFACE = "P2PNodeInterface"
21 TAPIFACE = "TapNodeInterface"
22 TUNIFACE = "TunNodeInterface"
23 NODEIFACE = "NodeInterface"
24 SWITCH = "Switch"
25 APPLICATION = "Application"
26 TUNCHANNEL = "TunChannel"
27
28 NS3_TESTBED_ID = "ns3"
29 FDNETDEV = "ns3::FdNetDevice"
30
31 def _follow_trace(testbed_instance, guid, trace_id, filename):
32     filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
33     trace = open(filepath, "wb")
34     testbed_instance.follow_trace(guid, trace_id, trace, filename)
35     return trace
36
37 ### Connection functions ####
38
39 def connect_switch(testbed_instance, switch_guid, interface_guid):
40     switch = testbed_instance._elements[switch_guid]
41     interface = testbed_instance._elements[interface_guid]
42     switch.connect(interface)
43    
44 def connect_fd(testbed_instance, tap_guid, cross_data):
45     import passfd
46     import socket
47     tap = testbed_instance._elements[tap_guid]
48     address = cross_data["tun_addr"]
49     sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
50     sock.connect(address)
51     passfd.sendfd(sock, tap.fd, '0')
52     # TODO: after succesful transfer, the tap device should close the fd
53
54 def connect_tunchannel_tun(testbed_instance, chan_guid, tap_guid):
55     connect_tunchannel_tap(testbed_instance, chan_guid, tap_guid, ethernet_mode=False)
56
57 def connect_tunchannel_tap(testbed_instance, chan_guid, tap_guid, ethernet_mode=True):
58     tap = testbed_instance._elements[tap_guid]
59     chan = testbed_instance._elements[chan_guid]
60
61     # Create a file object for the tap's interface device 
62     # and send it to the channel. It should comply with all the
63     # requirements for the channel's tun_socket.
64     import os
65     chan.tun_socket = os.fdopen(tap.fd)
66     
67     # Set the channel to ethernet mode (it's a tap)
68     chan.ethernet_mode = ethernet_mode
69     
70     # Check to see if the device uses PI headers
71     # It's normally so
72     with_pi = True
73     try:
74         import fcntl
75         import struct
76         TUNGETIFF = 0x800454d2
77         IFF_NO_PI = 0x00001000
78         struct_ifreq = "x"*16+"H"+"x"*22
79         flags = struct.unpack(struct_ifreq,
80             fcntl.ioctl(tap.fd, TUNGETIFF, struct.pack(struct_ifreq,0)) )[0]
81         with_pi = (0 == (flags & IFF_NO_PI))
82     except:
83         # maybe the kernel doesn't support the IOCTL,
84         # in which case, we assume it uses PI headers (as is usual)
85         pass
86     chan.with_pi = with_pi
87
88 ### Trace functions ###
89
90 def nodepcap_trace(testbed_instance, guid, trace_id):
91     node = testbed_instance._elements[guid]
92     parameters = testbed_instance._get_parameters(guid)
93     filename = "%d-pcap.stdout" % guid
94     stdout = _follow_trace(testbed_instance, guid, "pcap_stdout", filename)
95     filename = "%d-pcap.stderr" % guid
96     stderr = _follow_trace(testbed_instance, guid, "pcap_stderr", filename)
97     filename = "%d-node.pcap" % guid
98     filepath = testbed_instance.trace_filepath(guid, trace_id, filename)
99     command = "tcpdump -i 'any' -w %s" % filepath
100     user = "root"
101     trace = node.Popen(command, shell = True, stdout = stdout, 
102             stderr = stderr, user = user)
103     testbed_instance.follow_trace(guid, trace_id, trace, filename)
104
105 trace_functions = dict({
106     "pcap": nodepcap_trace,
107     })
108
109 ### Creation functions ###
110
111 def create_node(testbed_instance, guid):
112     parameters = testbed_instance._get_parameters(guid)
113     forward_X11 = False
114     if "forward_X11" in parameters:
115         forward_X11 = parameters["forward_X11"]
116         del parameters["forward_X11"]
117     element = testbed_instance.netns.Node(forward_X11 = forward_X11)
118     testbed_instance.elements[guid] = element
119
120 def create_p2piface(testbed_instance, guid):
121     if guid in testbed_instance.elements:
122         # The interface pair was already instantiated
123         return
124     # search for the node asociated with the p2piface
125     node1_guid = testbed_instance.get_connected(guid, "node", "devs")
126     if len(node1_guid) == 0:
127         raise RuntimeError("Can't instantiate interface %d outside netns \
128                 node" % guid)
129     node1 = testbed_instance.elements[node1_guid[0]]
130     # search for the pair p2piface
131     p2p_guid = testbed_instance.get_connected(guid, "p2p","p2p")
132     if len(p2p_guid) == 0:
133         raise RuntimeError("Can't instantiate p2p interface %d. \
134                 Missing interface pair" % guid)
135     guid2 = p2p_guid[0]
136     node2_guid = testbed_instance.get_connected(guid2, "node", "devs")
137     if len(node2_guid) == 0:
138         raise RuntimeError("Can't instantiate interface %d outside netns \
139                 node" % guid2)
140     node2 = testbed_instance.elements[node2_guid[0]]
141     element1, element2 = testbed_instance.netns.P2PInterface.create_pair(
142         node1, node2)
143     testbed_instance.elements[guid] = element1
144     testbed_instance.elements[guid2] = element2
145
146 def create_tapiface(testbed_instance, guid):
147     node_guid = testbed_instance.get_connected(guid, "node", "devs")
148     if len(node_guid) == 0:
149         raise RuntimeError("Can't instantiate interface %d outside netns \
150                 node" % guid)
151     node = testbed_instance.elements[node_guid[0]]
152     element = node.add_tap()
153     testbed_instance.elements[guid] = element
154
155 def create_tuniface(testbed_instance, guid):
156     node_guid = testbed_instance.get_connected(guid, "node", "devs")
157     if len(node_guid) == 0:
158         raise RuntimeError("Can't instantiate interface %d outside netns \
159                 node" % guid)
160     node = testbed_instance.elements[node_guid[0]]
161     element = node.add_tun()
162     testbed_instance.elements[guid] = element
163
164 def create_nodeiface(testbed_instance, guid):
165     node_guid = testbed_instance.get_connected(guid, "node", "devs")
166     if len(node_guid) == 0:
167         raise RuntimeError("Can't instantiate interface %d outside netns \
168                 node" % guid)
169     node = testbed_instance.elements[node_guid[0]]
170     element = node.add_if()
171     testbed_instance.elements[guid] = element
172
173 def create_switch(testbed_instance, guid):
174     element = testbed_instance.netns.Switch()
175     testbed_instance.elements[guid] = element
176
177 def create_application(testbed_instance, guid):
178     testbed_instance.elements[guid] = None # Delayed construction 
179
180 ### Start/Stop functions ###
181
182 def start_application(testbed_instance, guid):
183     parameters = testbed_instance._get_parameters(guid)
184     traces = testbed_instance._get_traces(guid)
185     command = parameters["command"]
186     user = None
187     if "user" in parameters:
188         user = parameters["user"]
189     stdout = stderr = None
190     if "stdout" in traces:
191         filename = "%d-stdout.trace" % guid
192         stdout = _follow_trace(testbed_instance, guid, "stdout", filename)
193     if "stderr" in traces:
194         filename = "%d-stderr.trace" % guid
195         stderr = _follow_trace(testbed_instance, guid, "stderr", filename)
196     node_guid = testbed_instance.get_connected(guid, "node", "apps")
197     if len(node_guid) == 0:
198         raise RuntimeError("Can't instantiate interface %d outside netns \
199                 node" % guid)
200     node = testbed_instance.elements[node_guid[0]]
201     element  = node.Popen(command, shell = True, stdout = stdout, 
202             stderr = stderr, user = user)
203     testbed_instance.elements[guid] = element
204
205 def stop_application(testbed_instance, guid):
206     #app = testbed_instance.elements[guid]
207     #app.signal()
208     pass
209
210 ### Status functions ###
211
212 def status_application(testbed_instance, guid):
213     if guid not in testbed_instance.elements.keys():
214         return AS.STATUS_NOT_STARTED
215     app = testbed_instance.elements[guid]
216     if app.poll() == None:
217         return AS.STATUS_RUNNING
218     return AS.STATUS_FINISHED
219
220 ### Configure functions ###
221
222 def configure_traces(testbed_instance, guid):
223     traces = testbed_instance._get_traces(guid)
224     for trace_id in traces:
225         if trace_id not in trace_functions:
226             continue
227         trace_func = trace_functions[trace_id]
228         trace_func(testbed_instance, guid, trace_id)
229
230 def configure_device(testbed_instance, guid):
231     configure_traces(testbed_instance, guid)
232     element = testbed_instance._elements[guid]
233     if not guid in testbed_instance._add_address:
234         return
235     addresses = testbed_instance._add_address[guid]
236     for address in addresses:
237         (address, netprefix, broadcast) = address
238         # TODO: Decide if we should add a ipv4 or ipv6 address
239         element.add_v4_address(address, netprefix)
240
241 def configure_node(testbed_instance, guid):
242     configure_traces(testbed_instance, guid)
243     element = testbed_instance._elements[guid]
244     if not guid in testbed_instance._add_route:
245         return
246     routes = testbed_instance._add_route[guid]
247     for route in routes:
248         (destination, netprefix, nexthop, metric) = route
249         element.add_route(prefix = destination, prefix_len = netprefix,
250             nexthop = nexthop, metric = metric)
251
252 ### Factory information ###
253
254 connector_types = dict({
255     "apps": dict({
256                 "help": "Connector from node to applications", 
257                 "name": "apps",
258                 "max": -1, 
259                 "min": 0
260             }),
261     "devs": dict({
262                 "help": "Connector from node to network interfaces", 
263                 "name": "devs",
264                 "max": -1, 
265                 "min": 0
266             }),
267     "node": dict({
268                 "help": "Connector to a Node", 
269                 "name": "node",
270                 "max": 1, 
271                 "min": 1
272             }),
273     "p2p": dict({
274                 "help": "Connector to a P2PInterface", 
275                 "name": "p2p",
276                 "max": 1, 
277                 "min": 0
278             }),
279     "->fd": dict({
280                 "help": "File descriptor receptor for devices with file descriptors",
281                 "name": "->fd",
282                 "max": 1,
283                 "min": 0
284             }),
285     "fd->": dict({
286                 "help": "File descriptor provider for devices with file descriptors",
287                 "name": "fd->",
288                 "max": 1,
289                 "min": 0
290             }),
291     "switch": dict({
292                 "help": "Connector to a switch", 
293                 "name": "switch",
294                 "max": 1, 
295                 "min": 0
296             }),
297     "tcp": dict({
298                 "help": "ip-ip tunneling over TCP link", 
299                 "name": "tcp",
300                 "max": 1, 
301                 "min": 0
302             }),
303     "udp": dict({
304                 "help": "ip-ip tunneling over UDP datagrams", 
305                 "name": "udp",
306                 "max": 1, 
307                 "min": 0
308             }),
309    })
310
311 connections = [
312     dict({
313         "from": (TESTBED_ID, NODE, "devs"),
314         "to":   (TESTBED_ID, P2PIFACE, "node"),
315         "can_cross": False
316     }),
317     dict({
318         "from": (TESTBED_ID, NODE, "devs"),
319         "to":   (TESTBED_ID, TAPIFACE, "node"),
320         "can_cross": False
321     }),
322     dict({
323         "from": (TESTBED_ID, NODE, "devs"),
324         "to":   (TESTBED_ID, TUNIFACE, "node"),
325         "can_cross": False
326     }),
327     dict({
328         "from": (TESTBED_ID, NODE, "devs"),
329         "to":   (TESTBED_ID, NODEIFACE, "node"),
330         "can_cross": False
331     }),
332     dict({
333         "from": (TESTBED_ID, P2PIFACE, "p2p"),
334         "to":   (TESTBED_ID, P2PIFACE, "p2p"),
335         "can_cross": False
336     }),
337     dict({
338         "from": (TESTBED_ID, TAPIFACE, "fd->"),
339         "to":   (None, None, "->fd"),
340         "compl_code": connect_fd,
341         "can_cross": True
342     }),
343     dict({
344         "from": (TESTBED_ID, TUNIFACE, "fd->"),
345         "to":   (None, None, "->fd"),
346         "compl_code": connect_fd,
347         "can_cross": True
348     }),
349      dict({
350         "from": (TESTBED_ID, SWITCH, "devs"),
351         "to":   (TESTBED_ID, NODEIFACE, "switch"),
352         "init_code": connect_switch,
353         "can_cross": False
354     }),
355     dict({
356         "from": (TESTBED_ID, NODE, "apps"),
357         "to":   (TESTBED_ID, APPLICATION, "node"),
358         "can_cross": False
359     }),
360     dict({
361         "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
362         "to":   (TESTBED_ID, TAPIFACE, "fd->" ),
363         "init_code": connect_tunchannel_tap,
364         "can_cross": False
365     }),
366     dict({
367         "from": (TESTBED_ID, TUNCHANNEL, "->fd" ),
368         "to":   (TESTBED_ID, TUNIFACE, "fd->" ),
369         "init_code": connect_tunchannel_tun,
370         "can_cross": False
371     }),
372     dict({
373         "from": (TESTBED_ID, TUNCHANNEL, "tcp"),
374         "to":   (None, None, "tcp"),
375         "init_code": functools.partial(crossconnect_tunchannel_peer_init,"tcp"),
376         "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"tcp"),
377         "can_cross": True
378     }),
379     dict({
380         "from": (TESTBED_ID, TUNCHANNEL, "udp"),
381         "to":   (None, None, "udp"),
382         "init_code": functools.partial(crossconnect_tunchannel_peer_init,"udp"),
383         "compl_code": functools.partial(crossconnect_tunchannel_peer_compl,"udp"),
384         "can_cross": True
385     }),
386 ]
387
388 attributes = dict({
389     "forward_X11": dict({      
390                 "name": "forward_X11",
391                 "help": "Forward x11 from main namespace to the node",
392                 "type": Attribute.BOOL, 
393                 "value": False,
394                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
395                 "validation_function": validation.is_bool
396             }),
397     "lladdr": dict({      
398                 "name": "lladdr", 
399                 "help": "Mac address", 
400                 "type": Attribute.STRING,
401                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
402                 "validation_function": validation.is_mac_address
403             }),
404     "up": dict({
405                 "name": "up",
406                 "help": "Link up",
407                 "type": Attribute.BOOL,
408                 "value": False,
409                 "validation_function": validation.is_bool
410             }),
411     "device_name": dict({
412                 "name": "name",
413                 "help": "Device name",
414                 "type": Attribute.STRING,
415                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
416                 "validation_function": validation.is_string
417             }),
418     "mtu":  dict({
419                 "name": "mtu", 
420                 "help": "Maximum transmition unit for device",
421                 "type": Attribute.INTEGER,
422                 "validation_function": validation.is_integer
423             }),
424     "broadcast": dict({ 
425                 "name": "broadcast",
426                 "help": "Broadcast address",
427                 "type": Attribute.STRING,
428                 "validation_function": validation.is_string # TODO: should be is address!
429             }),
430     "multicast": dict({      
431                 "name": "multicast",
432                 "help": "Multicast enabled",
433                 "type": Attribute.BOOL,
434                 "value": False,
435                 "validation_function": validation.is_bool
436             }),
437     "arp": dict({
438                 "name": "arp",
439                 "help": "ARP enabled",
440                 "type": Attribute.BOOL,
441                 "value": False,
442                 "validation_function": validation.is_bool
443             }),
444     "command": dict({
445                 "name": "command",
446                 "help": "Command line string",
447                 "type": Attribute.STRING,
448                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
449                 "validation_function": validation.is_string
450             }),
451     "user": dict({
452                 "name": "user",
453                 "help": "System user",
454                 "type": Attribute.STRING,
455                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
456                 "validation_function": validation.is_string
457             }),
458     "stdin": dict({
459                 "name": "stdin",
460                 "help": "Standard input",
461                 "type": Attribute.STRING,
462                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
463                 "validation_function": validation.is_string
464             }),
465     })
466
467 traces = dict({
468     "stdout": dict({
469                 "name": "stdout",
470                 "help": "Standard output stream"
471               }),
472     "stderr": dict({
473                 "name": "stderr",
474                 "help": "Application standard error",
475         }),
476     "node_pcap": dict({
477                 "name": "pcap",
478                 "help": "tcpdump at all node interfaces",
479         }) 
480     })
481
482 create_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, 
483         TUNIFACE, TUNCHANNEL, SWITCH,
484         APPLICATION ]
485
486 configure_order = [ P2PIFACE, NODEIFACE, TAPIFACE, 
487         TUNIFACE, TUNCHANNEL, SWITCH, 
488         NODE, APPLICATION ]
489
490 factories_info = dict({
491     NODE: dict({
492             "help": "Emulated Node with virtualized network stack",
493             "category": FC.CATEGORY_NODES,
494             "create_function": create_node,
495             "configure_function": configure_node,
496             "box_attributes": ["forward_X11"],
497             "connector_types": ["devs", "apps"],
498             "traces": ["node_pcap"],
499             "tags": [tags.NODE, tags.ALLOW_ROUTES],
500        }),
501     P2PIFACE: dict({
502             "help": "Point to point network interface",
503             "category": FC.CATEGORY_DEVICES,
504             "create_function": create_p2piface,
505             "configure_function": configure_device,
506             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
507                 "multicast", "broadcast", "arp"],
508             "connector_types": ["node", "p2p"],
509             "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
510        }),
511     TAPIFACE: dict({
512             "help": "Tap device network interface",
513             "category": FC.CATEGORY_DEVICES,
514             "create_function": create_tapiface,
515             "configure_function": configure_device,
516             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
517                 "multicast", "broadcast", "arp"],
518             "connector_types": ["node", "fd->"],
519             "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
520         }),
521     TUNIFACE: dict({
522             "help": "Tun device network interface",
523             "category": FC.CATEGORY_DEVICES,
524             "create_function": create_tuniface,
525             "configure_function": configure_device,
526             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
527                 "multicast", "broadcast", "arp"],
528             "connector_types": ["node", "fd->"],
529             "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
530         }),
531     NODEIFACE: dict({
532             "help": "Node network interface",
533             "category": FC.CATEGORY_DEVICES,
534             "create_function": create_nodeiface,
535             "configure_function": configure_device,
536             "box_attributes": ["lladdr", "up", "device_name", "mtu", 
537                 "multicast", "broadcast", "arp"],
538             "connector_types": ["node", "switch"],
539             "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
540         }),
541     SWITCH: dict({
542             "display_name": "Switch",
543             "help": "Switch interface",
544             "category": FC.CATEGORY_DEVICES,
545             "create_function": create_switch,
546             "box_attributes": ["up", "device_name", "mtu", "multicast"],
547              #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function),
548              #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function),
549              #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function),
550              #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function),
551              #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function)
552             "connector_types": ["devs"],
553             "tags": [tags.SWITCH],
554         }),
555     APPLICATION: dict({
556             "help": "Generic executable command line application",
557             "category": FC.CATEGORY_APPLICATIONS,
558             "create_function": create_application,
559             "start_function": start_application,
560             "stop_function": stop_application,
561             "status_function": status_application,
562             "box_attributes": ["command", "user"],
563             "connector_types": ["node"],
564             "traces": ["stdout", "stderr"],
565             "tags": [tags.APPLICATION],
566         }),
567      TUNCHANNEL : dict({
568             "category": FC.CATEGORY_TUNNELS,
569             "create_function": create_tunchannel,
570             "preconfigure_function": preconfigure_tunchannel,
571             "configure_function": postconfigure_tunchannel,
572             "prestart_function": prestart_tunchannel,
573             "help": "Channel to forward "+TAPIFACE+" data to "
574                 "other TAP interfaces supporting the NEPI tunneling protocol.",
575             "connector_types": ["->fd", "udp", "tcp"],
576             "allow_addresses": False,
577             "box_attributes": ["tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher"],
578             "tags": [tags.TUNNEL],
579     }),
580 })
581
582 testbed_attributes = dict({
583         "enable_debug": dict({
584                 "name": "enableDebug",
585                 "help": "Enable netns debug output",
586                 "type": Attribute.BOOL,
587                 "value": False,
588                 "validation_function": validation.is_bool
589             }),
590     })
591
592 supported_recovery_policies = [
593         DC.POLICY_FAIL,
594     ]
595
596 class MetadataInfo(metadata.MetadataInfo):
597     @property
598     def connector_types(self):
599         return connector_types
600
601     @property
602     def connections(self):
603         return connections
604
605     @property
606     def attributes(self):
607         return attributes
608
609     @property
610     def traces(self):
611         return traces
612
613     @property
614     def create_order(self):
615         return create_order
616
617     @property
618     def configure_order(self):
619         return configure_order
620
621     @property
622     def factories_info(self):
623         return factories_info
624
625     @property
626     def testbed_attributes(self):
627         return testbed_attributes
628
629     @property
630     def testbed_id(self):
631         return TESTBED_ID
632
633     @property
634     def testbed_version(self):
635         return TESTBED_VERSION
636     
637     @property
638     def supported_recover_policies(self):
639         return supported_recovery_policies
640