adding ns3 testbed: ongoing development! tests DON'T run OK!
[nepi.git] / src / nepi / core / testbed_impl.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from nepi.core import execute
5 from nepi.core.metadata import Metadata
6 from nepi.util import validation
7 from nepi.util.constants import AF_INET, AF_INET6, STATUS_UNDETERMINED, TIME_NOW
8
9 class TestbedInstance(execute.TestbedInstance):
10     def __init__(self, testbed_id, testbed_version):
11         super(TestbedInstance, self).__init__(testbed_id, testbed_version)
12         self._started = False
13         # testbed attributes for validation
14         self._attributes = None
15         # element factories for validation
16         self._factories = dict()
17
18         # experiment construction instructions
19         self._create = dict()
20         self._create_set = dict()
21         self._factory_set = dict()
22         self._connect = dict()
23         self._cross_connect = dict()
24         self._add_trace = dict()
25         self._add_address = dict()
26         self._add_route = dict()
27         self._configure = dict()
28
29         # log of set operations
30         self._set = dict()
31
32         # testbed element instances
33         self._elements = dict()
34
35         self._metadata = Metadata(self._testbed_id, self._testbed_version)
36         for factory in self._metadata.build_execute_factories():
37             self._factories[factory.factory_id] = factory
38         self._attributes = self._metadata.testbed_attributes()
39
40     @property
41     def guids(self):
42         return self._create.keys()
43
44     @property
45     def elements(self):
46         return self._elements
47
48     def configure(self, name, value):
49         if not self._attributes.has_attribute(name):
50             raise RuntimeError("Invalid attribute %s for testbed" % name)
51         # Validation
52         self._attributes.set_attribute_value(name, value)
53         self._configure[name] = value
54
55     def create(self, guid, factory_id):
56         if factory_id not in self._factories:
57             raise RuntimeError("Invalid element type %s for testbed version %s" %
58                     (factory_id, self._testbed_version))
59         if guid in self._create:
60             raise RuntimeError("Cannot add elements with the same guid: %d" %
61                     guid)
62         self._create[guid] = factory_id
63
64     def create_set(self, guid, name, value):
65         if not guid in self._create:
66             raise RuntimeError("Element guid %d doesn't exist" % guid)
67         factory_id = self._create[guid]
68         factory = self._factories[factory_id]
69         if not factory.box_attributes.has_attribute(name):
70             raise RuntimeError("Invalid attribute %s for element type %s" %
71                     (name, factory_id))
72         factory.box_attributes.set_attribute_value(name, value)
73         if guid not in self._create_set:
74             self._create_set[guid] = dict()
75         self._create_set[guid][name] = value
76
77     def factory_set(self, guid, name, value):
78         if not guid in self._create:
79             raise RuntimeError("Element guid %d doesn't exist" % guid)
80         factory_id = self._create[guid]
81         factory = self._factories[factory_id]
82         if not factory.has_attribute(name):
83             raise RuntimeError("Invalid attribute %s for element type %s" %
84                     (name, factory_id))
85         factory.set_attribute_value(name, value)
86         if guid not in self._factory_set:
87             self._factory_set[guid] = dict()
88         self._factory_set[guid][name] = value
89
90     def connect(self, guid1, connector_type_name1, guid2, 
91             connector_type_name2):
92         factory_id1 = self._create[guid1]
93         factory_id2 = self._create[guid2]
94         count = self._get_connection_count(guid1, connector_type_name1)
95         factory1 = self._factories[factory_id1]
96         connector_type = factory1.connector_type(connector_type_name1)
97         connector_type.can_connect(self._testbed_id, factory_id2, 
98                 connector_type_name2, count)
99         if not guid1 in self._connect:
100             self._connect[guid1] = dict()
101         if not connector_type_name1 in self._connect[guid1]:
102              self._connect[guid1][connector_type_name1] = dict()
103         self._connect[guid1][connector_type_name1][guid2] = \
104                connector_type_name2
105         if not guid2 in self._connect:
106             self._connect[guid2] = dict()
107         if not connector_type_name2 in self._connect[guid2]:
108              self._connect[guid2][connector_type_name2] = dict()
109         self._connect[guid2][connector_type_name2][guid1] = \
110                 connector_type_name1
111
112     def cross_connect(self, guid, connector_type_name, cross_guid, 
113             cross_testbed_id, cross_factory_id, cross_connector_type_name):
114         factory_id = self._create[guid]
115         count = self._get_connection_count(guid, connector_type_name)
116         factory = self._factories[factory_id]
117         connector_type = factory.connector_type(connector_type_name)
118         connector_type.can_connect(cross_testbed_id, cross_factory_id, 
119                 cross_connector_type_name, count, must_cross = True)
120         if not guid in self._connect:
121             self._cross_connect[guid] = dict()
122         if not connector_type_name in self._cross_connect[guid]:
123              self._cross_connect[guid][connector_type_name] = dict()
124         self._cross_connect[guid][connector_type_name] = \
125                 (cross_guid, cross_testbed_id, cross_factory_id, 
126                         cross_connector_type_name)
127
128     def add_trace(self, guid, trace_id):
129         if not guid in self._create:
130             raise RuntimeError("Element guid %d doesn't exist" % guid)
131         factory_id = self._create[guid]
132         factory = self._factories[factory_id]
133         if not trace_id in factory.traces:
134             raise RuntimeError("Element type '%s' has no trace '%s'" %
135                     (factory_id, trace_id))
136         if not guid in self._add_trace:
137             self._add_trace[guid] = list()
138         self._add_trace[guid].append(trace_id)
139
140     def add_address(self, guid, family, address, netprefix, broadcast):
141         if not guid in self._create:
142             raise RuntimeError("Element guid %d doesn't exist" % guid)
143         factory_id = self._create[guid]
144         factory = self._factories[factory_id]
145         if not factory.allow_addresses:
146             raise RuntimeError("Element type '%s' doesn't support addresses" %
147                     factory_id)
148         max_addresses = factory.get_attribute_value("MaxAddresses")
149         if guid in self._add_address:
150             count_addresses = len(self._add_address[guid])
151             if max_addresses == count_addresses:
152                 raise RuntimeError("Element guid %d of type '%s' can't accept \
153                         more addresses" % (guid, family_id))
154         else:
155             self._add_address[guid] = list()
156         self._add_address[guid].append((family, address, netprefix, broadcast))
157
158     def add_route(self, guid, destination, netprefix, nexthop):
159         if not guid in self._create:
160             raise RuntimeError("Element guid %d doesn't exist" % guid)
161         factory_id = self._create[guid]
162         factory = self._factories[factory_id]
163         if not factory.allow_routes:
164             raise RuntimeError("Element type '%s' doesn't support routes" %
165                     factory_id)
166         if not guid in self._add_route:
167             self._add_route[guid] = list()
168         self._add_route[guid].append((destination, netprefix, nexthop)) 
169
170     def do_setup(self):
171         raise NotImplementedError
172
173     def do_create(self):
174         guids = dict()
175         # order guids (elements) according to factory_id
176         for guid, factory_id in self._create.iteritems():
177             if not factory_id in guids:
178                guids[factory_id] = list()
179             guids[factory_id].append(guid)
180         # create elements following the factory_id order
181         for factory_id in self._metadata.factories_order:
182             # omit the factories that have no element to create
183             if factory_id not in guids:
184                 continue
185             factory = self._factories[factory_id]
186             for guid in guids[factory_id]:
187                 parameters = self._get_parameters(guid)
188                 factory_parameters = dict() if guid not in \
189                         self._factory_set else self._factory_set[guid]
190                 factory.create_function(self, guid, parameters, 
191                         factory_parameters)
192                 for name, value in parameters.iteritems():
193                     self.set(TIME_NOW, guid, name, value)
194
195     def do_connect(self):
196         for guid1, connections in self._connect.iteritems():
197             element1 = self._elements[guid1]
198             factory_id1 = self._create[guid1]
199             factory1 = self._factories[factory_id1]
200             for connector_type_name1, connections2 in connections.iteritems():
201                 connector_type1 = factory1.connector_type(connector_type_name1)
202                 for guid2, connector_type_name2 in connections2.iteritems():
203                     element2 = self._elements[guid2]
204                     factory_id2 = self._create[guid2]
205                     # Connections are executed in a "From -> To" direction only
206                     # This explicitly ignores the "To -> From" (mirror) 
207                     # connections of every connection pair. 
208                     code_to_connect = connector_type1.code_to_connect(
209                             self._testbed_id, factory_id2, 
210                             connector_type_name2)
211                     if code_to_connect:
212                         code_to_connect(self, element1, element2)
213
214     def do_configure(self):
215         raise NotImplementedError
216
217     def do_cross_connect(self):
218         for guid, cross_connections in self._cross_connect.iteritems():
219             element = self._elements[guid]
220             factory_id = self._create[guid]
221             factory = self._factories[factory_id]
222             for connector_type_name, cross_connection in \
223                     cross_connections.iteritems():
224                 connector_type = factory.connector_type(connector_type_name)
225                 (cross_testbed_id, cross_factory_id, 
226                         cross_connector_type_name) = cross_connection
227                 code_to_connect = connector_type.code_to_connect(
228                     cross_guid, cross_testbed_id, cross_factory_id, 
229                     cross_conector_type_name)
230                 if code_to_connect:
231                     code_to_connect(element, cross_guid)       
232
233     def set(self, time, guid, name, value):
234         if not guid in self._create:
235             raise RuntimeError("Element guid %d doesn't exist" % guid)
236         factory_id = self._create[guid]
237         factory = self._factories[factory_id]
238         if not factory.box_attributes.has_attribute(name):
239             raise RuntimeError("Invalid attribute %s for element type %s" %
240                     (name, factory_id))
241         if self._started and factory.is_attribute_design_only(name):
242             raise RuntimeError("Attribute %s can only be modified during experiment design" % name)
243         factory.box_attributes.set_attribute_value(name, value)
244         if guid not in self._set:
245             self._set[guid] = dict()
246         if time not in self._set[guid]:
247             self._set[guid][time] = dict()
248         self._set[guid][time][name] = value
249
250     def get(self, time, guid, name):
251         raise NotImplementedError
252
253     def start(self, time = TIME_NOW):
254         for guid, factory_id in self._create.iteritems():
255             factory = self._factories[factory_id]
256             start_function = factory.start_function
257             if start_function:
258                 traces = self._traces(guid)
259                 parameters = self._parameters(guid)
260                 start_function(self, guid, parameters, traces)
261         self._started = True
262
263     def action(self, time, guid, action):
264         raise NotImplementedError
265
266     def stop(self, time = TIME_NOW):
267         for guid, factory_id in self._create.iteritems():
268             factory = self._factories[factory_id]
269             stop_function = factory.stop_function
270             if stop_function:
271                 traces = self._traces(guid)
272                 parameters = self._parameters(guid)
273                 stop_function(self, guid, parameters, traces)
274
275     def status(self, guid):
276         if not guid in self._create:
277             raise RuntimeError("Element guid %d doesn't exist" % guid)
278         factory_id = self._create[guid]
279         factory = self._factories[factory_id]
280         status_function = factory.status_function
281         if status_function:
282             return status_function(self, guid, parameters, traces)
283         return STATUS_UNDETERMINED
284
285     def trace(self, guid, trace_id):
286         raise NotImplementedError
287
288     def shutdown(self):
289         raise NotImplementedError
290
291     def get_connected(self, guid, connector_type_name, 
292             other_connector_type_name):
293         """searchs the connected elements for the specific connector_type_name 
294         pair"""
295         if guid not in self._connect:
296             return []
297         # all connections for all connectors for guid
298         all_connections = self._connect[guid]
299         if connector_type_name not in all_connections:
300             return []
301         # all connections for the specific connector
302         connections = all_connections[connector_type_name]
303         specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
304                 in connections.iteritems() if \
305                 otr_connector_type_name == other_connector_type_name]
306         return specific_connections
307
308     def _get_connection_count(self, guid, connection_type_name):
309         count = 0
310         cross_count = 0
311         if guid in self._connect and connection_type_name in \
312                 self._connect[guid]:
313             count = len(self._connect[guid][connection_type_name])
314         if guid in self._cross_connect and connection_type_name in \
315                 self._cross_connect[guid]:
316             cross_count = len(self._cross_connect[guid][connection_type_name])
317         return count + cross_count
318
319     def _get_traces(self, guid):
320         return [] if guid not in self._add_trace else self._add_trace[guid]
321
322     def _get_parameters(self, guid):
323         return dict() if guid not in self._create_set else \
324                 self._create_set[guid]
325