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