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