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