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