2 # -*- coding: utf-8 -*-
4 from nepi.core.attributes import Attribute, AttributesMap
7 from nepi.util import validation
8 from nepi.util.constants import ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP, DeploymentConfiguration
10 # Attribute categories
11 CATEGORY_DEPLOYMENT = "Deployment"
13 class VersionedMetadataInfo(object):
15 def connector_types(self):
16 """ dictionary of dictionaries with allowed connection information.
19 "name": connector type name,
20 "max": maximum number of connections allowed (-1 for no limit),
21 "min": minimum number of connections allowed
24 raise NotImplementedError
27 def connections(self):
28 """ array of dictionaries with allowed connection information.
30 "from": (testbed_id1, factory_id1, connector_type_name1),
31 "to": (testbed_id2, factory_id2, connector_type_name2),
32 "init_code": connection function to invoke for connection initiation
33 "compl_code": connection function to invoke for connection
35 "can_cross": whether the connection can be done across testbed
39 raise NotImplementedError
43 """ dictionary of dictionaries of all available attributes.
45 "name": attribute name,
47 "type": attribute type,
48 "value": default attribute value,
49 "range": (maximum, minimun) values else None if not defined,
50 "allowed": array of posible values,
51 "flags": attributes flags,
52 "validation_function": validation function for the attribute
53 "category": category for the attribute
56 raise NotImplementedError
60 """ dictionary of dictionaries of all available traces.
66 raise NotImplementedError
69 def create_order(self):
70 """ list of factory ids that indicates the order in which the elements
71 should be instantiated.
73 raise NotImplementedError
76 def configure_order(self):
77 """ list of factory ids that indicates the order in which the elements
80 raise NotImplementedError
83 def preconfigure_order(self):
84 """ list of factory ids that indicates the order in which the elements
85 should be preconfigured.
87 Default: same as configure_order
89 return self.configure_order
92 def prestart_order(self):
93 """ list of factory ids that indicates the order in which the elements
94 should be prestart-configured.
96 Default: same as configure_order
98 return self.configure_order
101 def start_order(self):
102 """ list of factory ids that indicates the order in which the elements
105 Default: same as configure_order
107 return self.configure_order
110 def factories_info(self):
111 """ dictionary of dictionaries of factory specific information
113 "allow_addresses": whether the box allows adding IP addresses,
114 "allow_routes": wether the box allows adding routes,
115 "has_addresses": whether the box allows obtaining IP addresses,
116 "has_routes": wether the box allows obtaining routes,
118 "category": category the element belongs to,
119 "create_function": function for element instantiation,
120 "start_function": function for element starting,
121 "stop_function": function for element stoping,
122 "status_function": function for retrieving element status,
123 "preconfigure_function": function for element preconfiguration,
124 (just after connections are made,
125 just before netrefs are resolved)
126 "configure_function": function for element configuration,
127 "prestart_function": function for pre-start
128 element configuration (just before starting applications),
129 useful for synchronization of background setup tasks or
130 lazy instantiation or configuration of attributes
131 that require connection/cross-connection state before
133 After this point, all applications should be able to run.
134 "factory_attributes": list of references to attribute_ids,
135 "box_attributes": list of regerences to attribute_ids,
136 "traces": list of references to trace_id
137 "tags": list of references to tag_id
138 "connector_types": list of references to connector_types
141 raise NotImplementedError
144 def testbed_attributes(self):
145 """ dictionary of attributes for testbed instance configuration
146 attributes_id = dict({
147 "name": attribute name,
149 "type": attribute type,
150 "value": default attribute value,
151 "range": (maximum, minimun) values else None if not defined,
152 "allowed": array of posible values,
153 "flags": attributes flags,
154 "validation_function": validation function for the attribute
155 "category": category for the attribute
159 raise NotImplementedError
161 class Metadata(object):
162 STANDARD_BOX_ATTRIBUTES = (
165 validation_function = validation.is_string,
166 type = Attribute.STRING,
167 flags = Attribute.DesignOnly,
168 help = "A unique identifier for referring to this box",
172 # Shorthand for DeploymentConfiguration
173 # Syntactic sugar to shorten stuff
174 DC = DeploymentConfiguration
176 STANDARD_TESTBED_ATTRIBUTES = (
177 ("home_directory", dict(
178 name = "homeDirectory",
179 validation_function = validation.is_string,
180 help = "Path to the directory where traces and other files will be stored",
181 type = Attribute.STRING,
183 flags = Attribute.DesignOnly
187 validation_function = validation.is_string,
188 type = Attribute.STRING,
189 flags = Attribute.DesignOnly,
190 help = "A unique identifier for referring to this testbed",
194 DEPLOYMENT_ATTRIBUTES = (
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 = CATEGORY_DEPLOYMENT,
204 (DC.DEPLOYMENT_MODE, dict(name = DC.DEPLOYMENT_MODE,
205 help = "Instance execution mode",
206 type = Attribute.ENUM,
207 value = DC.MODE_SINGLE_PROCESS,
210 DC.MODE_SINGLE_PROCESS
212 flags = Attribute.DesignOnly,
213 validation_function = validation.is_enum,
214 category = CATEGORY_DEPLOYMENT,
216 (DC.DEPLOYMENT_COMMUNICATION, dict(name = DC.DEPLOYMENT_COMMUNICATION,
217 help = "Instance communication mode",
218 type = Attribute.ENUM,
219 value = DC.ACCESS_LOCAL,
224 flags = Attribute.DesignOnly,
225 validation_function = validation.is_enum,
226 category = CATEGORY_DEPLOYMENT,
228 (DC.DEPLOYMENT_HOST, dict(name = DC.DEPLOYMENT_HOST,
229 help = "Host where the testbed will be executed",
230 type = Attribute.STRING,
232 flags = Attribute.DesignOnly,
233 validation_function = validation.is_string,
234 category = CATEGORY_DEPLOYMENT,
236 (DC.DEPLOYMENT_USER, dict(name = DC.DEPLOYMENT_USER,
237 help = "User on the Host to execute the testbed",
238 type = Attribute.STRING,
239 value = getpass.getuser(),
240 flags = Attribute.DesignOnly,
241 validation_function = validation.is_string,
242 category = CATEGORY_DEPLOYMENT,
244 (DC.DEPLOYMENT_KEY, dict(name = DC.DEPLOYMENT_KEY,
245 help = "Path to SSH key to use for connecting",
246 type = Attribute.STRING,
247 flags = Attribute.DesignOnly,
248 validation_function = validation.is_string,
249 category = CATEGORY_DEPLOYMENT,
251 (DC.DEPLOYMENT_PORT, dict(name = DC.DEPLOYMENT_PORT,
252 help = "Port on the Host",
253 type = Attribute.INTEGER,
255 flags = Attribute.DesignOnly,
256 validation_function = validation.is_integer,
257 category = CATEGORY_DEPLOYMENT,
259 (DC.ROOT_DIRECTORY, dict(name = DC.ROOT_DIRECTORY,
260 help = "Root directory for storing process files",
261 type = Attribute.STRING,
263 flags = Attribute.DesignOnly,
264 validation_function = validation.is_string, # TODO: validation.is_path
265 category = CATEGORY_DEPLOYMENT,
267 (DC.USE_AGENT, dict(name = DC.USE_AGENT,
268 help = "Use -A option for forwarding of the authentication agent, if ssh access is used",
269 type = Attribute.BOOL,
271 flags = Attribute.DesignOnly,
272 validation_function = validation.is_bool,
273 category = CATEGORY_DEPLOYMENT,
275 (DC.LOG_LEVEL, dict(name = DC.LOG_LEVEL,
276 help = "Log level for instance",
277 type = Attribute.ENUM,
278 value = DC.ERROR_LEVEL,
283 flags = Attribute.DesignOnly,
284 validation_function = validation.is_enum,
285 category = CATEGORY_DEPLOYMENT,
287 (DC.RECOVER, dict(name = DC.RECOVER,
288 help = "Do not intantiate testbeds, rather, reconnect to already-running instances. Used to recover from a dead controller.",
289 type = Attribute.BOOL,
291 flags = Attribute.DesignOnly,
292 validation_function = validation.is_bool,
293 category = CATEGORY_DEPLOYMENT,
297 STANDARD_TESTBED_ATTRIBUTES += DEPLOYMENT_ATTRIBUTES
302 STANDARD_ATTRIBUTE_BUNDLES = {
305 "help": "TUNneling protocol used",
306 "type": Attribute.STRING,
307 "flags": Attribute.Invisible,
308 "validation_function": validation.is_string,
312 "help": "Randomly selected TUNneling protocol cryptographic key. "
313 "Endpoints must agree to use the minimum (in lexicographic order) "
314 "of both the remote and local sides.",
315 "type": Attribute.STRING,
316 "flags": Attribute.Invisible,
317 "validation_function": validation.is_string,
321 "help": "Address (IP, unix socket, whatever) of the tunnel endpoint",
322 "type": Attribute.STRING,
323 "flags": Attribute.Invisible,
324 "validation_function": validation.is_string,
328 "help": "IP port of the tunnel endpoint",
329 "type": Attribute.INTEGER,
330 "flags": Attribute.Invisible,
331 "validation_function": validation.is_integer,
333 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP : dict({
334 "name": ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
335 "help": "Commands to set up the environment needed to run NEPI testbeds",
336 "type": Attribute.STRING,
337 "flags": Attribute.Invisible,
338 "validation_function": validation.is_string
343 def __init__(self, testbed_id, version):
344 self._version = version
345 self._testbed_id = testbed_id
346 metadata_module = self._load_versioned_metadata_module()
347 self._metadata = metadata_module.VersionedMetadataInfo()
350 def create_order(self):
351 return self._metadata.create_order
354 def configure_order(self):
355 return self._metadata.configure_order
358 def preconfigure_order(self):
359 return self._metadata.preconfigure_order
362 def prestart_order(self):
363 return self._metadata.prestart_order
366 def start_order(self):
367 return self._metadata.start_order
369 def testbed_attributes(self):
370 attributes = AttributesMap()
372 # standard attributes
373 self._add_standard_attributes(attributes, None, True, False,
374 self.STANDARD_TESTBED_ATTRIBUTES)
376 # custom attributes - they override standard ones
377 for attr_info in self._metadata.testbed_attributes.values():
378 name = attr_info["name"]
379 help = attr_info["help"]
380 type = attr_info["type"]
381 value = attr_info["value"] if "value" in attr_info else None
382 range = attr_info["range"] if "range" in attr_info else None
383 allowed = attr_info["allowed"] if "allowed" in attr_info else None
384 flags = attr_info["flags"] if "flags" in attr_info \
385 else Attribute.NoFlags
386 validation_function = attr_info["validation_function"]
387 category = attr_info["category"] if "category" in attr_info else None
388 attributes.add_attribute(name, help, type, value,
389 range, allowed, flags, validation_function, category)
393 def build_design_factories(self):
394 from nepi.core.design import Factory
396 for factory_id, info in self._metadata.factories_info.iteritems():
398 category = info["category"]
399 allow_addresses = info.get("allow_addresses", False)
400 allow_routes = info.get("allow_routes", False)
401 has_addresses = info.get("has_addresses", False)
402 has_routes = info.get("has_routes", False)
403 factory = Factory(factory_id,
404 allow_addresses, has_addresses,
405 allow_routes, has_routes,
408 # standard attributes
409 self._add_standard_attributes(factory, info, True, True,
410 self.STANDARD_BOX_ATTRIBUTES)
412 # custom attributes - they override standard ones
413 self._add_attributes(factory, info, "factory_attributes")
414 self._add_attributes(factory, info, "box_attributes", True)
416 self._add_design_traces(factory, info)
417 self._add_tags(factory, info)
418 self._add_design_connector_types(factory, info)
419 factories.append(factory)
422 def build_execute_factories(self):
423 from nepi.core.execute import Factory
425 for factory_id, info in self._metadata.factories_info.iteritems():
426 create_function = info.get("create_function")
427 start_function = info.get("start_function")
428 stop_function = info.get("stop_function")
429 status_function = info.get("status_function")
430 configure_function = info.get("configure_function")
431 preconfigure_function = info.get("preconfigure_function")
432 prestart_function = info.get("prestart_function")
433 allow_addresses = info.get("allow_addresses", False)
434 allow_routes = info.get("allow_routes", False)
435 has_addresses = info.get("has_addresses", False)
436 has_routes = info.get("has_routes", False)
437 factory = Factory(factory_id, create_function, start_function,
438 stop_function, status_function,
439 configure_function, preconfigure_function,
441 allow_addresses, has_addresses,
442 allow_routes, has_routes)
444 # standard attributes
445 self._add_standard_attributes(factory, info, False, True,
446 self.STANDARD_BOX_ATTRIBUTES)
448 # custom attributes - they override standard ones
449 self._add_attributes(factory, info, "factory_attributes")
450 self._add_attributes(factory, info, "box_attributes", True)
452 self._add_execute_traces(factory, info)
453 self._add_tags(factory, info)
454 self._add_execute_connector_types(factory, info)
455 factories.append(factory)
458 def _load_versioned_metadata_module(self):
459 mod_name = "nepi.testbeds.%s.metadata_v%s" % (self._testbed_id.lower(),
461 if not mod_name in sys.modules:
463 return sys.modules[mod_name]
465 def _add_standard_attributes(self, factory, info, design, box, STANDARD_ATTRIBUTES):
467 attr_bundle = STANDARD_ATTRIBUTES
469 # Only add non-DesignOnly attributes
470 def nonDesign(attr_info):
471 return not (attr_info[1].get('flags',Attribute.NoFlags) & Attribute.DesignOnly)
472 attr_bundle = filter(nonDesign, STANDARD_ATTRIBUTES)
473 self._add_attributes(factory, info, None, box,
474 attr_bundle = STANDARD_ATTRIBUTES)
476 def _add_attributes(self, factory, info, attr_key, box_attributes = False, attr_bundle = ()):
477 if not attr_bundle and info and attr_key in info:
478 definitions = self.STANDARD_ATTRIBUTE_BUNDLES.copy()
479 definitions.update(self._metadata.attributes)
480 attr_bundle = [ (attr_id, definitions[attr_id])
481 for attr_id in info[attr_key] ]
482 for attr_id, attr_info in attr_bundle:
483 name = attr_info["name"]
484 help = attr_info["help"]
485 type = attr_info["type"]
486 value = attr_info["value"] if "value" in attr_info else None
487 range = attr_info["range"] if "range" in attr_info else None
488 allowed = attr_info["allowed"] if "allowed" in attr_info \
490 flags = attr_info["flags"] if "flags" in attr_info \
491 and attr_info["flags"] != None \
492 else Attribute.NoFlags
493 validation_function = attr_info["validation_function"]
494 category = attr_info["category"] if "category" in attr_info else None
496 factory.add_box_attribute(name, help, type, value, range,
497 allowed, flags, validation_function, category)
499 factory.add_attribute(name, help, type, value, range,
500 allowed, flags, validation_function, category)
502 def _add_design_traces(self, factory, info):
504 for trace in info["traces"]:
505 trace_info = self._metadata.traces[trace]
506 trace_id = trace_info["name"]
507 help = trace_info["help"]
508 factory.add_trace(trace_id, help)
510 def _add_execute_traces(self, factory, info):
512 for trace in info["traces"]:
513 trace_info = self._metadata.traces[trace]
514 trace_id = trace_info["name"]
515 factory.add_trace(trace_id)
517 def _add_tags(self, factory, info):
519 for tag_id in info["tags"]:
520 factory.add_tag(tag_id)
522 def _add_design_connector_types(self, factory, info):
523 from nepi.core.design import ConnectorType
524 if "connector_types" in info:
526 for connection in self._metadata.connections:
527 from_ = connection["from"]
528 to = connection["to"]
529 can_cross = connection["can_cross"]
530 if from_ not in connections:
531 connections[from_] = list()
532 if to not in connections:
533 connections[to] = list()
534 connections[from_].append((to, can_cross))
535 connections[to].append((from_, can_cross))
536 for connector_id in info["connector_types"]:
537 connector_type_info = self._metadata.connector_types[
539 name = connector_type_info["name"]
540 help = connector_type_info["help"]
541 max = connector_type_info["max"]
542 min = connector_type_info["min"]
543 testbed_id = self._testbed_id
544 factory_id = factory.factory_id
545 connector_type = ConnectorType(testbed_id, factory_id, name,
547 for (to, can_cross) in connections[(testbed_id, factory_id,
549 (testbed_id_to, factory_id_to, name_to) = to
550 connector_type.add_allowed_connection(testbed_id_to,
551 factory_id_to, name_to, can_cross)
552 factory.add_connector_type(connector_type)
554 def _add_execute_connector_types(self, factory, info):
555 from nepi.core.execute import ConnectorType
556 if "connector_types" in info:
557 from_connections = dict()
558 to_connections = dict()
559 for connection in self._metadata.connections:
560 from_ = connection["from"]
561 to = connection["to"]
562 can_cross = connection["can_cross"]
563 init_code = connection["init_code"] \
564 if "init_code" in connection else None
565 compl_code = connection["compl_code"] \
566 if "compl_code" in connection else None
567 if from_ not in from_connections:
568 from_connections[from_] = list()
569 if to not in to_connections:
570 to_connections[to] = list()
571 from_connections[from_].append((to, can_cross, init_code,
573 to_connections[to].append((from_, can_cross, init_code,
575 for connector_id in info["connector_types"]:
576 connector_type_info = self._metadata.connector_types[
578 name = connector_type_info["name"]
579 max = connector_type_info["max"]
580 min = connector_type_info["min"]
581 testbed_id = self._testbed_id
582 factory_id = factory.factory_id
583 connector_type = ConnectorType(testbed_id, factory_id, name,
585 connector_key = (testbed_id, factory_id, name)
586 if connector_key in to_connections:
587 for (from_, can_cross, init_code, compl_code) in \
588 to_connections[connector_key]:
589 (testbed_id_from, factory_id_from, name_from) = from_
590 connector_type.add_from_connection(testbed_id_from,
591 factory_id_from, name_from, can_cross,
592 init_code, compl_code)
593 if connector_key in from_connections:
594 for (to, can_cross, init_code, compl_code) in \
595 from_connections[(testbed_id, factory_id, name)]:
596 (testbed_id_to, factory_id_to, name_to) = to
597 connector_type.add_to_connection(testbed_id_to,
598 factory_id_to, name_to, can_cross, init_code,
600 factory.add_connector_type(connector_type)