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