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 tags, validation
10 from nepi.util.constants import ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP, \
11 DeploymentConfiguration as DC, \
12 AttributeCategories as AC
14 class MetadataInfo(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
115 "category": category the element belongs to,
116 "create_function": function for element instantiation,
117 "start_function": function for element starting,
118 "stop_function": function for element stoping,
119 "status_function": function for retrieving element status,
120 "preconfigure_function": function for element preconfiguration,
121 (just after connections are made,
122 just before netrefs are resolved)
123 "configure_function": function for element configuration,
124 "prestart_function": function for pre-start
125 element configuration (just before starting applications),
126 useful for synchronization of background setup tasks or
127 lazy instantiation or configuration of attributes
128 that require connection/cross-connection state before
130 After this point, all applications should be able to run.
131 "factory_attributes": list of references to attribute_ids,
132 "box_attributes": list of regerences to attribute_ids,
133 "traces": list of references to trace_id
134 "tags": list of references to tag_id
135 "connector_types": list of references to connector_types
138 raise NotImplementedError
141 def testbed_attributes(self):
142 """ dictionary of attributes for testbed instance configuration
143 attributes_id = dict({
144 "name": attribute name,
146 "type": attribute type,
147 "value": default attribute value,
148 "range": (maximum, minimun) values else None if not defined,
149 "allowed": array of posible values,
150 "flags": attributes flags,
151 "validation_function": validation function for the attribute
152 "category": category for the attribute
156 raise NotImplementedError
159 def testbed_id(self):
160 """ ID for the testbed """
161 raise NotImplementedError
164 def testbed_version(self):
165 """ version for the testbed """
166 raise NotImplementedError
168 class Metadata(object):
169 # These attributes should be added to all boxes
170 STANDARD_BOX_ATTRIBUTES = dict({
173 "validation_function" : validation.is_string,
174 "type" : Attribute.STRING,
175 "flags" : Attribute.ExecReadOnly |\
176 Attribute.ExecImmutable |\
178 "help" : "A unique identifier for referring to this box",
182 # These are the attribute definitions for tagged attributes
183 STANDARD_TAGGED_ATTRIBUTES_DEFINITIONS = dict({
184 "maxAddresses" : dict({
185 "name" : "maxAddresses",
186 "validation_function" : validation.is_integer,
187 "type" : Attribute.INTEGER,
189 "flags" : Attribute.DesignReadOnly |\
190 Attribute.ExecInvisible |\
192 "help" : "The maximum allowed number of addresses",
196 # Attributes to be added to all boxes with specific tags
197 STANDARD_TAGGED_BOX_ATTRIBUTES = dict({
198 tags.ALLOW_ADDRESSES : ["maxAddresses"],
199 tags.HAS_ADDRESSES : ["maxAddresses"],
202 # These attributes should be added to all testbeds
203 STANDARD_TESTBED_ATTRIBUTES = dict({
204 "home_directory" : dict({
205 "name" : "homeDirectory",
206 "validation_function" : validation.is_string,
207 "help" : "Path to the directory where traces and other files will be stored",
208 "type" : Attribute.STRING,
210 "flags" : Attribute.ExecReadOnly |\
211 Attribute.ExecImmutable |\
216 "validation_function" : validation.is_string,
217 "type" : Attribute.STRING,
218 "flags" : Attribute.ExecReadOnly |\
219 Attribute.ExecImmutable |\
221 "help" : "A unique identifier for referring to this testbed",
225 # These attributes should be added to all testbeds
226 DEPLOYMENT_ATTRIBUTES = dict({
227 # TESTBED DEPLOYMENT ATTRIBUTES
228 DC.DEPLOYMENT_ENVIRONMENT_SETUP : dict({
229 "name" : DC.DEPLOYMENT_ENVIRONMENT_SETUP,
230 "validation_function" : validation.is_string,
231 "help" : "Shell commands to run before spawning TestbedController processes",
232 "type" : Attribute.STRING,
233 "flags" : Attribute.ExecReadOnly |\
234 Attribute.ExecImmutable |\
236 "category" : AC.CATEGORY_DEPLOYMENT,
238 DC.DEPLOYMENT_MODE: dict({
239 "name" : DC.DEPLOYMENT_MODE,
240 "help" : "Instance execution mode",
241 "type" : Attribute.ENUM,
242 "value" : DC.MODE_SINGLE_PROCESS,
245 DC.MODE_SINGLE_PROCESS
247 "flags" : Attribute.ExecReadOnly |\
248 Attribute.ExecImmutable |\
250 "validation_function" : validation.is_enum,
251 "category" : AC.CATEGORY_DEPLOYMENT,
253 DC.DEPLOYMENT_COMMUNICATION : dict({
254 "name" : DC.DEPLOYMENT_COMMUNICATION,
255 "help" : "Instance communication mode",
256 "type" : Attribute.ENUM,
257 "value" : DC.ACCESS_LOCAL,
262 "flags" : Attribute.ExecReadOnly |\
263 Attribute.ExecImmutable |\
265 "validation_function" : validation.is_enum,
266 "category" : AC.CATEGORY_DEPLOYMENT,
268 DC.DEPLOYMENT_HOST : dict({
269 "name" : DC.DEPLOYMENT_HOST,
270 "help" : "Host where the testbed will be executed",
271 "type" : Attribute.STRING,
272 "value" : "localhost",
273 "flags" : Attribute.ExecReadOnly |\
274 Attribute.ExecImmutable |\
276 "validation_function" : validation.is_string,
277 "category" : AC.CATEGORY_DEPLOYMENT,
279 DC.DEPLOYMENT_USER : dict({
280 "name" : DC.DEPLOYMENT_USER,
281 "help" : "User on the Host to execute the testbed",
282 "type" : Attribute.STRING,
283 "value" : getpass.getuser(),
284 "flags" : Attribute.ExecReadOnly |\
285 Attribute.ExecImmutable |\
287 "validation_function" : validation.is_string,
288 "category" : AC.CATEGORY_DEPLOYMENT,
290 DC.DEPLOYMENT_KEY : dict({
291 "name" : DC.DEPLOYMENT_KEY,
292 "help" : "Path to SSH key to use for connecting",
293 "type" : Attribute.STRING,
294 "flags" : Attribute.ExecReadOnly |\
295 Attribute.ExecImmutable |\
297 "validation_function" : validation.is_string,
298 "category" : AC.CATEGORY_DEPLOYMENT,
300 DC.DEPLOYMENT_PORT : dict({
301 "name" : DC.DEPLOYMENT_PORT,
302 "help" : "Port on the Host",
303 "type" : Attribute.INTEGER,
305 "flags" : Attribute.ExecReadOnly |\
306 Attribute.ExecImmutable |\
308 "validation_function" : validation.is_integer,
309 "category" : AC.CATEGORY_DEPLOYMENT,
311 DC.ROOT_DIRECTORY : dict({
312 "name" : DC.ROOT_DIRECTORY,
313 "help" : "Root directory for storing process files",
314 "type" : Attribute.STRING,
316 "flags" : Attribute.ExecReadOnly |\
317 Attribute.ExecImmutable |\
319 "validation_function" : validation.is_string, # TODO: validation.is_path
320 "category" : AC.CATEGORY_DEPLOYMENT,
322 DC.USE_AGENT : dict({
323 "name" : DC.USE_AGENT,
324 "help" : "Use -A option for forwarding of the authentication agent, if ssh access is used",
325 "type" : Attribute.BOOL,
327 "flags" : Attribute.ExecReadOnly |\
328 Attribute.ExecImmutable |\
330 "validation_function" : validation.is_bool,
331 "category" : AC.CATEGORY_DEPLOYMENT,
333 DC.LOG_LEVEL : dict({
334 "name" : DC.LOG_LEVEL,
335 "help" : "Log level for instance",
336 "type" : Attribute.ENUM,
337 "value" : DC.ERROR_LEVEL,
342 "flags" : Attribute.ExecReadOnly |\
343 Attribute.ExecImmutable |\
345 "validation_function" : validation.is_enum,
346 "category" : AC.CATEGORY_DEPLOYMENT,
350 "help" : "Do not intantiate testbeds, rather, reconnect to already-running instances. Used to recover from a dead controller.",
351 "type" : Attribute.BOOL,
353 "flags" : Attribute.ExecReadOnly |\
354 Attribute.ExecImmutable |\
356 "validation_function" : validation.is_bool,
357 "category" : AC.CATEGORY_DEPLOYMENT,
361 # These attributes could appear in the boxes attribute list
362 STANDARD_BOX_ATTRIBUTE_DEFINITIONS = dict({
364 "name" : "tun_proto",
365 "help" : "TUNneling protocol used",
366 "type" : Attribute.STRING,
367 "flags" : Attribute.DesignInvisible | \
368 Attribute.ExecInvisible | \
369 Attribute.ExecImmutable | \
371 "validation_function" : validation.is_string,
375 "help" : "Randomly selected TUNneling protocol cryptographic key. "
376 "Endpoints must agree to use the minimum (in lexicographic order) "
377 "of both the remote and local sides.",
378 "type" : Attribute.STRING,
379 "flags" : Attribute.DesignInvisible | \
380 Attribute.ExecInvisible | \
381 Attribute.ExecImmutable | \
383 "validation_function" : validation.is_string,
387 "help" : "Address (IP, unix socket, whatever) of the tunnel endpoint",
388 "type" : Attribute.STRING,
389 "flags" : Attribute.DesignInvisible | \
390 Attribute.ExecInvisible | \
391 Attribute.ExecImmutable | \
393 "validation_function" : validation.is_string,
397 "help" : "IP port of the tunnel endpoint",
398 "type" : Attribute.INTEGER,
399 "flags" : Attribute.DesignInvisible | \
400 Attribute.ExecInvisible | \
401 Attribute.ExecImmutable | \
403 "validation_function" : validation.is_integer,
405 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP : dict({
406 "name" : ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
407 "help" : "Commands to set up the environment needed to run NEPI testbeds",
408 "type" : Attribute.STRING,
409 "flags" : Attribute.DesignInvisible | \
410 Attribute.ExecInvisible | \
411 Attribute.ExecImmutable | \
413 "validation_function" : validation.is_string
417 STANDARD_TESTBED_ATTRIBUTES.update(DEPLOYMENT_ATTRIBUTES.copy())
419 def __init__(self, testbed_id):
420 self._testbed_id = testbed_id
421 metadata_module = self._load_metadata_module()
422 self._metadata = metadata_module.MetadataInfo()
423 if testbed_id != self._metadata.testbed_id:
424 raise RuntimeError("Bad testbed id. Asked for %s, got %s" % \
425 (testbed_id, self._metadata.testbed_id ))
428 def create_order(self):
429 return self._metadata.create_order
432 def configure_order(self):
433 return self._metadata.configure_order
436 def preconfigure_order(self):
437 return self._metadata.preconfigure_order
440 def prestart_order(self):
441 return self._metadata.prestart_order
444 def start_order(self):
445 return self._metadata.start_order
448 def testbed_version(self):
449 return self._metadata.testbed_version
452 def testbed_id(self):
453 return self._testbed_id
455 def testbed_attributes(self):
456 attributes = AttributesMap()
457 testbed_attributes = self._testbed_attributes()
458 self._add_attributes(attributes.add_attribute, testbed_attributes)
461 def build_factories(self):
463 for factory_id, info in self._metadata.factories_info.iteritems():
464 create_function = info.get("create_function")
465 start_function = info.get("start_function")
466 stop_function = info.get("stop_function")
467 status_function = info.get("status_function")
468 configure_function = info.get("configure_function")
469 preconfigure_function = info.get("preconfigure_function")
470 prestart_function = info.get("prestart_function")
472 category = info["category"]
473 factory = Factory(factory_id,
479 preconfigure_function,
484 factory_attributes = self._factory_attributes(info)
485 self._add_attributes(factory.add_attribute, factory_attributes)
486 box_attributes = self._box_attributes(info)
487 self._add_attributes(factory.add_box_attribute, box_attributes)
489 self._add_traces(factory, info)
490 self._add_tags(factory, info)
491 self._add_connector_types(factory, info)
492 factories.append(factory)
495 def _load_metadata_module(self):
496 mod_name = "nepi.testbeds.%s.metadata" % (self._testbed_id.lower())
497 if not mod_name in sys.modules:
499 return sys.modules[mod_name]
501 def _testbed_attributes(self):
503 attributes = self.STANDARD_TESTBED_ATTRIBUTES.copy()
505 attributes.update(self._metadata.testbed_attributes.copy())
508 def _factory_attributes(self, info):
509 tagged_attributes = self._tagged_attributes(info)
510 if "factory_attributes" in info:
511 definitions = self._metadata.attributes.copy()
512 # filter attributes corresponding to the factory_id
513 factory_attributes = self._filter_attributes(info["factory_attributes"],
516 factory_attributes = dict()
517 attributes = dict(tagged_attributes.items() + \
518 factory_attributes.items())
521 def _box_attributes(self, info):
522 tagged_attributes = self._tagged_attributes(info)
523 if "box_attributes" in info:
524 definitions = self.STANDARD_BOX_ATTRIBUTE_DEFINITIONS.copy()
525 definitions.update(self._metadata.attributes)
526 box_attributes = self._filter_attributes(info["box_attributes"],
529 box_attributes = dict()
530 attributes = dict(tagged_attributes.items() + \
531 box_attributes.items())
532 attributes.update(self.STANDARD_BOX_ATTRIBUTES.copy())
535 def _tagged_attributes(self, info):
536 tagged_attributes = dict()
537 for tag_id in info.get("tags", []):
538 if tag_id in self.STANDARD_TAGGED_BOX_ATTRIBUTES:
539 attr_list = self.STANDARD_TAGGED_BOX_ATTRIBUTES[tag_id]
540 attributes = self._filter_attributes(attr_list,
541 self.STANDARD_TAGGED_ATTRIBUTES_DEFINITIONS)
542 tagged_attributes.update(attributes)
543 return tagged_attributes
545 def _filter_attributes(self, attr_list, definitions):
546 # filter attributes not corresponding to the factory
547 attributes = dict((attr_id, definitions[attr_id]) \
548 for attr_id in attr_list)
551 def _add_attributes(self, add_attr_func, attributes):
552 for attr_id, attr_info in attributes.iteritems():
553 name = attr_info["name"]
554 help = attr_info["help"]
555 type = attr_info["type"]
556 value = attr_info.get("value")
557 range = attr_info.get("range")
558 allowed = attr_info.get("allowed")
559 flags = attr_info.get("flags")
560 validation_function = attr_info["validation_function"]
561 category = attr_info.get("category")
562 add_attr_func(name, help, type, value, range, allowed, flags,
563 validation_function, category)
565 def _add_traces(self, factory, info):
566 for trace_id in info.get("traces", []):
567 trace_info = self._metadata.traces[trace_id]
568 name = trace_info["name"]
569 help = trace_info["help"]
570 factory.add_trace(name, help)
572 def _add_tags(self, factory, info):
573 for tag_id in info.get("tags", []):
574 factory.add_tag(tag_id)
576 def _add_connector_types(self, factory, info):
577 if "connector_types" in info:
578 from_connections = dict()
579 to_connections = dict()
580 for connection in self._metadata.connections:
581 from_ = connection["from"]
582 to = connection["to"]
583 can_cross = connection["can_cross"]
584 init_code = connection.get("init_code")
585 compl_code = connection.get("compl_code")
586 if from_ not in from_connections:
587 from_connections[from_] = list()
588 if to not in to_connections:
589 to_connections[to] = list()
590 from_connections[from_].append((to, can_cross, init_code,
592 to_connections[to].append((from_, can_cross, init_code,
594 for connector_id in info["connector_types"]:
595 connector_type_info = self._metadata.connector_types[
597 name = connector_type_info["name"]
598 help = connector_type_info["help"]
599 max = connector_type_info["max"]
600 min = connector_type_info["min"]
601 testbed_id = self._testbed_id
602 factory_id = factory.factory_id
603 connector_type = ConnectorType(testbed_id, factory_id, name,
605 connector_key = (testbed_id, factory_id, name)
606 if connector_key in to_connections:
607 for (from_, can_cross, init_code, compl_code) in \
608 to_connections[connector_key]:
609 (testbed_id_from, factory_id_from, name_from) = from_
610 connector_type.add_from_connection(testbed_id_from,
611 factory_id_from, name_from, can_cross,
612 init_code, compl_code)
613 if connector_key in from_connections:
614 for (to, can_cross, init_code, compl_code) in \
615 from_connections[(testbed_id, factory_id, name)]:
616 (testbed_id_to, factory_id_to, name_to) = to
617 connector_type.add_to_connection(testbed_id_to,
618 factory_id_to, name_to, can_cross, init_code,
620 factory.add_connector_type(connector_type)