2 # -*- coding: utf-8 -*-
4 from nepi.core.attributes import Attribute, AttributesMap
5 from nepi.core.connector import ConnectorType
6 from nepi.core.factory import Factory
9 from nepi.util import validation
10 from nepi.util.constants import ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP, \
11 DeploymentConfiguration as DC, \
12 AttributeCategories as AC
14 class VersionedMetadataInfo(object):
16 def connector_types(self):
17 """ dictionary of dictionaries with allowed connection information.
20 "name": connector type name,
21 "max": maximum number of connections allowed (-1 for no limit),
22 "min": minimum number of connections allowed
25 raise NotImplementedError
28 def connections(self):
29 """ array of dictionaries with allowed connection information.
31 "from": (testbed_id1, factory_id1, connector_type_name1),
32 "to": (testbed_id2, factory_id2, connector_type_name2),
33 "init_code": connection function to invoke for connection initiation
34 "compl_code": connection function to invoke for connection
36 "can_cross": whether the connection can be done across testbed
40 raise NotImplementedError
44 """ dictionary of dictionaries of all available attributes.
46 "name": attribute name,
48 "type": attribute type,
49 "value": default attribute value,
50 "range": (maximum, minimun) values else None if not defined,
51 "allowed": array of posible values,
52 "flags": attributes flags,
53 "validation_function": validation function for the attribute
54 "category": category for the attribute
57 raise NotImplementedError
61 """ dictionary of dictionaries of all available traces.
67 raise NotImplementedError
70 def create_order(self):
71 """ list of factory ids that indicates the order in which the elements
72 should be instantiated.
74 raise NotImplementedError
77 def configure_order(self):
78 """ list of factory ids that indicates the order in which the elements
81 raise NotImplementedError
84 def preconfigure_order(self):
85 """ list of factory ids that indicates the order in which the elements
86 should be preconfigured.
88 Default: same as configure_order
90 return self.configure_order
93 def prestart_order(self):
94 """ list of factory ids that indicates the order in which the elements
95 should be prestart-configured.
97 Default: same as configure_order
99 return self.configure_order
102 def start_order(self):
103 """ list of factory ids that indicates the order in which the elements
106 Default: same as configure_order
108 return self.configure_order
111 def factories_info(self):
112 """ dictionary of dictionaries of factory specific information
114 "allow_addresses": whether the box allows adding IP addresses,
115 "allow_routes": wether the box allows adding routes,
116 "has_addresses": whether the box allows obtaining IP addresses,
117 "has_routes": wether the box allows obtaining routes,
119 "category": category the element belongs to,
120 "create_function": function for element instantiation,
121 "start_function": function for element starting,
122 "stop_function": function for element stoping,
123 "status_function": function for retrieving element status,
124 "preconfigure_function": function for element preconfiguration,
125 (just after connections are made,
126 just before netrefs are resolved)
127 "configure_function": function for element configuration,
128 "prestart_function": function for pre-start
129 element configuration (just before starting applications),
130 useful for synchronization of background setup tasks or
131 lazy instantiation or configuration of attributes
132 that require connection/cross-connection state before
134 After this point, all applications should be able to run.
135 "factory_attributes": list of references to attribute_ids,
136 "box_attributes": list of regerences to attribute_ids,
137 "traces": list of references to trace_id
138 "tags": list of references to tag_id
139 "connector_types": list of references to connector_types
142 raise NotImplementedError
145 def testbed_attributes(self):
146 """ dictionary of attributes for testbed instance configuration
147 attributes_id = dict({
148 "name": attribute name,
150 "type": attribute type,
151 "value": default attribute value,
152 "range": (maximum, minimun) values else None if not defined,
153 "allowed": array of posible values,
154 "flags": attributes flags,
155 "validation_function": validation function for the attribute
156 "category": category for the attribute
160 raise NotImplementedError
162 class Metadata(object):
163 # These attributes should be added to all boxes
164 STANDARD_BOX_ATTRIBUTES = dict({
167 "validation_function" : validation.is_string,
168 "type" : Attribute.STRING,
169 "flags" : Attribute.DesignOnly,
170 "help" : "A unique identifier for referring to this box",
174 # These attributes should be added to all testbeds
175 STANDARD_TESTBED_ATTRIBUTES = dict({
176 "home_directory" : dict({
177 "name" : "homeDirectory",
178 "validation_function" : validation.is_string,
179 "help" : "Path to the directory where traces and other files will be stored",
180 "type" : Attribute.STRING,
182 "flags" : Attribute.DesignOnly
186 "validation_function" : validation.is_string,
187 "type" : Attribute.STRING,
188 "flags" : Attribute.DesignOnly,
189 "help" : "A unique identifier for referring to this testbed",
193 # These attributes should be added to all testbeds
194 DEPLOYMENT_ATTRIBUTES = dict({
195 # TESTBED DEPLOYMENT ATTRIBUTES
196 DC.DEPLOYMENT_ENVIRONMENT_SETUP : dict({
197 "name" : DC.DEPLOYMENT_ENVIRONMENT_SETUP,
198 "validation_function" : validation.is_string,
199 "help" : "Shell commands to run before spawning TestbedController processes",
200 "type" : Attribute.STRING,
201 "flags" : Attribute.DesignOnly,
202 "category" : AC.CATEGORY_DEPLOYMENT,
204 DC.DEPLOYMENT_MODE: dict({
205 "name" : DC.DEPLOYMENT_MODE,
206 "help" : "Instance execution mode",
207 "type" : Attribute.ENUM,
208 "value" : DC.MODE_SINGLE_PROCESS,
211 DC.MODE_SINGLE_PROCESS
213 "flags" : Attribute.DesignOnly,
214 "validation_function" : validation.is_enum,
215 "category" : AC.CATEGORY_DEPLOYMENT,
217 DC.DEPLOYMENT_COMMUNICATION : dict({
218 "name" : DC.DEPLOYMENT_COMMUNICATION,
219 "help" : "Instance communication mode",
220 "type" : Attribute.ENUM,
221 "value" : DC.ACCESS_LOCAL,
226 "flags" : Attribute.DesignOnly,
227 "validation_function" : validation.is_enum,
228 "category" : AC.CATEGORY_DEPLOYMENT,
230 DC.DEPLOYMENT_HOST : dict({
231 "name" : DC.DEPLOYMENT_HOST,
232 "help" : "Host where the testbed will be executed",
233 "type" : Attribute.STRING,
234 "value" : "localhost",
235 "flags" : Attribute.DesignOnly,
236 "validation_function" : validation.is_string,
237 "category" : AC.CATEGORY_DEPLOYMENT,
239 DC.DEPLOYMENT_USER : dict({
240 "name" : DC.DEPLOYMENT_USER,
241 "help" : "User on the Host to execute the testbed",
242 "type" : Attribute.STRING,
243 "value" : getpass.getuser(),
244 "flags" : Attribute.DesignOnly,
245 "validation_function" : validation.is_string,
246 "category" : AC.CATEGORY_DEPLOYMENT,
248 DC.DEPLOYMENT_KEY : dict({
249 "name" : DC.DEPLOYMENT_KEY,
250 "help" : "Path to SSH key to use for connecting",
251 "type" : Attribute.STRING,
252 "flags" : Attribute.DesignOnly,
253 "validation_function" : validation.is_string,
254 "category" : AC.CATEGORY_DEPLOYMENT,
256 DC.DEPLOYMENT_PORT : dict({
257 "name" : DC.DEPLOYMENT_PORT,
258 "help" : "Port on the Host",
259 "type" : Attribute.INTEGER,
261 "flags" : Attribute.DesignOnly,
262 "validation_function" : validation.is_integer,
263 "category" : AC.CATEGORY_DEPLOYMENT,
265 DC.ROOT_DIRECTORY : dict({
266 "name" : DC.ROOT_DIRECTORY,
267 "help" : "Root directory for storing process files",
268 "type" : Attribute.STRING,
270 "flags" : Attribute.DesignOnly,
271 "validation_function" : validation.is_string, # TODO: validation.is_path
272 "category" : AC.CATEGORY_DEPLOYMENT,
274 DC.USE_AGENT : dict({
275 "name" : DC.USE_AGENT,
276 "help" : "Use -A option for forwarding of the authentication agent, if ssh access is used",
277 "type" : Attribute.BOOL,
279 "flags" : Attribute.DesignOnly,
280 "validation_function" : validation.is_bool,
281 "category" : AC.CATEGORY_DEPLOYMENT,
283 DC.LOG_LEVEL : dict({
284 "name" : DC.LOG_LEVEL,
285 "help" : "Log level for instance",
286 "type" : Attribute.ENUM,
287 "value" : DC.ERROR_LEVEL,
292 "flags" : Attribute.DesignOnly,
293 "validation_function" : validation.is_enum,
294 "category" : AC.CATEGORY_DEPLOYMENT,
298 "help" : "Do not intantiate testbeds, rather, reconnect to already-running instances. Used to recover from a dead controller.",
299 "type" : Attribute.BOOL,
301 "flags" : Attribute.DesignOnly,
302 "validation_function" : validation.is_bool,
303 "category" : AC.CATEGORY_DEPLOYMENT,
307 # These attributes could appear in the boxes attribute list
308 STANDARD_BOX_ATTRIBUTE_DEFINITIONS = dict({
310 "name" : "tun_proto",
311 "help" : "TUNneling protocol used",
312 "type" : Attribute.STRING,
313 "flags" : Attribute.Invisible,
314 "validation_function" : validation.is_string,
318 "help" : "Randomly selected TUNneling protocol cryptographic key. "
319 "Endpoints must agree to use the minimum (in lexicographic order) "
320 "of both the remote and local sides.",
321 "type" : Attribute.STRING,
322 "flags" :Attribute.Invisible,
323 "validation_function" : validation.is_string,
327 "help" : "Address (IP, unix socket, whatever) of the tunnel endpoint",
328 "type" : Attribute.STRING,
329 "flags" : Attribute.Invisible,
330 "validation_function" : validation.is_string,
334 "help" : "IP port of the tunnel endpoint",
335 "type" : Attribute.INTEGER,
336 "flags" : Attribute.Invisible,
337 "validation_function" : validation.is_integer,
339 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP : dict({
340 "name" : ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
341 "help" : "Commands to set up the environment needed to run NEPI testbeds",
342 "type" : Attribute.STRING,
343 "flags" : Attribute.Invisible,
344 "validation_function" : validation.is_string
348 STANDARD_TESTBED_ATTRIBUTES.update(DEPLOYMENT_ATTRIBUTES.copy())
350 def __init__(self, testbed_id, version):
351 self._version = version
352 self._testbed_id = testbed_id
353 metadata_module = self._load_versioned_metadata_module()
354 self._metadata = metadata_module.VersionedMetadataInfo()
357 def create_order(self):
358 return self._metadata.create_order
361 def configure_order(self):
362 return self._metadata.configure_order
365 def preconfigure_order(self):
366 return self._metadata.preconfigure_order
369 def prestart_order(self):
370 return self._metadata.prestart_order
373 def start_order(self):
374 return self._metadata.start_order
376 def testbed_attributes(self):
377 attributes = AttributesMap()
378 testbed_attributes = self._testbed_attributes()
379 self._add_attributes(attributes.add_attribute, testbed_attributes)
382 def build_factories(self):
384 for factory_id, info in self._metadata.factories_info.iteritems():
385 create_function = info.get("create_function")
386 start_function = info.get("start_function")
387 stop_function = info.get("stop_function")
388 status_function = info.get("status_function")
389 configure_function = info.get("configure_function")
390 preconfigure_function = info.get("preconfigure_function")
391 prestart_function = info.get("prestart_function")
392 allow_addresses = info.get("allow_addresses", False)
393 allow_routes = info.get("allow_routes", False)
394 has_addresses = info.get("has_addresses", False)
395 has_routes = info.get("has_routes", False)
397 category = info["category"]
398 factory = Factory(factory_id,
404 preconfigure_function,
413 factory_attributes = self._factory_attributes(factory_id, info)
414 self._add_attributes(factory.add_attribute, factory_attributes)
415 box_attributes = self._box_attributes(factory_id, info)
416 self._add_attributes(factory.add_box_attribute, box_attributes)
418 self._add_traces(factory, info)
419 self._add_tags(factory, info)
420 self._add_connector_types(factory, info)
421 factories.append(factory)
424 def _load_versioned_metadata_module(self):
425 mod_name = "nepi.testbeds.%s.metadata_v%s" % (self._testbed_id.lower(),
427 if not mod_name in sys.modules:
429 return sys.modules[mod_name]
431 def _testbed_attributes(self):
433 attributes = self.STANDARD_TESTBED_ATTRIBUTES.copy()
435 attributes.update(self._metadata.testbed_attributes.copy())
438 def _factory_attributes(self, factory_id, info):
439 if "factory_attributes" not in info:
441 definitions = self._metadata.attributes.copy()
442 # filter attributes corresponding to the factory_id
443 return self._filter_attributes(info["factory_attributes"],
446 def _box_attributes(self, factory_id, info):
447 if "box_attributes" in info:
448 definitions = self.STANDARD_BOX_ATTRIBUTE_DEFINITIONS.copy()
449 definitions.update(self._metadata.attributes)
450 attributes = self._filter_attributes(info["box_attributes"],
454 attributes.update(self.STANDARD_BOX_ATTRIBUTES.copy())
457 def _filter_attributes(self, attr_list, definitions):
458 # filter attributes corresponding to the factory_id
459 attributes = dict((attr_id, definitions[attr_id]) \
460 for attr_id in attr_list)
463 def _add_attributes(self, add_attr_func, attributes):
464 for attr_id, attr_info in attributes.iteritems():
465 name = attr_info["name"]
466 help = attr_info["help"]
467 type = attr_info["type"]
468 value = attr_info["value"] if "value" in attr_info else None
469 range = attr_info["range"] if "range" in attr_info else None
470 allowed = attr_info["allowed"] if "allowed" in attr_info \
472 flags = attr_info["flags"] if "flags" in attr_info \
473 and attr_info["flags"] != None \
474 else Attribute.NoFlags
475 validation_function = attr_info["validation_function"]
476 category = attr_info["category"] if "category" in attr_info else None
477 add_attr_func(name, help, type, value, range, allowed, flags,
478 validation_function, category)
480 def _add_traces(self, factory, info):
482 for trace_id in info["traces"]:
483 trace_info = self._metadata.traces[trace_id]
484 name = trace_info["name"]
485 help = trace_info["help"]
486 factory.add_trace(name, help)
488 def _add_tags(self, factory, info):
490 for tag_id in info["tags"]:
491 factory.add_tag(tag_id)
493 def _add_connector_types(self, factory, info):
494 if "connector_types" in info:
495 from_connections = dict()
496 to_connections = dict()
497 for connection in self._metadata.connections:
498 from_ = connection["from"]
499 to = connection["to"]
500 can_cross = connection["can_cross"]
501 init_code = connection["init_code"] \
502 if "init_code" in connection else None
503 compl_code = connection["compl_code"] \
504 if "compl_code" in connection else None
505 if from_ not in from_connections:
506 from_connections[from_] = list()
507 if to not in to_connections:
508 to_connections[to] = list()
509 from_connections[from_].append((to, can_cross, init_code,
511 to_connections[to].append((from_, can_cross, init_code,
513 for connector_id in info["connector_types"]:
514 connector_type_info = self._metadata.connector_types[
516 name = connector_type_info["name"]
517 help = connector_type_info["help"]
518 max = connector_type_info["max"]
519 min = connector_type_info["min"]
520 testbed_id = self._testbed_id
521 factory_id = factory.factory_id
522 connector_type = ConnectorType(testbed_id, factory_id, name,
524 connector_key = (testbed_id, factory_id, name)
525 if connector_key in to_connections:
526 for (from_, can_cross, init_code, compl_code) in \
527 to_connections[connector_key]:
528 (testbed_id_from, factory_id_from, name_from) = from_
529 connector_type.add_from_connection(testbed_id_from,
530 factory_id_from, name_from, can_cross,
531 init_code, compl_code)
532 if connector_key in from_connections:
533 for (to, can_cross, init_code, compl_code) in \
534 from_connections[(testbed_id, factory_id, name)]:
535 (testbed_id_to, factory_id_to, name_to) = to
536 connector_type.add_to_connection(testbed_id_to,
537 factory_id_to, name_to, can_cross, init_code,
539 factory.add_connector_type(connector_type)