def __init__(self, engine=DEFAULT_PLATFORM, **kwargs): if engine not in platforms(): raise RuntimeError('Unknown platform engine "{}".'.format(engine)) self.nml = ExtendedNMLManager(**kwargs) self.engine = engine self.nodes = OrderedDict() self.ports = OrderedDict() self._platform = None self._built = False
def common_mgr(): """ Create a base topology. This uses the ExtendedNMLManager for it's helpers. """ # Create base topology mgr = ExtendedNMLManager(name='Graphviz Namespace') sw1 = mgr.create_node(identifier='sw1', name='My Switch 1') sw2 = mgr.create_node(identifier='sw2', name='My Switch 2') assert mgr.get_object('sw1') is not None assert mgr.get_object('sw2') is not None sw1p1 = mgr.create_biport(sw1) sw1p2 = mgr.create_biport(sw1) sw1p3 = mgr.create_biport(sw1) # noqa sw2p1 = mgr.create_biport(sw2) sw2p2 = mgr.create_biport(sw2) sw2p3 = mgr.create_biport(sw2) # noqa sw1p1_sw2p1 = mgr.create_bilink(sw1p1, sw2p1) # noqa sw1p2_sw2p2 = mgr.create_bilink(sw1p2, sw2p2) # noqa return mgr
class TopologyManager(object): """ Main Topology Manager object. This object is responsible to build a topology given a description. There is three options to build a topology: - Using the simplified textual description using a Graphviz like syntax. To use this option call the :meth:`parse`. - Using a basic Python dictionary to load the description of the topology. To use this option call the :meth:`load`. - Build a full NML topology using NML objects and relations and register all the objects in the namespace using the embedded :class:`pynml.manager.ExtendedNMLManager`, for example: :: mgr = TopologyManager() sw1 = mgr.create_node(name='My Switch 1') sw2 = mgr.create_node(name='My Switch 2') sw1p1 = mgr.create_biport(sw1) # ... See :class:`pynml.manager.ExtendedNMLManager` for more information of this usage. :param str engine: Name of the platform engine to build the topology. See :func:`platforms` for how to get and discover available platforms. """ def __init__(self, engine=DEFAULT_PLATFORM, **kwargs): super(TopologyManager, self).__init__() if engine not in platforms(): raise RuntimeError('Unknown platform engine "{}".'.format(engine)) self.nml = ExtendedNMLManager(**kwargs) self.engine = engine self.nodes = OrderedDict() self.ports = OrderedDict() self._platform = None self._built = False def load(self, dictmeta, inject=None): """ Load a topology description in a dictionary format. Dictionary format: :: { 'nodes': [ { 'nodes': ['sw1', 'hs1', ...], 'attributes': {...} }, ], 'ports': [ { 'ports': [('sw1', '1'), ('hs1', '1'), ...], 'attributes': {...} } ] 'links': [ { 'endpoints': (('sw1', '1'), ('hs1', '1')), 'attributes': {...} }, ] } :param dict dictmeta: The dictionary to load the topology from. :param dict inject: An attributes injection sub-dictionary as defined by :func:`parse_attribute_injection`. """ # Load the environment environment = dictmeta.get('environment', OrderedDict()) if inject is not None and 'environment' in inject: environment.update(inject['environment']) self.nml.create_environment(**environment) # Load nodes for nodes_spec in dictmeta.get('nodes', []): for node_id in nodes_spec['nodes']: # Explicitly create node attrs = deepcopy(nodes_spec['attributes']) attrs['identifier'] = node_id # Inject the run-specific attributes if inject is not None and node_id in inject['nodes']: attrs.update(inject['nodes'][node_id]) self.nml.create_node(**attrs) # Load ports for ports_spec in dictmeta.get('ports', []): for node_id, port in ports_spec['ports']: node = self.nml.get_object(node_id) # Explicitly create port attrs = deepcopy(ports_spec['attributes']) attrs['identifier'] = '{}-{}'.format(node_id, port) attrs['label'] = port # Inject the run-specific attributes if inject is not None and (node_id, port) in inject['ports']: attrs.update(inject['ports'][(node_id, port)]) self.nml.create_biport(node, **attrs) # Load links for link_spec in dictmeta.get('links', []): # Get endpoints endpoints = [None, None] for idx, (node_id, port) in enumerate(link_spec['endpoints']): # Auto-create node node = self.nml.get_object(node_id) # Auto-create biport port_id = '{}-{}'.format(node_id, port) biport = self.nml.get_object(port_id) # Register endpoint endpoints[idx] = biport # Explicit-create links attrs = deepcopy(link_spec['attributes']) # Inject the run-specific attributes if inject is not None and \ link_spec['endpoints'] in inject['links']: attrs.update(inject['links'][link_spec['endpoints']]) self.nml.create_bilink(*endpoints, **attrs) def parse(self, txtmeta, load=True, inject=None): """ Parse a textual topology meta-description. For a description of the textual format see pyszn package documentation. :param str txtmeta: The textual meta-description of the topology. :param bool load: If ``True`` (the default) call :meth:`load` immediately after parse. :param dict inject: An attributes injection sub-dictionary as defined by :func:`parse_attribute_injection`. """ data = parse_txtmeta(txtmeta) if load: self.load(data, inject=inject) return data def is_built(self): """ Check if the current topology was built. :rtype: bool :return: True if the topology was successfully built. """ return self._built def build(self): """ Build the topology hold by this manager. This method instance the platform engine and request the build of the topology defined. """ if self._built: raise RuntimeError('You cannot build a topology twice.') node_enode_map = OrderedDict() timestamp = datetime.now().replace(microsecond=0).isoformat() stage = 'instance' # Instance platform plugin = load_platform(self.engine) self._platform = plugin(timestamp, self.nml) try: stage = 'pre_build' self._platform.pre_build() stage = 'add_node' for node in self.nml.nodes(): enode = self._platform.add_node(node) # Check that engine node implements the minimum interface if not isinstance(enode, BaseNode): msg = ('Platform {} returned an invalid ' 'engine node {}.').format(self.engine, enode) log.critical(msg) raise Exception(msg) # Register engine node node_enode_map[node.identifier] = enode.identifier self.nodes[enode.identifier] = enode # Register empty port map self.ports[enode.identifier] = OrderedDict() stage = 'add_biport' for node, biport in self.nml.biports(): eport = self._platform.add_biport(node, biport) # Check that engine port is of correct type if not isinstance(eport, string_types): msg = ('Platform {} returned an invalid ' 'engine port name {}.').format(self.engine, enode) log.critical(msg) raise Exception(msg) # Register engine port label = biport.metadata.get('label', biport.identifier) enode_id = node_enode_map[node.identifier] self.ports[enode_id][label] = eport stage = 'add_bilink' for node_porta, node_portb, bilink in self.nml.bilinks(): self._platform.add_bilink(node_porta, node_portb, bilink) stage = 'post_build' # Assign the port mapping to the enode so they know their mapping # and be able to change it if required # (and globally, we do not use deepcopy). for enode_id, enode in self.nodes.items(): enode.ports = self.ports[enode_id] self._platform.post_build() except Exception: e = exc_info()[1] log.critical( ('Build failed at stage "{}" with "{}". ' 'Calling plugin rollback routine...').format(stage, e)) log.debug(format_exc()) self._platform.rollback(stage, self.nodes, e) raise self._built = True def unbuild(self): """ Undo the topology. This removes all references to the engine nodes and request the platform to destroy the topology. """ if not self._built: raise RuntimeError('You cannot unbuild and never built topology.') # Remove own reference to enodes self.nodes = OrderedDict() # Call platform destroy hook self._platform.destroy() # Explicitly delete platform del self._platform self._platform = None def get(self, identifier): """ Get a platform engine with given identifier. :param str identifier: The node identifier. :rtype: A subclass of :class:`topology.platforms.node.BaseNode` :return: The engine node implementing the communication with the node. """ return self.nodes.get(identifier, None) def relink(self, link_id): """ Relink back a link specified in the topology. :param str link_id: Link identifier to be recreated. """ if not self._built: raise RuntimeError('You cannot relink on a never built topology.') self._platform.relink(link_id) def unlink(self, link_id): """ Unlink (break) a link specified in the topology. :param str link_id: Link identifier to be recreated. """ if not self._built: raise RuntimeError('You cannot unlink on a never built topology.') self._platform.unlink(link_id) def _set_test_log(self, log): """ Set the current test execution log. This log is set by testlog library :param log: the logging object requested """ for enode in self.nodes.values(): enode._set_test_log(log)
class TopologyManager(object): """ Main Topology Manager object. This object is responsible to build a topology given a description. There is three options to build a topology: - Using the simplified textual description using a Graphviz like syntax. To use this option call the :meth:`parse`. - Using a basic Python dictionary to load the description of the topology. To use this option call the :meth:`load`. - Build a full NML topology using NML objects and relations and register all the objects in the namespace using the embedded :class:`pynml.manager.ExtendedNMLManager`, for example: :: mgr = TopologyManager() sw1 = mgr.create_node(name='My Switch 1') sw2 = mgr.create_node(name='My Switch 2') sw1p1 = mgr.create_biport(sw1) # ... See :class:`pynml.manager.ExtendedNMLManager` for more information of this usage. :param str engine: Name of the platform engine to build the topology. See :func:`platforms` for how to get and discover available platforms. """ def __init__(self, engine=DEFAULT_PLATFORM, **kwargs): if engine not in platforms(): raise RuntimeError('Unknown platform engine "{}".'.format(engine)) self.nml = ExtendedNMLManager(**kwargs) self.engine = engine self.nodes = OrderedDict() self.ports = OrderedDict() self._platform = None self._built = False def load(self, dictmeta, inject=None): """ Load a topology description in a dictionary format. Dictionary format: :: { 'nodes': [ { 'nodes': ['sw1', 'hs1', ...], 'attributes': {...} }, ], 'ports': [ { 'ports': [('sw1', '1'), ('hs1', '1'), ...], 'attributes': {...} } ] 'links': [ { 'endpoints': (('sw1', '1'), ('hs1', '1')), 'attributes': {...} }, ] } See also the module :mod:`topology.parser`. :param dict dictmeta: The dictionary to load the topology from. :param dict inject: An attributes injection sub-dictionary as defined by :func:`parse_attribute_injection`. """ # Load nodes for nodes_spec in dictmeta.get('nodes', []): for node_id in nodes_spec['nodes']: # Explicitly create node attrs = deepcopy(nodes_spec['attributes']) attrs['identifier'] = node_id # Inject the run-specific attributes if inject is not None and node_id in inject: for key, value in inject[node_id].items(): attrs[key] = value self.nml.create_node(**attrs) # Load ports for ports_spec in dictmeta.get('ports', []): for node_id, port in ports_spec['ports']: # Auto-create node node = self.nml.get_object(node_id) if node is None: node = self.nml.create_node(identifier=node_id) # Explicitly create port attrs = deepcopy(ports_spec['attributes']) attrs['identifier'] = '{}-{}'.format(node_id, port) attrs['label'] = port self.nml.create_biport(node, **attrs) # Load links for link_spec in dictmeta.get('links', []): # Get endpoints endpoints = [None, None] for idx, (node_id, port) in enumerate(link_spec['endpoints']): # Auto-create node node = self.nml.get_object(node_id) if node is None: node = self.nml.create_node(identifier=node_id) # Auto-create biport port_id = '{}-{}'.format(node_id, port) biport = self.nml.get_object(port_id) if biport is None: attrs = { 'identifier': port_id, 'label': port } biport = self.nml.create_biport(node, **attrs) # Register endpoint endpoints[idx] = biport # Explicit-create links attrs = deepcopy(link_spec['attributes']) self.nml.create_bilink(*endpoints, **attrs) def parse(self, txtmeta, load=True, inject=None): """ Parse a textual topology meta-description. For a description of the textual format see the module :mod:`topology.parser`. :param str txtmeta: The textual meta-description of the topology. :param bool load: If ``True`` (the default) call :meth:`load` immediately after parse. :param dict inject: An attributes injection sub-dictionary as defined by :func:`parse_attribute_injection`. """ data = parse_txtmeta(txtmeta) if load: self.load(data, inject=inject) return data def is_built(self): """ Check if the current topology was built. :rtype: bool :return: True if the topology was successfully built. """ return self._built def build(self): """ Build the topology hold by this manager. This method instance the platform engine and request the build of the topology defined. """ if self._built: raise RuntimeError( 'You cannot build a topology twice.' ) node_enode_map = OrderedDict() timestamp = datetime.now().replace(microsecond=0).isoformat() stage = 'instance' # Instance platform plugin = load_platform(self.engine) self._platform = plugin(timestamp, self.nml) try: stage = 'pre_build' self._platform.pre_build() stage = 'add_node' for node in self.nml.nodes(): enode = self._platform.add_node(node) # Check that engine node implements the minimum interface if not isinstance(enode, BaseNode): msg = ( 'Platform {} returned an invalid ' 'engine node {}.' ).format(self.engine, enode) log.critical(msg) raise Exception(msg) # Register engine node node_enode_map[node.identifier] = enode.identifier self.nodes[enode.identifier] = enode # Register empty port map self.ports[enode.identifier] = OrderedDict() stage = 'add_biport' for node, biport in self.nml.biports(): eport = self._platform.add_biport(node, biport) # Check that engine port is of correct type if not isinstance(eport, string_types): msg = ( 'Platform {} returned an invalid ' 'engine port name {}.' ).format(self.engine, enode) log.critical(msg) raise Exception(msg) # Register engine port label = biport.metadata.get('label', biport.identifier) enode_id = node_enode_map[node.identifier] self.ports[enode_id][label] = eport stage = 'add_bilink' for node_porta, node_portb, bilink in self.nml.bilinks(): self._platform.add_bilink(node_porta, node_portb, bilink) stage = 'post_build' # Assign the port mapping to the enode so they know their mapping # and be able to change it if required # (and globally, we do not use deepcopy). for enode_id, enode in self.nodes.items(): enode.ports = self.ports[enode_id] self._platform.post_build() except Exception as e: log.critical( ( 'Build failed at stage "{}" with "{}". ' 'Calling plugin rollback routine...' ).format(stage, e) ) log.debug(format_exc()) self._platform.rollback(stage, self.nodes, e) raise e self._built = True def unbuild(self): """ Undo the topology. This removes all references to the engine nodes and request the platform to destroy the topology. """ if not self._built: raise RuntimeError( 'You cannot unbuild and never built topology.' ) # Remove own reference to enodes self.nodes = OrderedDict() # Call platform destroy hook self._platform.destroy() # Explicitly delete platform del self._platform self._platform = None def get(self, identifier): """ Get a platform engine with given identifier. :param str identifier: The node identifier. :rtype: A subclass of :class:`topology.platforms.base.BaseNode` :return: The engine node implementing the communication with the node. """ return self.nodes.get(identifier, None) def relink(self, link_id): """ Relink back a link specified in the topology. :param str link_id: Link identifier to be recreated. """ if not self._built: raise RuntimeError( 'You cannot relink on a never built topology.' ) self._platform.relink(link_id) def unlink(self, link_id): """ Unlink (break) a link specified in the topology. :param str link_id: Link identifier to be recreated. """ if not self._built: raise RuntimeError( 'You cannot unlink on a never built topology.' ) self._platform.unlink(link_id)