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