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