Added routes to OMF nodes
[nepi.git] / src / nepi / core / design.py
1 # -*- coding: utf-8 -*-
2
3 """
4 Experiment design API
5 """
6
7 from nepi.core.attributes import AttributesMap, Attribute
8 from nepi.core.metadata import Metadata
9 from nepi.util import validation
10 from nepi.util.guid import GuidGenerator
11 from nepi.util.graphical_info import GraphicalInfo
12 from nepi.util.parser._xml import XmlExperimentParser
13 from nepi.util.tags import Taggable
14 import sys
15
16 class Connector(object):
17     """A Connector sepcifies the connection points in an Object"""
18     def __init__(self, box, connector_type):
19         super(Connector, self).__init__()
20         self._box = box
21         self._connector_type = connector_type
22         self._connections = list()
23
24     def __str__(self):
25         return "Connector(%s, %s)" % (self.box, self.connector_type)
26
27     @property
28     def box(self):
29         return self._box
30
31     @property
32     def connector_type(self):
33         return self._connector_type
34
35     @property
36     def connections(self):
37         return self._connections
38
39     def is_full(self):
40         """Return True if the connector has the maximum number of connections
41         """
42         return len(self.connections) == self.connector_type.max
43
44     def is_complete(self):
45         """Return True if the connector has the minimum number of connections
46         """
47         return len(self.connections) >= self.connector_type.min
48
49     def is_connected(self, connector):
50         return connector in self._connections
51
52     def connect(self, connector):
53         if not self.can_connect(connector) or not connector.can_connect(self):
54             raise RuntimeError("Could not connect. %s to %s" % (self, connector))
55         self._connections.append(connector)
56         connector._connections.append(self)
57
58     def get_connected_box(self, idx = 0):
59         if len(self._connections) == 0:
60             return None
61         return self._connections[idx].box
62
63     def disconnect(self, connector):
64         if connector not in self._connections or\
65                 self not in connector._connections:
66                 raise RuntimeError("Could not disconnect.")
67         self._connections.remove(connector)
68         connector._connections.remove(self)
69
70     def can_connect(self, connector):
71         # can't connect with self
72         if self.box.guid == connector.box.guid:
73             return False
74         if self.is_full() or connector.is_full():
75             return False
76         if self.is_connected(connector):
77             return False
78         (testbed_id, factory_id, name) = connector.connector_type.connector_type_id
79         testbed_guid1 = self.box.testbed_guid
80         testbed_guid2 = connector.box.testbed_guid
81         must_cross = (testbed_guid1 != testbed_guid2)
82         return self.connector_type.can_connect(testbed_id, factory_id, name,
83                 must_cross)
84
85     def destroy(self):
86         for connector in self.connections:
87             self.disconnect(connector)
88         self._box = self._connectors = None
89
90 class Trace(AttributesMap):
91     def __init__(self, name, help, enabled = False):
92         super(Trace, self).__init__()
93         self._name = name
94         self._help = help       
95         self._enabled = enabled
96     
97     @property
98     def name(self):
99         return self._name
100
101     @property
102     def help(self):
103         return self._help
104
105     @property
106     def enabled(self):
107         return self._enabled
108
109     def enable(self):
110         self._enabled = True
111
112     def disable(self):
113         self._enabled = False
114
115 class Address(AttributesMap):
116     def __init__(self):
117         super(Address, self).__init__()
118         self.add_attribute(name = "Address",
119                 help = "Address number", 
120                 type = Attribute.STRING,
121                 flags = Attribute.NoDefaultValue,
122                 validation_function = validation.is_ip_address)
123         self.add_attribute(name = "NetPrefix",
124                 help = "Network prefix for the address", 
125                 type = Attribute.INTEGER, 
126                 range = (0, 128),
127                 value = 24,
128                 flags = Attribute.NoDefaultValue,
129                 validation_function = validation.is_integer)
130         self.add_attribute(name = "Broadcast",
131                 help = "Broadcast address", 
132                 type = Attribute.STRING,
133                 validation_function = validation.is_ip4_address)
134                 
135 class Route(AttributesMap):
136     def __init__(self):
137         super(Route, self).__init__()
138         self.add_attribute(name = "Destination", 
139                 help = "Network destintation",
140                 type = Attribute.STRING, 
141                 validation_function = validation.is_ref_address)
142         self.add_attribute(name = "NetPrefix",
143                 help = "Network destination prefix", 
144                 type = Attribute.INTEGER, 
145                 range = (0, 128),
146                 value = 24,
147                 flags = Attribute.NoDefaultValue,
148                 validation_function = validation.is_integer)
149         self.add_attribute(name = "NextHop",
150                 help = "Address for the next hop", 
151                 type = Attribute.STRING,
152                 flags = Attribute.NoDefaultValue,
153                 validation_function = validation.is_ref_address)
154         self.add_attribute(name = "Metric",
155                 help = "Routing metric", 
156                 type = Attribute.INTEGER,
157                 value = 0,
158                 flags = Attribute.NoDefaultValue,
159                 validation_function = validation.is_integer)
160         self.add_attribute(name = "Device",
161                 help = "Device name", 
162                 type = Attribute.STRING,
163                 value = None,
164                 flags = Attribute.NoDefaultValue,
165                 validation_function = validation.is_string)
166
167 class Box(AttributesMap, Taggable):
168     def __init__(self, guid, factory, testbed_guid, container = None):
169         super(Box, self).__init__()
170         # guid -- global unique identifier
171         self._guid = guid
172         # factory_id -- factory identifier or name
173         self._factory_id = factory.factory_id
174         # testbed_guid -- parent testbed guid
175         self._testbed_guid = testbed_guid
176         # container -- boxes can be nested inside other 'container' boxes
177         self._container = container
178         # traces -- list of available traces for the box
179         self._traces = dict()
180         # connectors -- list of available connectors for the box
181         self._connectors = dict()
182         # factory_attributes -- factory attributes for box construction
183         self._factory_attributes = dict()
184         # graphical_info -- GUI position information
185         self.graphical_info = GraphicalInfo()
186
187         for connector_type in factory.connector_types:
188             connector = Connector(self, connector_type)
189             self._connectors[connector_type.name] = connector
190         for (name, help, enabled) in factory.traces:
191             trace = Trace(name, help, enabled)
192             self._traces[name] = trace
193         for tag_id in factory.tags:
194             self.add_tag(tag_id)
195         for attr in factory.box_attributes.attributes:
196             self.add_attribute(attr.name, attr.help, attr.type, attr.value, 
197                     attr.range, attr.allowed, attr.flags, 
198                     attr.validation_function, attr.category)
199         for attr in factory.attributes:
200             if attr.modified or attr.is_metadata:
201                 self._factory_attributes[attr.name] = attr.value
202
203     def __str__(self):
204         return "Box(%s, %s, %s)" % (self.guid, self.factory_id, self.testbed_guid)
205
206     @property
207     def guid(self):
208         return self._guid
209
210     @property
211     def factory_id(self):
212         return self._factory_id
213
214     @property
215     def testbed_guid(self):
216         return self._testbed_guid
217
218     @property
219     def container(self):
220         return self._container
221
222     @property
223     def connectors(self):
224         return self._connectors.values()
225
226     @property
227     def traces(self):
228         return self._traces.values()
229
230     @property
231     def traces_list(self):
232         return self._traces.keys()
233
234     @property
235     def factory_attributes(self):
236         return self._factory_attributes
237
238     def trace_help(self, trace_id):
239         return self._traces[trace_id].help
240
241     def enable_trace(self, trace_id):
242         self._traces[trace_id].enable()
243
244     def disable_trace(self, trace_id):
245         self._traces[trace_id].disable()
246
247     def is_trace_enabled(self, trace_id):
248         return self._traces[trace_id].enabled
249
250     def connector(self, name):
251         return self._connectors[name]
252
253     def destroy(self):
254         super(Box, self).destroy()
255         for c in self.connectors:
256             c.destroy()         
257         for t in self.traces:
258             t.destroy()
259         self._connectors = self._traces = self._factory_attributes = None
260
261 class FactoriesProvider(object):
262     def __init__(self, testbed_id):
263         super(FactoriesProvider, self).__init__()
264         self._testbed_id = testbed_id
265         self._factories = dict()
266
267         metadata = Metadata(testbed_id) 
268         for factory in metadata.build_factories():
269             self.add_factory(factory)
270
271         self._testbed_version = metadata.testbed_version
272
273     @property
274     def testbed_id(self):
275         return self._testbed_id
276
277     @property
278     def testbed_version(self):
279         return self._testbed_version
280
281     @property
282     def factories(self):
283         return self._factories.values()
284
285     def factory(self, factory_id):
286         return self._factories[factory_id]
287
288     def add_factory(self, factory):
289         self._factories[factory.factory_id] = factory
290
291     def remove_factory(self, factory_id):
292         del self._factories[factory_id]
293
294 class TestbedDescription(AttributesMap):
295     def __init__(self, guid_generator, provider, guid = None):
296         super(TestbedDescription, self).__init__()
297         self._guid_generator = guid_generator
298         self._guid = guid_generator.next(guid)
299         self._provider = provider
300         self._boxes = dict()
301         self.graphical_info = GraphicalInfo()
302
303         metadata = Metadata(provider.testbed_id)
304         for attr in metadata.testbed_attributes().attributes:
305             self.add_attribute(attr.name, attr.help, attr.type, attr.value, 
306                     attr.range, attr.allowed, attr.flags, 
307                     attr.validation_function, attr.category)
308
309     @property
310     def guid(self):
311         return self._guid
312
313     @property
314     def provider(self):
315         return self._provider
316
317     @property
318     def boxes(self):
319         return self._boxes.values()
320
321     def box(self, guid):
322         return self._boxes[guid] if guid in self._boxes else None
323
324     def create(self, factory_id, guid = None):
325         guid = self._guid_generator.next(guid)
326         factory = self._provider.factory(factory_id)
327         box = factory.create(guid, self)
328         self._boxes[guid] = box
329         return box
330
331     def delete(self, guid):
332         box = self._boxes[guid]
333         del self._boxes[guid]
334         box.destroy()
335
336     def destroy(self):
337         for guid, box in self._boxes.iteritems():
338             box.destroy()
339         self._boxes = None
340
341 class ExperimentDescription(object):
342     def __init__(self):
343         self._guid_generator = GuidGenerator()
344         self._testbed_descriptions = dict()
345
346     @property
347     def testbed_descriptions(self):
348         return self._testbed_descriptions.values()
349
350     def to_xml(self):
351         parser = XmlExperimentParser()
352         return parser.to_xml(self)
353
354     def from_xml(self, xml):
355         parser = XmlExperimentParser()
356         parser.from_xml(self, xml)
357
358     def testbed_description(self, guid):
359         return self._testbed_descriptions[guid] \
360                 if guid in self._testbed_descriptions else None
361
362     def box(self, guid):
363         for testbed_description in self._testbed_descriptions.values():
364             box = testbed_description.box(guid)
365             if box: return box
366         return None
367
368     def get_element(self, guid):
369         if guid in self._testbed_descriptions:
370             return self._testbed_descriptions[guid]
371         for testbed_description in self._testbed_descriptions.values():
372             box = testbed_description.box(guid)
373             if box: return box
374         return None
375
376     def get_element_by_label(self, label):
377         for tbd_desc in self._testbed_descriptions.values():
378             l = tbd_desc.get_attribute_value("label")
379             if label == l:
380                 return tbd_desc
381             for box in tbd_desc.boxes:
382                 l = box.get_attribute_value("label")
383                 if label == l:
384                     return box
385         return None
386     
387     def add_testbed_description(self, provider, guid = None):
388         testbed_description = TestbedDescription(self._guid_generator, 
389                 provider, guid)
390         guid = testbed_description.guid
391         self._testbed_descriptions[guid] = testbed_description
392         return testbed_description
393
394     def remove_testbed_description(self, guid):
395         testbed_description = self._testbed_descriptions[guid]
396         del self._testbed_descriptions[guid]
397         testbed_description.destroy()
398
399     def destroy(self):
400         for testbed_description in self.testbed_descriptions:
401             testbed_description.destroy()
402
403