Initially working version of PlanetLab testbed implementation.
[nepi.git] / src / nepi / core / metadata.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from nepi.core.attributes import Attribute, AttributesMap
5 import sys
6 from nepi.util import validation
7
8 class VersionedMetadataInfo(object):
9     @property
10     def connector_types(self):
11         """ dictionary of dictionaries with allowed connection information.
12             connector_id: dict({
13                 "help": help text, 
14                 "name": connector type name,
15                 "max": maximum number of connections allowed (-1 for no limit),
16                 "min": minimum number of connections allowed
17             }),
18         """
19         raise NotImplementedError
20
21     @property
22     def connections(self):
23         """ array of dictionaries with allowed connection information.
24         dict({
25             "from": (testbed_id1, factory_id1, connector_type_name1),
26             "to": (testbed_id2, factory_id2, connector_type_name2),
27             "code": connection function to invoke upon connection creation
28             "can_cross": whether the connection can be done across testbed 
29                             instances
30          }),
31         """
32         raise NotImplementedError
33
34     @property
35     def attributes(self):
36         """ dictionary of dictionaries of all available attributes.
37             attribute_id: dict({
38                 "name": attribute name,
39                 "help": help text,
40                 "type": attribute type, 
41                 "value": default attribute value,
42                 "range": (maximum, minimun) values else None if not defined,
43                 "allowed": array of posible values,
44                 "flags": attributes flags,
45                 "validation_function": validation function for the attribute
46             })
47         """
48         raise NotImplementedError
49
50     @property
51     def traces(self):
52         """ dictionary of dictionaries of all available traces.
53             trace_id: dict({
54                 "name": trace name,
55                 "help": help text
56             })
57         """
58         raise NotImplementedError
59
60     @property
61     def create_order(self):
62         """ list of factory ids that indicates the order in which the elements
63         should be instantiated.
64         """
65         raise NotImplementedError
66
67     @property
68     def configure_order(self):
69         """ list of factory ids that indicates the order in which the elements
70         should be configured.
71         """
72         raise NotImplementedError
73
74     @property
75     def preconfigure_order(self):
76         """ list of factory ids that indicates the order in which the elements
77         should be preconfigured.
78         
79         Default: same as configure_order
80         """
81         return self.configure_order
82
83     @property
84     def factories_info(self):
85         """ dictionary of dictionaries of factory specific information
86             factory_id: dict({
87                 "allow_addresses": whether the box allows adding IP addresses,
88                 "allow_routes": wether the box allows adding routes,
89                 "help": help text,
90                 "category": category the element belongs to,
91                 "create_function": function for element instantiation,
92                 "start_function": function for element starting,
93                 "stop_function": function for element stoping,
94                 "status_function": function for retrieving element status,
95                 "preconfigure_function": function for element preconfiguration,
96                     (just after connections are made, 
97                     just before netrefs are resolved)
98                 "configure_function": function for element configuration,
99                 "factory_attributes": list of references to attribute_ids,
100                 "box_attributes": list of regerences to attribute_ids,
101                 "traces": list of references to trace_id
102                 "connector_types": list of references to connector_types
103            })
104         """
105         raise NotImplementedError
106
107     @property
108     def testbed_attributes(self):
109         """ dictionary of attributes for testbed instance configuration
110             attributes_id = dict({
111                 "name": attribute name,
112                 "help": help text,
113                 "type": attribute type, 
114                 "value": default attribute value,
115                 "range": (maximum, minimun) values else None if not defined,
116                 "allowed": array of posible values,
117                 "flags": attributes flags,
118                 "validation_function": validation function for the attribute
119              })
120             ]
121         """
122         raise NotImplementedError
123
124 class Metadata(object):
125     STANDARD_BOX_ATTRIBUTES = (
126         ("label", dict({
127             "name": "label",
128             "validation_function": validation.is_string,
129             "type": Attribute.STRING,
130             "flags": Attribute.DesignOnly,
131             "help": "A unique identifier for referring to this box",
132         })),
133     )
134
135     STANDARD_TESTBED_ATTRIBUTES = (
136         ("home_directory", dict({
137             "name": "homeDirectory",
138             "validation_function": validation.is_string,
139             "help": "Path to the directory where traces and other files will be stored",
140             "type": Attribute.STRING,
141             "value": "",
142             "flags": Attribute.DesignOnly,
143         })),
144     )
145
146     def __init__(self, testbed_id, version):
147         self._version = version
148         self._testbed_id = testbed_id
149         metadata_module = self._load_versioned_metadata_module()
150         self._metadata = metadata_module.VersionedMetadataInfo()
151
152     @property
153     def create_order(self):
154         return self._metadata.create_order
155
156     @property
157     def configure_order(self):
158         return self._metadata.configure_order
159
160     @property
161     def preconfigure_order(self):
162         return self._metadata.preconfigure_order
163
164     def testbed_attributes(self):
165         attributes = AttributesMap()
166
167         # standard attributes
168         self._add_standard_attributes(attributes, None, True, False,
169             self.STANDARD_TESTBED_ATTRIBUTES)
170         
171         # custom attributes - they override standard ones
172         for attr_info in self._metadata.testbed_attributes.values():
173             name = attr_info["name"]
174             help = attr_info["help"]
175             type = attr_info["type"] 
176             value = attr_info["value"] if "value" in attr_info else None
177             range = attr_info["range"] if "range" in attr_info else None
178             allowed = attr_info["allowed"] if "allowed" in attr_info else None
179             flags =  attr_info["flags"] if "flags" in attr_info \
180                     else Attribute.NoFlags
181             validation_function = attr_info["validation_function"]
182             attributes.add_attribute(name, help, type, value, 
183                     range, allowed, flags, validation_function)
184         
185         return attributes
186
187     def build_design_factories(self):
188         from nepi.core.design import Factory
189         factories = list()
190         for factory_id, info in self._metadata.factories_info.iteritems():
191             help = info["help"]
192             category = info["category"]
193             allow_addresses = info["allow_addresses"] \
194                     if "allow_addresses" in info else False
195             allow_routes = info["allow_routes"] \
196                     if "allow_routes" in info else False
197             factory = Factory(factory_id, allow_addresses, allow_routes,
198                     help, category)
199             
200             # standard attributes
201             self._add_standard_attributes(factory, info, True, True,
202                 self.STANDARD_BOX_ATTRIBUTES)
203             
204             # custom attributes - they override standard ones
205             self._add_attributes(factory, info, "factory_attributes")
206             self._add_attributes(factory, info, "box_attributes", True)
207             
208             self._add_design_traces(factory, info)
209             self._add_design_connector_types(factory, info)
210             factories.append(factory)
211         return factories
212
213     def build_execute_factories(self):
214         from nepi.core.execute import Factory
215         factories = list()
216         for factory_id, info in self._metadata.factories_info.iteritems():
217             create_function = info.get("create_function")
218             start_function = info.get("start_function")
219             stop_function = info.get("stop_function")
220             status_function = info.get("status_function")
221             configure_function = info.get("configure_function")
222             preconfigure_function = info.get("preconfigure_function")
223             allow_addresses = info.get("allow_addresses", False)
224             allow_routes = info.get("allow_routes", False)
225             factory = Factory(factory_id, create_function, start_function,
226                     stop_function, status_function, 
227                     configure_function, preconfigure_function,
228                     allow_addresses, allow_routes)
229                     
230             # standard attributes
231             self._add_standard_attributes(factory, info, False, True,
232                 self.STANDARD_BOX_ATTRIBUTES)
233             
234             # custom attributes - they override standard ones
235             self._add_attributes(factory, info, "factory_attributes")
236             self._add_attributes(factory, info, "box_attributes", True)
237             
238             self._add_execute_traces(factory, info)
239             self._add_execute_connector_types(factory, info)
240             factories.append(factory)
241         return factories
242
243     def _load_versioned_metadata_module(self):
244         mod_name = "nepi.testbeds.%s.metadata_v%s" % (self._testbed_id.lower(),
245                 self._version)
246         if not mod_name in sys.modules:
247             __import__(mod_name)
248         return sys.modules[mod_name]
249
250     def _add_standard_attributes(self, factory, info, design, box, STANDARD_ATTRIBUTES):
251         if design:
252             attr_bundle = STANDARD_ATTRIBUTES
253         else:
254             # Only add non-DesignOnly attributes
255             def nonDesign(attr_info):
256                 return not (attr_info[1].get('flags',Attribute.NoFlags) & Attribute.DesignOnly)
257             attr_bundle = filter(nonDesign, STANDARD_ATTRIBUTES)
258         self._add_attributes(factory, info, None, box, 
259             attr_bundle = STANDARD_ATTRIBUTES)
260
261     def _add_attributes(self, factory, info, attr_key, box_attributes = False, attr_bundle = ()):
262         if not attr_bundle and info and attr_key in info:
263             attr_bundle = [ (attr_id, self._metadata.attributes[attr_id])
264                             for attr_id in info[attr_key] ]
265         for attr_id, attr_info in attr_bundle:
266             name = attr_info["name"]
267             help = attr_info["help"]
268             type = attr_info["type"] 
269             value = attr_info["value"] if "value" in attr_info else None
270             range = attr_info["range"] if "range" in attr_info else None
271             allowed = attr_info["allowed"] if "allowed" in attr_info \
272                     else None
273             flags = attr_info["flags"] if "flags" in attr_info \
274                     and attr_info["flags"] != None \
275                     else Attribute.NoFlags
276             validation_function = attr_info["validation_function"]
277             if box_attributes:
278                 factory.add_box_attribute(name, help, type, value, range, 
279                         allowed, flags, validation_function)
280             else:
281                 factory.add_attribute(name, help, type, value, range, 
282                         allowed, flags, validation_function)
283
284     def _add_design_traces(self, factory, info):
285         if "traces" in info:
286             for trace in info["traces"]:
287                 trace_info = self._metadata.traces[trace]
288                 trace_id = trace_info["name"]
289                 help = trace_info["help"]
290                 factory.add_trace(trace_id, help)
291
292     def _add_execute_traces(self, factory, info):
293         if "traces" in info:
294             for trace in info["traces"]:
295                 trace_info = self._metadata.traces[trace]
296                 trace_id = trace_info["name"]
297                 factory.add_trace(trace_id)
298
299     def _add_design_connector_types(self, factory, info):
300         from nepi.core.design import ConnectorType
301         if "connector_types" in info:
302             connections = dict()
303             for connection in self._metadata.connections:
304                 from_ = connection["from"]
305                 to = connection["to"]
306                 can_cross = connection["can_cross"]
307                 if from_ not in connections:
308                     connections[from_] = list()
309                 if to not in connections:
310                     connections[to] = list()
311                 connections[from_].append((to, can_cross))
312                 connections[to].append((from_, can_cross))
313             for connector_id in info["connector_types"]:
314                 connector_type_info = self._metadata.connector_types[
315                         connector_id]
316                 name = connector_type_info["name"]
317                 help = connector_type_info["help"]
318                 max = connector_type_info["max"]
319                 min = connector_type_info["min"]
320                 testbed_id = self._testbed_id
321                 factory_id = factory.factory_id
322                 connector_type = ConnectorType(testbed_id, factory_id, name, 
323                         help, max, min)
324                 for (to, can_cross) in connections[(testbed_id, factory_id, 
325                         name)]:
326                     (testbed_id_to, factory_id_to, name_to) = to
327                     connector_type.add_allowed_connection(testbed_id_to, 
328                             factory_id_to, name_to, can_cross)
329                 factory.add_connector_type(connector_type)
330
331     def _add_execute_connector_types(self, factory, info):
332         from nepi.core.execute import ConnectorType
333         if "connector_types" in info:
334             from_connections = dict()
335             to_connections = dict()
336             for connection in self._metadata.connections:
337                 from_ = connection["from"]
338                 to = connection["to"]
339                 can_cross = connection["can_cross"]
340                 code = connection["code"]
341                 if from_ not in from_connections:
342                     from_connections[from_] = list()
343                 if to not in to_connections:
344                     to_connections[to] = list()
345                 from_connections[from_].append((to, can_cross, code))
346                 to_connections[to].append((from_, can_cross, code))
347             for connector_id in info["connector_types"]:
348                 connector_type_info = self._metadata.connector_types[
349                         connector_id]
350                 name = connector_type_info["name"]
351                 max = connector_type_info["max"]
352                 min = connector_type_info["min"]
353                 testbed_id = self._testbed_id
354                 factory_id = factory.factory_id
355                 connector_type = ConnectorType(testbed_id, factory_id, name, 
356                         max, min)
357                 connector_key = (testbed_id, factory_id, name)
358                 if connector_key in to_connections:
359                     for (from_, can_cross, code) in to_connections[connector_key]:
360                         (testbed_id_from, factory_id_from, name_from) = from_
361                         connector_type.add_from_connection(testbed_id_from, 
362                                 factory_id_from, name_from, can_cross, code)
363                 if connector_key in from_connections:
364                     for (to, can_cross, code) in from_connections[(testbed_id, 
365                             factory_id, name)]:
366                         (testbed_id_to, factory_id_to, name_to) = to
367                         connector_type.add_to_connection(testbed_id_to, 
368                                 factory_id_to, name_to, can_cross, code)
369                 factory.add_connector_type(connector_type)
370