NOT WORKING COMMIT! started adding netns testbed instance support
[nepi.git] / src / nepi / testbeds / netns / testbed.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from nepi.core import testbed
5
6 CREATE = 0
7 SET = 1
8 CONNECT = 2
9 ADD_TRACE = 3
10 ADD_ADDRESS = 4
11 ADD_ROUTE = 5
12
13 class TestbedConfiguration(testbed.TestbedConfiguration):
14     pass
15
16 class TestbedInstance(testbed.TestbedInstance):
17     def __init__(self, configuration):
18         self._netns = self._load_netns_module(configuration)
19         self._elements = dict()
20         self._configuration = configuration
21         self._instructions = dict({
22             CREATE: dict(),
23             SET: dict(),
24             CONNECT: dict(),
25             ADD_TRACE: dict(),
26             ADD_ADDRESS: dict(),
27             ADD_ROUTE: dict()
28         })
29         self._factories = dict({
30             "Node": list(),
31             "P2PInterface": list(),
32             "TapNodeInterface": list(),
33             "NodeInterface": list(),
34             "Switch": list(),
35             "Application": list()
36         })
37         self._connections = list()
38
39     def create(self, guid, factory_id):
40         if guid in self._instructions[CREATE]:
41             # XXX: Validation
42             raise RuntimeError("cannot add two elements with the same guid")
43         if factory_id not in self._factories:
44             # XXX: Validation
45             raise RuntimeError("%s is not an allowed factory_id" % factory_id)
46         self._instructions[CREATE][guid] = factory_id
47         self._factories[factory_id].append(guid)
48
49     def create_set(self, guid, name, value):
50         if guid not in self._instructions[SET]:
51             self._instructions[SET][guid] = dict()
52         self._instructions[SET][guid][name] = value
53        
54     def connect(self, guid1, connector_type_name1, guid2, 
55             connector_type_name2):
56         if not guid1 in self._instructions[CONNECT]:
57             self._instructions[CONNECT] = dict()
58         if not connector_type_name1 in self._instructions[CONNECT][guid1]:
59              self._instructions[CONNECT][guid1][connector_type_name1] = dict()
60         self._instructions[CONNECT][guid1][connector_type_name1][guid2] = \
61                 connector_type_name2
62         self._connections.append((guid1, connector_type_name1, guid2, 
63             connector_type_name2))
64
65     def add_trace(self, guid, trace_id):
66         if not guid in self._instructions[ADD_TRACE]:
67             self._instructions[ADD_TRACE][guid] = list()
68         self._instructions[ADD_TRACE][guid].append(trace_id)
69
70     def add_adddress(self, guid, family, address, netprefix, broadcast):
71         if not guid in self._instructions[ADD_ADDRESS]:
72             self._instructions[ADD_ADDRESS][guid] = list()
73         self._instructions[ADD_ADDRESS][guid].append((guid, family, address, 
74                 netprefix, broadcast))
75
76     def add_route(self, guid, family, destination, netprefix, nexthop, 
77             interface):
78         if not guid in self._instructions[ADD_ROUTE]:
79             self._instructions[ADD_ROUTE][guid] = list()
80         self._instructions[ADD_ROUTE][guid].append((family, destination, 
81                 netprefix, nexthop, interface)) 
82
83     def do_create(self):
84         # nodes need to be created first
85         factories_order = ["Node", "P2PInterface", "TapNodeInterface", 
86                 "NodeInterface", "Switch", "Application"]
87         for guid in self._factories[factory_id] \
88                 for factory_id in factories_order:
89             self._create_element(guid, factory_id)
90         # free self._factories as it is not going to be used further
91         # TODO: Check if this methods frees everithing... 
92         # maybe there are still some references!
93         self._factories = None
94
95     def do_connect(self):
96         cross_connections = list()
97         for (guid1, connector_type_name1, guid2, connector_type_name2) \
98                 in self._connections:
99             if guid1 not in self._elements or guid2 not in self._elements:
100                 # at least one of the elements does not belong to this
101                 # TestbedIntsance and so it needs to be treated as a cross 
102                 # testbed connection
103                 cross_connections.append(guid1, connector_type_name1, guid2,
104                         connector_type_name2)
105             else:
106                 # TODO: do Whatever is needed to connect
107                 pass
108         self._connections = cross_connections
109
110     def do_configure(self):
111         raise NotImplementedError
112         #self._object.add_route(
113         #            prefix = destination, 
114         #            prefix_len = netprefix, 
115         #            nexthop = nexthop)
116
117     def do_cross_connect(self):
118         for (guid1, connector_type_name1, guid2, connector_type_name2) \
119                 in self._connections:
120             # TODO: do Whatever is needed to connect
121             pass
122         # free connections list as is not going to be used further
123         self._connections = None
124
125     def set(self, time, guid, name, value):
126         raise NotImplementedError
127
128     def get(self, time, guid, name):
129         raise NotImplementedError
130
131     def start(self, time):
132         raise NotImplementedError
133
134     def action(self, time, guid, action):
135         raise NotImplementedError
136
137     def stop(self, time):
138         raise NotImplementedError
139
140     def status(self, guid):
141         raise NotImplementedError
142
143     def trace(self, guid, trace_id):
144         raise NotImplementedError
145
146     def shutdown(self):
147         raise NotImplementedError
148
149     def _netns_module(self, configuration):
150         # TODO: Do something with the configuration!!!
151         import sys
152         __import__("netns")
153         return sys.modules["netns"]
154
155     def _create_element(self, guid, factory_id):
156         paremeters = dict()
157         if guid in self._instructions[SET]:
158             parameters = self._instructions[SET][guid] 
159         if guid not in self._elements.keys():
160             if factory_id == "Node":
161                 self._create_node_element(guid, parameters)
162             elif factory_id == "P2PInterface":
163                 self._create_p2piface_element(guid, parameters)
164         self._set_attributes(guid, parameters)
165
166     def _create_node_element(self, guid, parameters):
167         forward_X11 = False
168         if "forward_X11" in paremeters:
169             forward_X11 = parameters["forward_X11"]
170             del parameters["forward_X11"]
171         element = self._netns.Node(forward_X11 = forward_X11)
172         self._elements[guid] = element
173
174     def _create_p2piface_element(self, guid, parameters):
175                 # search in the connections the node asociated with this
176                 # P2PInterface and the other P2PInterface to which it is 
177                 # connected
178                 connection = 
179                 node1 = 
180                 node2 = 
181                 element1, element2 = self._netns.P2PInterface.create_pair(
182                         node1, node2)
183                 self._elements[guid] = element1
184                 self._elements[guid2] = element2
185
186     def _set_attributes(self, guid, paramenters):
187         for name, value in parameters:
188             # TODO: Validate attribute does not exist!!!
189             setattr(element, name, value)
190
191 def connect_switch(switch_connector, interface_connector):
192     switch = switch_connector.container_element
193     interface = interface_connector.container_element
194     if switch.is_installed() and interface.is_installed():
195         switch._object.connect(interface._object)
196         return True
197     return False
198    
199 #XXX: This connection function cannot be use to transfer a file descriptor
200 # to a remote tap device
201 def connect_fd_local(tap_connector, fdnd_connector):
202     tap = tap_connector.container_element
203     fdnd = fdnd_connector.container_element
204     if fdnd.is_installed() and tap.is_installed():
205         socket = tap.server.modules.socket
206         passfd = tap.server.modules.passfd
207         fd = tap.file_descriptor
208         address = fdnd.socket_address
209         sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
210         sock.connect(address)
211         passfd.sendfd(sock, fd, '0')
212         # TODO: after succesful transfer, the tap device should close the fd
213         return True
214     return False
215
216 connections = [
217     dict({
218         'from' : [ "netns", "Node", "devs" ],
219         'to' :   [ "netns", "P2PInterface", "node" ],
220         'code' : do_nothing,
221         'can_cross' : False
222     }),
223     dict({
224         'from' : [ "netns", "Node", "devs" ],
225         'to' :   [ "netns", "TapNodeInterface", "node" ],
226         'code' : do_nothing,
227         'can_cross' : False
228     }),
229     dict({
230         'from' : [ "netns", "Node", "devs" ],
231         'to' :   [ "netns", "NodeInterface", "node" ],
232         'code' : do_nothing,
233         'can_cross' : False
234     }),
235     dict({
236         'from' : [ "netns", "P2PInterface", "p2p" ],
237         'to' :   [ "netns", "P2PInterface", "p2p" ],
238         'code' : do_nothing,
239         'can_cross' : False
240     }),
241     dict({
242         'from' : [ "netns", "TapNodeInterface", "fd" ],
243         'to' :   [ "ns3", "ns3::FileDescriptorNetDevice", "fd" ],
244         'code' : connect_fd_local,
245         'can_cross' : True
246     }),
247      dict({
248         'from' : [ "netns", "Switch", "devs" ],
249         'to' :   [ "netns", "NodeInterface", "switch" ],
250         'code' : connect_switch,
251         'can_cross' : False
252     }),
253     dict({
254         'from' : [ "netns", "Node", "apps" ],
255         'to' :   [ "netns", "Application", "node" ],
256         'code' : do_nothing,
257         'can_cross' : False
258     })
259 ]
260
261 class TapNodeInterface(object):
262     def __init__(self):
263         # GET THE NODE!
264         self._object = node._object.add_tap()
265
266 class NodeInterface(object):
267     def __init__(self):
268         # GET NODE
269         self._object = n.add_if()
270
271 class Switch(Topo_ChannelMixin, NetnsElement):
272     def __init__(self, factory, server, container = None):
273         self._object = self.server.modules.netns.Switch()
274
275 class NetnsApplication(NetnsElement):
276     def __init__(self, factory, server, container = None):
277         super(NetnsApplication, self).__init__(factory, server, container)
278         # attributes
279         self.add_string_attribute(name = "command",
280             help = "Command name")
281         self.add_string_attribute(name = "user",
282             help = "system user")
283         self.add_string_attribute(name = "stdin",
284             help = "Standard input")
285         #traces
286         stdout = StdTrace(
287                 name='StdoutTrace',
288                 help='Application standard output',
289                 filename='stdout.txt',
290                 element=self)
291         stderr = StdTrace(
292                 name='StderrTrace',
293                 help='Application standard error',
294                 filename='stderr.txt',
295                 element=self)
296         self._add_trace(stdout)
297         self._add_trace(stderr)
298  
299     def start(self):
300         user = self.get_attribute("user").value
301
302         stdin = stdout = stderr = None
303         if self.get_attribute('stdin').value:
304             filename = self.get_attribute('stdin')
305             stdin = self.server.modules.__builtin__.open(filename, 'rb')
306
307         trace = self.get_trace("StdoutTrace")
308         if trace.is_enabled:
309             filename = trace.real_filepath
310             stdout = self.server.modules.__builtin__.open(filename, 'wb')
311
312         trace = self.get_trace("StderrTrace")
313         if trace.is_enabled:
314             filename = trace.real_filepath
315             stderr = self.server.modules.__builtin__.open(filename, 'wb')
316
317         cnctr = self.connector("node")
318         node = iter(cnctr.connections).next().get_other(cnctr)\
319             .container_element
320         force_install(node)    
321         n = node._object
322         command = self.get_attribute('command').value
323         targets = re.findall(r"%target:(.*?)%", command)
324         for target in targets:
325             try:
326                 (family, address, port) = resolve_netref(target, AF_INET, 
327                     self.server.experiment )
328                 command = command.replace("%%target:%s%%" % target, address.address)
329             except:
330                 continue
331
332         self._object  = n.Popen(command, shell = True,  stdout = stdout, stdin = stdin,
333             stderr = stderr, user = user)
334
335     def is_completed(self):
336         if self._object is not None:
337             returncode = self._object.poll()
338             if returncode is not None:
339                 return True
340         return False
341
342 class StdTrace(Trace):
343     def install(self):
344         pass
345
346     @property
347     def real_filepath(self):
348         filename = self.get_attribute("Filename").value
349         return self.element.server.get_trace_filepath(filename)
350
351     def read_trace(self):
352         filename = self.get_attribute("Filename").value
353         return self.element.server.read_trace(filename)
354
355 def force_install(object):
356     if not object.is_installed():
357         object.install()
358