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