xml serialization of the experiment description
[nepi.git] / src / nepi / core / description.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from nepi.core.attributes import AttributesMap, Attribute
5 from nepi.util import validation
6 from nepi.util.guid import GuidGenerator
7 from nepi.util.parser._xml import XmlExperimentParser
8 import sys
9
10 AF_INET = 0
11 AF_INET6 = 1
12
13 class ConnectorType(object):
14     """A ConnectorType defines a kind of connector that can be used in an Object.
15     """
16     def __init__(self, connector_type_id, help, name, max = -1, min = 0):
17         super(ConnectorType, self).__init__()
18         """
19         ConnectorType(name, help, display_name, max, min):
20         - connector_type_id: (unique) identifier for this type. 
21             Typically: testbed_id + factory_id + name
22         - name: descriptive name for the user
23         - help: help text
24         - max: amount of connections that this type support, -1 for no limit
25         - min: minimum amount of connections to this type of connector
26         """
27         if max == -1:
28             max = sys.maxint
29         elif max <= 0:
30                 raise RuntimeError(
31              'The maximum number of connections allowed need to be more than 0')
32         if min < 0:
33             raise RuntimeError(
34              'The minimum number of connections allowed needs to be at least 0')
35         self._connector_type_id = connector_type_id
36         self._help = help
37         self._name = name
38         self._max = max
39         self._min = min
40         # list of connector_type_ids with which this connector_type is allowed
41         # to connect
42         self._allowed_connector_type_ids = list()
43
44     @property
45     def connector_type_id(self):
46         return self._connector_type_id
47
48     @property
49     def help(self):
50         return self._help
51
52     @property
53     def name(self):
54         return self._name
55
56     @property
57     def max(self):
58         return self._max
59
60     @property
61     def min(self):
62         return self._min
63
64     def add_allowed_connector_type_id(self, connector_type_id):
65         self._allowed_connector_type_ids.append(connector_type_id)
66
67     def can_connect(self, connector_type_id):
68         return connector_type_id in self._allowed_connector_type_ids
69
70 class Connector(object):
71     """A Connector sepcifies the connection points in an Object"""
72     def __init__(self, box, connector_type):
73         super(Connector, self).__init__()
74         self._box = box
75         self._connector_type = connector_type
76         self._connections = dict()
77
78     @property
79     def box(self):
80         return self._box
81
82     @property
83     def connector_type(self):
84         return self._connector_type
85
86     @property
87     def connections(self):
88         return self._connections.values()
89
90     def is_full(self):
91         """Return True if the connector has the maximum number of connections"""
92         return len(self.connections) == self.connector_type.max
93
94     def is_complete(self):
95         """Return True if the connector has the minimum number of connections"""
96         return len(self.connections) >= self.connector_type.min
97
98     def is_connected(self, connector):
99         return connector._key in self._connections
100
101     def connect(self, connector):
102         if self.is_full() or connector.is_full():
103             raise RuntimeError("Connector is full")    
104         if not self.can_connect(connector) or not connector.can_connect(self):
105             raise RuntimeError("Could not connect.")
106         self._connections[connector._key] = connector
107         connector._connections[self._key] = self
108
109     def disconnect(self, connector):
110         if connector._key not in self._connections or\
111                 self._key not in connector._connections:
112                 raise RuntimeError("Could not disconnect.")
113         del self._connections[connector._key]
114         del connector._connections[self._key]
115
116     def can_connect(self, connector):
117         connector_type_id = connector.connector_type.connector_type_id
118         return self.connector_type.can_connect(connector_type_id) 
119
120     def destroy(self):
121         for connector in self._connections:
122             self.disconnect(connector)
123         self._box = self._connectors = None
124
125     @property
126     def _key(self):
127         return "%d_%s" % (self.box.guid, 
128                 self.connector_type.connector_type_id)
129
130 class Trace(AttributesMap):
131     def __init__(self, name, help, enabled = False):
132         super(Trace, self).__init__()
133         self._name = name
134         self._help = help       
135         self.enabled = enabled
136     
137     @property
138     def name(self):
139         return self._name
140
141     @property
142     def help(self):
143         return self._help
144
145 class Address(AttributesMap):
146     def __init__(self, family):
147         super(Address, self).__init__()
148         self.add_attribute(name = "AutoConfigure", 
149                 help = "If set, this address will automatically be assigned", 
150                 type = Attribute.BOOL,
151                 value = False,
152                 validation_function = validation.is_bool)
153         self.add_attribute(name = "Family",
154                 help = "Address family type: AF_INET, AFT_INET6", 
155                 type = Attribute.INTEGER, 
156                 value = family,
157                 readonly = True)
158         address_validation = validation.is_ip4_address if family == AF_INET \
159                         else validation.is_ip6_address
160         self.add_attribute(name = "Address",
161                 help = "Address number", 
162                 type = Attribute.STRING,
163                 validation_function = address_validation)
164         prefix_range = (0, 32) if family == AF_INET else (0, 128)
165         self.add_attribute(name = "NetPrefix",
166                 help = "Network prefix for the address", 
167                 type = Attribute.INTEGER, 
168                 range = prefix_range,
169                 validation_function = validation.is_integer)
170         if family == AF_INET:
171             self.add_attribute(name = "Broadcast",
172                     help = "Broadcast address", 
173                     type = Attribute.STRING,
174                     validation_function = validation.is_ip4_address)
175                 
176 class Route(AttributesMap):
177     def __init__(self, family):
178         super(Route, self).__init__()
179         self.add_attribute(name = "Family",
180                 help = "Address family type: AF_INET, AFT_INET6", 
181                 type = Attribute.INTEGER, 
182                 value = family,
183                 readonly = True)
184         address_validation = validation.is_ip4_address if family == AF_INET \
185                         else validation.is_ip6_address
186         self.add_attribute(name = "Destination", 
187                 help = "Network destintation",
188                 type = Attribute.STRING, 
189                 validation_function = address_validation)
190         prefix_range = (0, 32) if family == AF_INET else (0, 128)
191         self.add_attribute(name = "NetPrefix",
192                 help = "Network destination prefix", 
193                 type = Attribute.INTEGER, 
194                 prefix_range = prefix_range,
195                 validation_function = validation.is_integer)
196         self.add_attribute(name = "NextHop",
197                 help = "Address for the next hop", 
198                 type = Attribute.STRING,
199                 validation_function = address_validation)
200         self.add_attribute(name = "Interface",
201                 help = "Local interface address", 
202                 type = Attribute.STRING,
203                 validation_function = address_validation)
204
205 class Box(AttributesMap):
206     def __init__(self, guid, factory, container = None):
207         super(Box, self).__init__()
208         # general unique id
209         self._guid = guid
210         # factory identifier or name
211         self._factory_id = factory.factory_id
212         # boxes can be nested inside other 'container' boxes
213         self._container = container
214         # traces for the box
215         self._traces = dict()
216         # connectors for the box
217         self._connectors = dict()
218         # factory attributes for box construction
219         self._factory_attributes = list()
220
221         for connector_type in factory.connector_types:
222             connector = Connector(self, connector_type)
223             self._connectors[connector_type.name] = connector
224         for trace in factory.traces:
225             tr = Trace(trace.name, trace.help, trace.enabled)
226             self._traces[trace.name] = tr
227         for attr in factory.box_attributes:
228             self.add_attribute(attr.name, attr.help, attr.type, attr.value, 
229                     attr.range, attr.allowed, attr.readonly, 
230                     attr.validation_function)
231         for attr in factory.attributes:
232             self._factory_attributes.append(attr)
233
234     @property
235     def guid(self):
236         return self._guid
237
238     @property
239     def factory_id(self):
240         return self._factory_id
241
242     @property
243     def container(self):
244         return self._container
245
246     @property
247     def connectors(self):
248         return self._connectors.values()
249
250     @property
251     def traces(self):
252         return self._traces.values()
253
254     @property
255     def factory_attributes(self):
256         return self._factory_attributes
257
258     @property
259     def addresses(self):
260         return []
261
262     @property
263     def routes(self):
264         return []
265
266     def connector(self, name):
267         return self._connectors[name]
268
269     def trace(self, name):
270         return self._traces[name]
271
272     def destroy(self):
273         super(Box, self).destroy()
274         for c in self.connectors:
275             c.destroy()         
276         for t in self.traces:
277             t.destroy()
278         self._connectors = self._traces = self._factory_attributes = None
279
280 class AddressableBox(Box):
281     def __init__(self, guid, factory, family, max_addresses = 1, container = None):
282         super(AddressableBox, self).__init__(guid, factory, container)
283         self._family = family
284         # maximum number of addresses this box can have
285         self._max_addresses = max_addresses
286         self._addresses = list()
287
288     @property
289     def addresses(self):
290         return self._addresses
291
292     @property
293     def max_addresses(self):
294         return self._max_addresses
295
296     def add_address(self):
297         if len(self._addresses) == self.max_addresses:
298             raise RuntimeError("Maximun number of addresses for this box reached.")
299         address = Address(family = self._family)
300         self._addresses.append(address)
301         return address
302
303     def delete_address(self, address):
304         self._addresses.remove(address)
305         del address
306
307     def destroy(self):
308         super(AddressableBox, self).destroy()
309         for address in self.addresses:
310             self.delete_address(address)
311         self._addresses = None
312
313 class RoutingTableBox(Box):
314     def __init__(self, guid, factory, container = None):
315         super(RoutingTableBox, self).__init__(guid, factory, container)
316         self._routes = list()
317
318     @property
319     def routes(self):
320         return self._routes
321
322     def add_route(self, family):
323         route = Route(family = family)
324         self._routes.append(route)
325         return route
326
327     def delete_route(self, route):
328         self._route.remove(route)
329         del route
330
331     def destroy(self):
332         super(RoutingCapableBox, self).destroy()
333         for route in self.routes:
334             self.delete_route(route)
335         self._route = None
336
337 class BoxFactory(AttributesMap):
338     def __init__(self, factory_id, display_name, help = None, category = None):
339         super(BoxFactory, self).__init__()
340         self._factory_id = factory_id
341         self._help = help
342         self._category = category
343         self._display_name = display_name
344         self._connector_types = set()
345         self._traces = list()
346         self._box_attributes = list()
347
348     @property
349     def factory_id(self):
350         return self._factory_id
351
352     @property
353     def help(self):
354         return self._help
355
356     @property
357     def category(self):
358         return self._category
359
360     @property
361     def display_name(self):
362         return self._display_name
363
364     @property
365     def connector_types(self):
366         return self._connector_types
367
368     @property
369     def traces(self):
370         return self._traces
371
372     @property
373     def box_attributes(self):
374         return self._box_attributes
375
376     def add_connector_type(self, connector_type_id, help, name, max = -1, 
377             min = 0, allowed_connector_type_ids = []):
378         connector_type = ConnectorType(connector_type_id, help, name, max, min)
379         for connector_type_id in allowed_connector_type_ids:
380             connector_type.add_allowed_connector_type_id(connector_type_id)
381         self._connector_types.add(connector_type)
382
383     def add_trace(self, name, help, enabled = False):
384         trace = Trace(name, help, enabled)
385         self._traces.append(trace)
386
387     def add_box_attribute(self, name, help, type, value = None, range = None,
388         allowed = None, readonly = False, validation_function = None):
389         attribute = Attribute(name, help, type, value, range, allowed, readonly,
390                 validation_function)
391         self._box_attributes.append(attribute)
392
393     def create(self, guid, testbed_description):
394         return Box(guid, self)
395
396     def destroy(self):
397         super(BoxFactory, self).destroy()
398         self._connector_types = None
399
400 class AddressableBoxFactory(BoxFactory):
401     def __init__(self, factory_id, display_name, family, max_addresses = 1,
402             help = None, category = None):
403         super(AddressableBoxFactory, self).__init__(factory_id,
404                 display_name, help, category)
405         self._family = family
406         self._max_addresses = 1
407
408     def create(self, guid, testbed_description):
409         return AddressableBox(guid, self, self._family, 
410                 self._max_addresses)
411
412 class RoutingTableBoxFactory(BoxFactory):
413     def create(self, guid, testbed_description):
414         return RoutingTableBox(guid, self)
415
416 class TestbedFactoriesProvider(object):
417     def __init__(self, testbed_id, testbed_version):
418         super(TestbedFactoriesProvider, self).__init__()
419         self._testbed_id = testbed_id
420         self._testbed_version = testbed_version
421         self._factories = dict()
422
423     @property
424     def testbed_id(self):
425         return self._testbed_id
426
427     @property
428     def testbed_version(self):
429         return self._testbed_version
430
431     def factory(self, factory_id):
432         return self._factories[factory_id]
433
434     def add_factory(self, factory):
435         self._factories[factory.factory_id] = factory
436
437     def remove_factory(self, factory_id):
438         del self._factories[factory_id]
439
440     def list_factories(self):
441         return self._factories.keys()
442
443 class TestbedDescription(AttributesMap):
444     def __init__(self, guid_generator, provider):
445         super(TestbedDescription, self).__init__()
446         self._guid_generator = guid_generator
447         self._guid = guid_generator.next()
448         self._provider = provider
449         self._boxes = dict()
450
451     @property
452     def guid(self):
453         return self._guid
454
455     @property
456     def provider(self):
457         return self._provider
458
459     @property
460     def boxes(self):
461         return self._boxes.values()
462
463     def box(self, guid):
464         return self._boxes[guid] if guid in self._boxes else None
465
466     def create(self, factory_id):
467         guid = self._guid_generator.next()
468         factory = self._provider.factory(factory_id)
469         box = factory.create(guid, self)
470         self._boxes[guid] = box
471         return box
472
473     def delete(self, guid):
474         box = self._boxes[guid]
475         del self._boxes[guid]
476         box.destroy()
477
478     def destroy(self):
479         for guid, box in self._boxes.iteitems():
480             del self._boxes[guid]
481             box.destroy()
482         self._boxes = None
483
484 class ExperimentDescription(object):
485     def __init__(self, guid = 0):
486         self._guid_generator = GuidGenerator(guid)
487         # testbed design instances
488         self._testbed_descriptions = dict()
489
490     @property
491     def testbed_descriptions(self):
492         return self._testbed_descriptions.values()
493
494     def to_xml(self):
495         parser = XmlExperimentParser()
496         return parser.to_xml(self)
497
498     def from_xml(self, xml):
499         parser = XmlExperimentParser()
500         parser.from_xml(self, xml)
501
502     def testbed_description(self, guid):
503         return self._testbed_descriptions[guid] \
504                 if guid in self._testbed_descriptions else None
505
506     def box(self, guid):
507         for testbed_description in self._testbed_descriptions.values():
508             box = testbed_description.box(guid)
509             if box: return box
510         return None
511
512     def add_testbed_description(self, provider):
513         testbed_description = TestbedDescription(self._guid_generator, 
514                 provider)
515         guid = testbed_description.guid
516         self._testbed_descriptions[guid] = testbed_description
517         return testbed_description
518
519     def remove_testbed_description(self, testbed_description):
520         guid = testbed_description.guid
521         del self._testbed_descriptions[guid]
522