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