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 class VersionedMetadataInfo(object):
12 def connector_types(self):
13 """ dictionary of dictionaries with allowed connection information.
16 "name": connector type name,
17 "max": maximum number of connections allowed (-1 for no limit),
18 "min": minimum number of connections allowed
21 raise NotImplementedError
24 def connections(self):
25 """ array of dictionaries with allowed connection information.
27 "from": (testbed_id1, factory_id1, connector_type_name1),
28 "to": (testbed_id2, factory_id2, connector_type_name2),
29 "init_code": connection function to invoke for connection initiation
30 "compl_code": connection function to invoke for connection
32 "can_cross": whether the connection can be done across testbed
36 raise NotImplementedError
40 """ dictionary of dictionaries of all available attributes.
42 "name": attribute name,
44 "type": attribute type,
45 "value": default attribute value,
46 "range": (maximum, minimun) values else None if not defined,
47 "allowed": array of posible values,
48 "flags": attributes flags,
49 "validation_function": validation function for the attribute
52 raise NotImplementedError
56 """ dictionary of dictionaries of all available traces.
62 raise NotImplementedError
65 def create_order(self):
66 """ list of factory ids that indicates the order in which the elements
67 should be instantiated.
69 raise NotImplementedError
72 def configure_order(self):
73 """ list of factory ids that indicates the order in which the elements
76 raise NotImplementedError
79 def preconfigure_order(self):
80 """ list of factory ids that indicates the order in which the elements
81 should be preconfigured.
83 Default: same as configure_order
85 return self.configure_order
88 def factories_info(self):
89 """ dictionary of dictionaries of factory specific information
91 "allow_addresses": whether the box allows adding IP addresses,
92 "allow_routes": wether the box allows adding routes,
93 "has_addresses": whether the box allows obtaining IP addresses,
94 "has_routes": wether the box allows obtaining routes,
96 "category": category the element belongs to,
97 "create_function": function for element instantiation,
98 "start_function": function for element starting,
99 "stop_function": function for element stoping,
100 "status_function": function for retrieving element status,
101 "preconfigure_function": function for element preconfiguration,
102 (just after connections are made,
103 just before netrefs are resolved)
104 "configure_function": function for element configuration,
105 "factory_attributes": list of references to attribute_ids,
106 "box_attributes": list of regerences to attribute_ids,
107 "traces": list of references to trace_id
108 "connector_types": list of references to connector_types
111 raise NotImplementedError
114 def testbed_attributes(self):
115 """ dictionary of attributes for testbed instance configuration
116 attributes_id = dict({
117 "name": attribute name,
119 "type": attribute type,
120 "value": default attribute value,
121 "range": (maximum, minimun) values else None if not defined,
122 "allowed": array of posible values,
123 "flags": attributes flags,
124 "validation_function": validation function for the attribute
128 raise NotImplementedError
130 class Metadata(object):
131 STANDARD_BOX_ATTRIBUTES = (
134 validation_function = validation.is_string,
135 type = Attribute.STRING,
136 flags = Attribute.DesignOnly,
137 help = "A unique identifier for referring to this box",
141 # Shorthand for DeploymentConfiguration
142 # Syntactic sugar to shorten stuff
143 DC = DeploymentConfiguration
145 STANDARD_TESTBED_ATTRIBUTES = (
146 ("home_directory", dict(
147 name = "homeDirectory",
148 validation_function = validation.is_string,
149 help = "Path to the directory where traces and other files will be stored",
150 type = Attribute.STRING,
152 flags = Attribute.DesignOnly,
156 DEPLOYMENT_ATTRIBUTES = (
157 # TESTBED DEPLOYMENT ATTRIBUTES
158 (DC.DEPLOYMENT_ENVIRONMENT_SETUP, dict(
159 name = DC.DEPLOYMENT_ENVIRONMENT_SETUP,
160 validation_function = validation.is_string,
161 help = "Shell commands to run before spawning TestbedController processes",
162 type = Attribute.STRING,
163 flags = Attribute.DesignOnly,
165 (DC.DEPLOYMENT_MODE, dict(name = DC.DEPLOYMENT_MODE,
166 help = "Instance execution mode",
167 type = Attribute.ENUM,
168 value = DC.MODE_SINGLE_PROCESS,
171 DC.MODE_SINGLE_PROCESS
173 flags = Attribute.DesignOnly,
174 validation_function = validation.is_enum
176 (DC.DEPLOYMENT_COMMUNICATION, dict(name = DC.DEPLOYMENT_COMMUNICATION,
177 help = "Instance communication mode",
178 type = Attribute.ENUM,
179 value = DC.ACCESS_LOCAL,
184 flags = Attribute.DesignOnly,
185 validation_function = validation.is_enum
187 (DC.DEPLOYMENT_HOST, dict(name = DC.DEPLOYMENT_HOST,
188 help = "Host where the testbed will be executed",
189 type = Attribute.STRING,
191 flags = Attribute.DesignOnly,
192 validation_function = validation.is_string
194 (DC.DEPLOYMENT_USER, dict(name = DC.DEPLOYMENT_USER,
195 help = "User on the Host to execute the testbed",
196 type = Attribute.STRING,
197 value = getpass.getuser(),
198 flags = Attribute.DesignOnly,
199 validation_function = validation.is_string
201 (DC.DEPLOYMENT_KEY, dict(name = DC.DEPLOYMENT_KEY,
202 help = "Path to SSH key to use for connecting",
203 type = Attribute.STRING,
204 flags = Attribute.DesignOnly,
205 validation_function = validation.is_string
207 (DC.DEPLOYMENT_PORT, dict(name = DC.DEPLOYMENT_PORT,
208 help = "Port on the Host",
209 type = Attribute.INTEGER,
211 flags = Attribute.DesignOnly,
212 validation_function = validation.is_integer
214 (DC.ROOT_DIRECTORY, dict(name = DC.ROOT_DIRECTORY,
215 help = "Root directory for storing process files",
216 type = Attribute.STRING,
218 flags = Attribute.DesignOnly,
219 validation_function = validation.is_string # TODO: validation.is_path
221 (DC.USE_AGENT, dict(name = DC.USE_AGENT,
222 help = "Use -A option for forwarding of the authentication agent, if ssh access is used",
223 type = Attribute.BOOL,
225 flags = Attribute.DesignOnly,
226 validation_function = validation.is_bool
228 (DC.LOG_LEVEL, dict(name = DC.LOG_LEVEL,
229 help = "Log level for instance",
230 type = Attribute.ENUM,
231 value = DC.ERROR_LEVEL,
236 flags = Attribute.DesignOnly,
237 validation_function = validation.is_enum
239 (DC.RECOVER, dict(name = DC.RECOVER,
240 help = "Do not intantiate testbeds, rather, reconnect to already-running instances. Used to recover from a dead controller.",
241 type = Attribute.BOOL,
243 flags = Attribute.DesignOnly,
244 validation_function = validation.is_bool
248 STANDARD_TESTBED_ATTRIBUTES += DEPLOYMENT_ATTRIBUTES
253 STANDARD_ATTRIBUTE_BUNDLES = {
256 "help": "TUNneling protocol used",
257 "type": Attribute.STRING,
258 "flags": Attribute.Invisible,
259 "validation_function": validation.is_string,
263 "help": "Randomly selected TUNneling protocol cryptographic key. "
264 "Endpoints must agree to use the minimum (in lexicographic order) "
265 "of both the remote and local sides.",
266 "type": Attribute.STRING,
267 "flags": Attribute.Invisible,
268 "validation_function": validation.is_string,
272 "help": "Address (IP, unix socket, whatever) of the tunnel endpoint",
273 "type": Attribute.STRING,
274 "flags": Attribute.Invisible,
275 "validation_function": validation.is_string,
279 "help": "IP port of the tunnel endpoint",
280 "type": Attribute.INTEGER,
281 "flags": Attribute.Invisible,
282 "validation_function": validation.is_integer,
284 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP : dict({
285 "name": ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
286 "help": "Commands to set up the environment needed to run NEPI testbeds",
287 "type": Attribute.STRING,
288 "flags": Attribute.Invisible,
289 "validation_function": validation.is_string
294 def __init__(self, testbed_id, version):
295 self._version = version
296 self._testbed_id = testbed_id
297 metadata_module = self._load_versioned_metadata_module()
298 self._metadata = metadata_module.VersionedMetadataInfo()
301 def create_order(self):
302 return self._metadata.create_order
305 def configure_order(self):
306 return self._metadata.configure_order
309 def preconfigure_order(self):
310 return self._metadata.preconfigure_order
312 def testbed_attributes(self):
313 attributes = AttributesMap()
315 # standard attributes
316 self._add_standard_attributes(attributes, None, True, False,
317 self.STANDARD_TESTBED_ATTRIBUTES)
319 # custom attributes - they override standard ones
320 for attr_info in self._metadata.testbed_attributes.values():
321 name = attr_info["name"]
322 help = attr_info["help"]
323 type = attr_info["type"]
324 value = attr_info["value"] if "value" in attr_info else None
325 range = attr_info["range"] if "range" in attr_info else None
326 allowed = attr_info["allowed"] if "allowed" in attr_info else None
327 flags = attr_info["flags"] if "flags" in attr_info \
328 else Attribute.NoFlags
329 validation_function = attr_info["validation_function"]
330 attributes.add_attribute(name, help, type, value,
331 range, allowed, flags, validation_function)
335 def build_design_factories(self):
336 from nepi.core.design import Factory
338 for factory_id, info in self._metadata.factories_info.iteritems():
340 category = info["category"]
341 allow_addresses = info.get("allow_addresses", False)
342 allow_routes = info.get("allow_routes", False)
343 has_addresses = info.get("has_addresses", False)
344 has_routes = info.get("has_routes", False)
345 factory = Factory(factory_id,
346 allow_addresses, has_addresses,
347 allow_routes, has_routes,
350 # standard attributes
351 self._add_standard_attributes(factory, info, True, True,
352 self.STANDARD_BOX_ATTRIBUTES)
354 # custom attributes - they override standard ones
355 self._add_attributes(factory, info, "factory_attributes")
356 self._add_attributes(factory, info, "box_attributes", True)
358 self._add_design_traces(factory, info)
359 self._add_design_connector_types(factory, info)
360 factories.append(factory)
363 def build_execute_factories(self):
364 from nepi.core.execute import Factory
366 for factory_id, info in self._metadata.factories_info.iteritems():
367 create_function = info.get("create_function")
368 start_function = info.get("start_function")
369 stop_function = info.get("stop_function")
370 status_function = info.get("status_function")
371 configure_function = info.get("configure_function")
372 preconfigure_function = info.get("preconfigure_function")
373 allow_addresses = info.get("allow_addresses", False)
374 allow_routes = info.get("allow_routes", False)
375 has_addresses = info.get("has_addresses", False)
376 has_routes = info.get("has_routes", False)
377 factory = Factory(factory_id, create_function, start_function,
378 stop_function, status_function,
379 configure_function, preconfigure_function,
380 allow_addresses, has_addresses,
381 allow_routes, has_routes)
383 # standard attributes
384 self._add_standard_attributes(factory, info, False, True,
385 self.STANDARD_BOX_ATTRIBUTES)
387 # custom attributes - they override standard ones
388 self._add_attributes(factory, info, "factory_attributes")
389 self._add_attributes(factory, info, "box_attributes", True)
391 self._add_execute_traces(factory, info)
392 self._add_execute_connector_types(factory, info)
393 factories.append(factory)
396 def _load_versioned_metadata_module(self):
397 mod_name = "nepi.testbeds.%s.metadata_v%s" % (self._testbed_id.lower(),
399 if not mod_name in sys.modules:
401 return sys.modules[mod_name]
403 def _add_standard_attributes(self, factory, info, design, box, STANDARD_ATTRIBUTES):
405 attr_bundle = STANDARD_ATTRIBUTES
407 # Only add non-DesignOnly attributes
408 def nonDesign(attr_info):
409 return not (attr_info[1].get('flags',Attribute.NoFlags) & Attribute.DesignOnly)
410 attr_bundle = filter(nonDesign, STANDARD_ATTRIBUTES)
411 self._add_attributes(factory, info, None, box,
412 attr_bundle = STANDARD_ATTRIBUTES)
414 def _add_attributes(self, factory, info, attr_key, box_attributes = False, attr_bundle = ()):
415 if not attr_bundle and info and attr_key in info:
416 definitions = self.STANDARD_ATTRIBUTE_BUNDLES.copy()
417 definitions.update(self._metadata.attributes)
418 attr_bundle = [ (attr_id, definitions[attr_id])
419 for attr_id in info[attr_key] ]
420 for attr_id, attr_info in attr_bundle:
421 name = attr_info["name"]
422 help = attr_info["help"]
423 type = attr_info["type"]
424 value = attr_info["value"] if "value" in attr_info else None
425 range = attr_info["range"] if "range" in attr_info else None
426 allowed = attr_info["allowed"] if "allowed" in attr_info \
428 flags = attr_info["flags"] if "flags" in attr_info \
429 and attr_info["flags"] != None \
430 else Attribute.NoFlags
431 validation_function = attr_info["validation_function"]
433 factory.add_box_attribute(name, help, type, value, range,
434 allowed, flags, validation_function)
436 factory.add_attribute(name, help, type, value, range,
437 allowed, flags, validation_function)
439 def _add_design_traces(self, factory, info):
441 for trace in info["traces"]:
442 trace_info = self._metadata.traces[trace]
443 trace_id = trace_info["name"]
444 help = trace_info["help"]
445 factory.add_trace(trace_id, help)
447 def _add_execute_traces(self, factory, info):
449 for trace in info["traces"]:
450 trace_info = self._metadata.traces[trace]
451 trace_id = trace_info["name"]
452 factory.add_trace(trace_id)
454 def _add_design_connector_types(self, factory, info):
455 from nepi.core.design import ConnectorType
456 if "connector_types" in info:
458 for connection in self._metadata.connections:
459 from_ = connection["from"]
460 to = connection["to"]
461 can_cross = connection["can_cross"]
462 if from_ not in connections:
463 connections[from_] = list()
464 if to not in connections:
465 connections[to] = list()
466 connections[from_].append((to, can_cross))
467 connections[to].append((from_, can_cross))
468 for connector_id in info["connector_types"]:
469 connector_type_info = self._metadata.connector_types[
471 name = connector_type_info["name"]
472 help = connector_type_info["help"]
473 max = connector_type_info["max"]
474 min = connector_type_info["min"]
475 testbed_id = self._testbed_id
476 factory_id = factory.factory_id
477 connector_type = ConnectorType(testbed_id, factory_id, name,
479 for (to, can_cross) in connections[(testbed_id, factory_id,
481 (testbed_id_to, factory_id_to, name_to) = to
482 connector_type.add_allowed_connection(testbed_id_to,
483 factory_id_to, name_to, can_cross)
484 factory.add_connector_type(connector_type)
486 def _add_execute_connector_types(self, factory, info):
487 from nepi.core.execute import ConnectorType
488 if "connector_types" in info:
489 from_connections = dict()
490 to_connections = dict()
491 for connection in self._metadata.connections:
492 from_ = connection["from"]
493 to = connection["to"]
494 can_cross = connection["can_cross"]
495 init_code = connection["init_code"] \
496 if "init_code" in connection else None
497 compl_code = connection["compl_code"] \
498 if "compl_code" in connection else None
499 if from_ not in from_connections:
500 from_connections[from_] = list()
501 if to not in to_connections:
502 to_connections[to] = list()
503 from_connections[from_].append((to, can_cross, init_code,
505 to_connections[to].append((from_, can_cross, init_code,
507 for connector_id in info["connector_types"]:
508 connector_type_info = self._metadata.connector_types[
510 name = connector_type_info["name"]
511 max = connector_type_info["max"]
512 min = connector_type_info["min"]
513 testbed_id = self._testbed_id
514 factory_id = factory.factory_id
515 connector_type = ConnectorType(testbed_id, factory_id, name,
517 connector_key = (testbed_id, factory_id, name)
518 if connector_key in to_connections:
519 for (from_, can_cross, init_code, compl_code) in \
520 to_connections[connector_key]:
521 (testbed_id_from, factory_id_from, name_from) = from_
522 connector_type.add_from_connection(testbed_id_from,
523 factory_id_from, name_from, can_cross,
524 init_code, compl_code)
525 if connector_key in from_connections:
526 for (to, can_cross, init_code, compl_code) in \
527 from_connections[(testbed_id, factory_id, name)]:
528 (testbed_id_to, factory_id_to, name_to) = to
529 connector_type.add_to_connection(testbed_id_to,
530 factory_id_to, name_to, can_cross, init_code,
532 factory.add_connector_type(connector_type)