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 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
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
158 class Metadata(object):
159 # These attributes should be added to all boxes
160 STANDARD_BOX_ATTRIBUTES = dict({
163 "validation_function" : validation.is_string,
164 "type" : Attribute.STRING,
165 "flags" : Attribute.ExecReadOnly |\
166 Attribute.ExecImmutable |\
168 "help" : "A unique identifier for referring to this box",
172 # These are the attribute definitions for tagged attributes
173 STANDARD_TAGGED_ATTRIBUTES_DEFINITIONS = dict({
174 "maxAddresses" : dict({
175 "name" : "maxAddresses",
176 "validation_function" : validation.is_integer,
177 "type" : Attribute.INTEGER,
179 "flags" : Attribute.DesignReadOnly |\
180 Attribute.ExecInvisible |\
182 "help" : "The maximum allowed number of addresses",
186 # Attributes to be added to all boxes with specific tags
187 STANDARD_TAGGED_BOX_ATTRIBUTES = dict({
188 tags.ALLOW_ADDRESSES : ["maxAddresses"],
189 tags.HAS_ADDRESSES : ["maxAddresses"],
192 # These attributes should be added to all testbeds
193 STANDARD_TESTBED_ATTRIBUTES = dict({
194 "home_directory" : dict({
195 "name" : "homeDirectory",
196 "validation_function" : validation.is_string,
197 "help" : "Path to the directory where traces and other files will be stored",
198 "type" : Attribute.STRING,
200 "flags" : Attribute.ExecReadOnly |\
201 Attribute.ExecImmutable |\
206 "validation_function" : validation.is_string,
207 "type" : Attribute.STRING,
208 "flags" : Attribute.ExecReadOnly |\
209 Attribute.ExecImmutable |\
211 "help" : "A unique identifier for referring to this testbed",
215 # These attributes should be added to all testbeds
216 DEPLOYMENT_ATTRIBUTES = dict({
217 # TESTBED DEPLOYMENT ATTRIBUTES
218 DC.DEPLOYMENT_ENVIRONMENT_SETUP : dict({
219 "name" : DC.DEPLOYMENT_ENVIRONMENT_SETUP,
220 "validation_function" : validation.is_string,
221 "help" : "Shell commands to run before spawning TestbedController processes",
222 "type" : Attribute.STRING,
223 "flags" : Attribute.ExecReadOnly |\
224 Attribute.ExecImmutable |\
226 "category" : AC.CATEGORY_DEPLOYMENT,
228 DC.DEPLOYMENT_MODE: dict({
229 "name" : DC.DEPLOYMENT_MODE,
230 "help" : "Instance execution mode",
231 "type" : Attribute.ENUM,
232 "value" : DC.MODE_SINGLE_PROCESS,
235 DC.MODE_SINGLE_PROCESS
237 "flags" : Attribute.ExecReadOnly |\
238 Attribute.ExecImmutable |\
240 "validation_function" : validation.is_enum,
241 "category" : AC.CATEGORY_DEPLOYMENT,
243 DC.DEPLOYMENT_COMMUNICATION : dict({
244 "name" : DC.DEPLOYMENT_COMMUNICATION,
245 "help" : "Instance communication mode",
246 "type" : Attribute.ENUM,
247 "value" : DC.ACCESS_LOCAL,
252 "flags" : Attribute.ExecReadOnly |\
253 Attribute.ExecImmutable |\
255 "validation_function" : validation.is_enum,
256 "category" : AC.CATEGORY_DEPLOYMENT,
258 DC.DEPLOYMENT_HOST : dict({
259 "name" : DC.DEPLOYMENT_HOST,
260 "help" : "Host where the testbed will be executed",
261 "type" : Attribute.STRING,
262 "value" : "localhost",
263 "flags" : Attribute.ExecReadOnly |\
264 Attribute.ExecImmutable |\
266 "validation_function" : validation.is_string,
267 "category" : AC.CATEGORY_DEPLOYMENT,
269 DC.DEPLOYMENT_USER : dict({
270 "name" : DC.DEPLOYMENT_USER,
271 "help" : "User on the Host to execute the testbed",
272 "type" : Attribute.STRING,
273 "value" : getpass.getuser(),
274 "flags" : Attribute.ExecReadOnly |\
275 Attribute.ExecImmutable |\
277 "validation_function" : validation.is_string,
278 "category" : AC.CATEGORY_DEPLOYMENT,
280 DC.DEPLOYMENT_KEY : dict({
281 "name" : DC.DEPLOYMENT_KEY,
282 "help" : "Path to SSH key to use for connecting",
283 "type" : Attribute.STRING,
284 "flags" : Attribute.ExecReadOnly |\
285 Attribute.ExecImmutable |\
287 "validation_function" : validation.is_string,
288 "category" : AC.CATEGORY_DEPLOYMENT,
290 DC.DEPLOYMENT_PORT : dict({
291 "name" : DC.DEPLOYMENT_PORT,
292 "help" : "Port on the Host",
293 "type" : Attribute.INTEGER,
295 "flags" : Attribute.ExecReadOnly |\
296 Attribute.ExecImmutable |\
298 "validation_function" : validation.is_integer,
299 "category" : AC.CATEGORY_DEPLOYMENT,
301 DC.ROOT_DIRECTORY : dict({
302 "name" : DC.ROOT_DIRECTORY,
303 "help" : "Root directory for storing process files",
304 "type" : Attribute.STRING,
306 "flags" : Attribute.ExecReadOnly |\
307 Attribute.ExecImmutable |\
309 "validation_function" : validation.is_string, # TODO: validation.is_path
310 "category" : AC.CATEGORY_DEPLOYMENT,
312 DC.USE_AGENT : dict({
313 "name" : DC.USE_AGENT,
314 "help" : "Use -A option for forwarding of the authentication agent, if ssh access is used",
315 "type" : Attribute.BOOL,
317 "flags" : Attribute.ExecReadOnly |\
318 Attribute.ExecImmutable |\
320 "validation_function" : validation.is_bool,
321 "category" : AC.CATEGORY_DEPLOYMENT,
323 DC.LOG_LEVEL : dict({
324 "name" : DC.LOG_LEVEL,
325 "help" : "Log level for instance",
326 "type" : Attribute.ENUM,
327 "value" : DC.ERROR_LEVEL,
332 "flags" : Attribute.ExecReadOnly |\
333 Attribute.ExecImmutable |\
335 "validation_function" : validation.is_enum,
336 "category" : AC.CATEGORY_DEPLOYMENT,
340 "help" : "Do not intantiate testbeds, rather, reconnect to already-running instances. Used to recover from a dead controller.",
341 "type" : Attribute.BOOL,
343 "flags" : Attribute.ExecReadOnly |\
344 Attribute.ExecImmutable |\
346 "validation_function" : validation.is_bool,
347 "category" : AC.CATEGORY_DEPLOYMENT,
351 # These attributes could appear in the boxes attribute list
352 STANDARD_BOX_ATTRIBUTE_DEFINITIONS = dict({
354 "name" : "tun_proto",
355 "help" : "TUNneling protocol used",
356 "type" : Attribute.STRING,
357 "flags" : Attribute.DesignInvisible | \
358 Attribute.ExecInvisible | \
359 Attribute.ExecImmutable | \
361 "validation_function" : validation.is_string,
365 "help" : "Randomly selected TUNneling protocol cryptographic key. "
366 "Endpoints must agree to use the minimum (in lexicographic order) "
367 "of both the remote and local sides.",
368 "type" : Attribute.STRING,
369 "flags" : Attribute.DesignInvisible | \
370 Attribute.ExecInvisible | \
371 Attribute.ExecImmutable | \
373 "validation_function" : validation.is_string,
377 "help" : "Address (IP, unix socket, whatever) of the tunnel endpoint",
378 "type" : Attribute.STRING,
379 "flags" : Attribute.DesignInvisible | \
380 Attribute.ExecInvisible | \
381 Attribute.ExecImmutable | \
383 "validation_function" : validation.is_string,
387 "help" : "IP port of the tunnel endpoint",
388 "type" : Attribute.INTEGER,
389 "flags" : Attribute.DesignInvisible | \
390 Attribute.ExecInvisible | \
391 Attribute.ExecImmutable | \
393 "validation_function" : validation.is_integer,
395 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP : dict({
396 "name" : ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
397 "help" : "Commands to set up the environment needed to run NEPI testbeds",
398 "type" : Attribute.STRING,
399 "flags" : Attribute.DesignInvisible | \
400 Attribute.ExecInvisible | \
401 Attribute.ExecImmutable | \
403 "validation_function" : validation.is_string
407 STANDARD_TESTBED_ATTRIBUTES.update(DEPLOYMENT_ATTRIBUTES.copy())
409 def __init__(self, testbed_id, version):
410 self._version = version
411 self._testbed_id = testbed_id
412 metadata_module = self._load_versioned_metadata_module()
413 self._metadata = metadata_module.VersionedMetadataInfo()
416 def create_order(self):
417 return self._metadata.create_order
420 def configure_order(self):
421 return self._metadata.configure_order
424 def preconfigure_order(self):
425 return self._metadata.preconfigure_order
428 def prestart_order(self):
429 return self._metadata.prestart_order
432 def start_order(self):
433 return self._metadata.start_order
435 def testbed_attributes(self):
436 attributes = AttributesMap()
437 testbed_attributes = self._testbed_attributes()
438 self._add_attributes(attributes.add_attribute, testbed_attributes)
441 def build_factories(self):
443 for factory_id, info in self._metadata.factories_info.iteritems():
444 create_function = info.get("create_function")
445 start_function = info.get("start_function")
446 stop_function = info.get("stop_function")
447 status_function = info.get("status_function")
448 configure_function = info.get("configure_function")
449 preconfigure_function = info.get("preconfigure_function")
450 prestart_function = info.get("prestart_function")
452 category = info["category"]
453 factory = Factory(factory_id,
459 preconfigure_function,
464 factory_attributes = self._factory_attributes(info)
465 self._add_attributes(factory.add_attribute, factory_attributes)
466 box_attributes = self._box_attributes(info)
467 self._add_attributes(factory.add_box_attribute, box_attributes)
469 self._add_traces(factory, info)
470 self._add_tags(factory, info)
471 self._add_connector_types(factory, info)
472 factories.append(factory)
475 def _load_versioned_metadata_module(self):
476 mod_name = "nepi.testbeds.%s.metadata_v%s" % (self._testbed_id.lower(),
478 if not mod_name in sys.modules:
480 return sys.modules[mod_name]
482 def _testbed_attributes(self):
484 attributes = self.STANDARD_TESTBED_ATTRIBUTES.copy()
486 attributes.update(self._metadata.testbed_attributes.copy())
489 def _factory_attributes(self, info):
490 tagged_attributes = self._tagged_attributes(info)
491 if "factory_attributes" in info:
492 definitions = self._metadata.attributes.copy()
493 # filter attributes corresponding to the factory_id
494 factory_attributes = self._filter_attributes(info["factory_attributes"],
497 factory_attributes = dict()
498 attributes = dict(tagged_attributes.items() + \
499 factory_attributes.items())
502 def _box_attributes(self, info):
503 tagged_attributes = self._tagged_attributes(info)
504 if "box_attributes" in info:
505 definitions = self.STANDARD_BOX_ATTRIBUTE_DEFINITIONS.copy()
506 definitions.update(self._metadata.attributes)
507 box_attributes = self._filter_attributes(info["box_attributes"],
510 box_attributes = dict()
511 attributes = dict(tagged_attributes.items() + \
512 box_attributes.items())
513 attributes.update(self.STANDARD_BOX_ATTRIBUTES.copy())
516 def _tagged_attributes(self, info):
517 tagged_attributes = dict()
518 for tag_id in info.get("tags", []):
519 if tag_id in self.STANDARD_TAGGED_BOX_ATTRIBUTES:
520 attr_list = self.STANDARD_TAGGED_BOX_ATTRIBUTES[tag_id]
521 attributes = self._filter_attributes(attr_list,
522 self.STANDARD_TAGGED_ATTRIBUTES_DEFINITIONS)
523 tagged_attributes.update(attributes)
524 return tagged_attributes
526 def _filter_attributes(self, attr_list, definitions):
527 # filter attributes not corresponding to the factory
528 attributes = dict((attr_id, definitions[attr_id]) \
529 for attr_id in attr_list)
532 def _add_attributes(self, add_attr_func, attributes):
533 for attr_id, attr_info in attributes.iteritems():
534 name = attr_info["name"]
535 help = attr_info["help"]
536 type = attr_info["type"]
537 value = attr_info.get("value")
538 range = attr_info.get("range")
539 allowed = attr_info.get("allowed")
540 flags = attr_info.get("flags")
541 validation_function = attr_info["validation_function"]
542 category = attr_info.get("category")
543 add_attr_func(name, help, type, value, range, allowed, flags,
544 validation_function, category)
546 def _add_traces(self, factory, info):
547 for trace_id in info.get("traces", []):
548 trace_info = self._metadata.traces[trace_id]
549 name = trace_info["name"]
550 help = trace_info["help"]
551 factory.add_trace(name, help)
553 def _add_tags(self, factory, info):
554 for tag_id in info.get("tags", []):
555 factory.add_tag(tag_id)
557 def _add_connector_types(self, factory, info):
558 if "connector_types" in info:
559 from_connections = dict()
560 to_connections = dict()
561 for connection in self._metadata.connections:
562 from_ = connection["from"]
563 to = connection["to"]
564 can_cross = connection["can_cross"]
565 init_code = connection.get("init_code")
566 compl_code = connection.get("compl_code")
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 help = connector_type_info["help"]
580 max = connector_type_info["max"]
581 min = connector_type_info["min"]
582 testbed_id = self._testbed_id
583 factory_id = factory.factory_id
584 connector_type = ConnectorType(testbed_id, factory_id, name,
586 connector_key = (testbed_id, factory_id, name)
587 if connector_key in to_connections:
588 for (from_, can_cross, init_code, compl_code) in \
589 to_connections[connector_key]:
590 (testbed_id_from, factory_id_from, name_from) = from_
591 connector_type.add_from_connection(testbed_id_from,
592 factory_id_from, name_from, can_cross,
593 init_code, compl_code)
594 if connector_key in from_connections:
595 for (to, can_cross, init_code, compl_code) in \
596 from_connections[(testbed_id, factory_id, name)]:
597 (testbed_id_to, factory_id_to, name_to) = to
598 connector_type.add_to_connection(testbed_id_to,
599 factory_id_to, name_to, can_cross, init_code,
601 factory.add_connector_type(connector_type)