.. toctree:: ========================= Plugins Development Guide ========================= The Topology framework is easily extendable. In particular, you can extend the platform where the topology is built and run (virtual, physical, etc) and extend the mechanisms available to communicate with the nodes (shells like vtysh or bash, RESTful API, OpenFlow, OVSDB management protocol, protobuffers, etc). .. contents:: :local: Provide a new Platform Engine ============================= An Platform Engine is a system that allows to build and run a topology description. The Topology framework only provides a built-in Platform Engine for debugging. You can provide your own or install any of the known Engine Platforms available as plugins like: - `Docker `_. - `Connect `_. Entry Point ----------- The entry point to provide a new Platform Engine is ``topology_platform_``, currently ``topology_platform_10``. To extend `topology` to support your platform first implement in your package a subclass of :class:`topology.platforms.platform.BasePlatform`. It is recommended, but not required, that your package is called ``topology_`` and your sublcass is called ``Platform``. For example, in your module ``topology_my.platform``: .. code-block:: python from topology.platforms.platform import BasePlatform class MyPlatform(BasePlatform): pass Then specify in your `setup.py`: .. code-block:: python # Entry points entry_points={ 'topology_platform_10': ['my = topology_my.platform:MyPlatform'] } .. _engine-platform-worflow: Platform Engine Worflow ----------------------- The new platform needs to implement a set of hooks that will be called during the build and unbuild phases of a topology setup. For a textual description of each hook please consult the :class:`topology.platforms.platform.BasePlatform` class. The following sequence diagram shows the interaction between the :class:`topology.manager.TopologyManager`, which drives the lifecycle of a topology, and the *Platform Engine* which is responsible of building and running the topology itself. The interaction is straightforward, the main detail is that the ``add_node`` hook receives a *Specification Node* in standard NML format and must return a *Engine Node*, which is a different type of node that possess a communication interface. Users of the topology interact with *Engine Nodes*, not with *Specification Nodes*. Is up to the *Platform Engine* to implement the one (or many) *Engine Nodes*. Each *Engine Node* inherits from the :class:`topology.platforms.node.BaseNode` class and must implement its interface. Another detail is that the *Platform Engine* must return the real or final port name of a given *Specification port* in the ``add_biport`` hook. This allows to the engine to change names to map to a specific platform or to change layout or perform autoport connections. With this mapping the user will be able to reference a port using the specification name and it could be mapped to the real port name. .. uml:: actor User as U participant TopologyManager as M participant "platform: BasePlatform" as P participant "enode : BaseNode" as N U -->> M: <>(engine) activate M U -> M: load(dict), parse(str), register_node(node) U <<-- M: <> U -> M: build() M -->> P: <>(timestamp, nml) activate P M -> P: pre_build() M <<-- P: <> loop each node M -> P: add_node(node) P -->> N: <>() activate N M <<-- P: enode M -> M: register(enode) end loop each biport M -> P: add_biport(node, biport) M <<-- P: eport M -> M: register(eport) end loop each bilink M -> P: add_bilink(node_port_a, node_port_b, bilink) M <<-- P: <> end M -> P: post_build() M <<-- P: <> U <<-- M: <> loop as needed U -> M: get(enode_name) M -->> U: enode U -> N: send_command(cmd) U <<-- N: response end U -> M: unbuild() M -> P: destroy() M <-- P: <> destroy P destroy N U <-- M: <> .. _provide-a-new-communication-library: Provide a new Communication Library =================================== A communication library is a component that a allows an *Engine Node* to speak in a particular language or medium. Because the *Platform Engine* is the one responsible to provide a functional *Engine Node* is up to the *Platform Engine* to support the use of the communication libraries provided by this extension mechanism (but it is highly recommended to do so). Entry Point ----------- The entry point to provide a new *Communication Library* is ``topology_library_``, currently ``topology_library_10``. To extend `topology` to support your communication library you must implement in your package a module with all your public functions and classes and create a registry entry (see below). It is recommended, but not required, that your package is called ``topology_lib_``. For example, in your module ``topology_lib_my.library``: .. code-block:: python def foo_function(enode, myarg1=None, myarg2=100): return {'ham': myarg1} def bar_function(enode, myarg1=None, myarg2=100): return {'ham': 200} class FooClass(object): def __init__(self, enode, myarg): print(123) __all__ = ['foo_function', 'bar_function', 'FooClass'] Then specify in your `setup.py`: .. code-block:: python # Entry points entry_points={ 'topology_library_10': ['my = topology_lib_my.library'] } With this, and if your *Platform Engine* builds your *Engine Nodes* to support communication libraries (see below), your functions will be available to the ``enode`` like this: .. code-block:: python >>> sw1.libs.my.foo_function(myarg1=275) {'ham': 275} >>> sw1.libs.my.FooClass("hi") 123 Please note, all your functions and classes are registered inside a namespace with the name of the communication library as you specified in the ``setup.py`` Saving state ------------ All communication functions and classes receive the *Engine Node* as first parameter, all other parameters are up to the function. The ``enode`` argument can be used to store state or data for the library or to trigger calls to other libraries or commands as part of the communication flow. A common pattern is to use a class to store the state of the communication library and store an instances of that class inside the enode. The decorator :func:`topology.libraries.utils.stateprovider` allows to easily implement this pattern: .. code-block:: python from topology.libraries.utils import stateprovider class MyState(object): def __init__(self): self.my_variable_one = 100 @stateprovider(MyState) def my_library_function(enode, state, arg1, arg2, keyword_arg1=None): print(state.my_variable_one) state.my_variable_one += 100 __all__ = ['my_library_function'] Shell command wrapping libraries -------------------------------- A common pattern is to use communication libraries to wrap and parse shell commands. It is recommended to check first the availability of any dependency shell using the method :meth:`topology.platforms.node.BaseNode.available_shells`. See :class:`topology.platforms.node.BaseNode` for more information about the *Engine Node* interface. For an example of a communication library that wrap shells commands please review the topology_lib_ping_ library documentation for more information.. .. _topology_lib_ping: https://github.com/HPENetworking/topology_lib_ping/blob/master/lib/topology_lib_ping/library.py Supporting communication libraries ---------------------------------- Most of the nodes you use will derive from the common :class:`topology.platforms.node.CommonNode`. If for some reason you require to use a different base node, say :class:`topology.platforms.node.BaseNode` and you still want to support communication libraries make sure to create and attribute ``libs`` with an instance of :class:`topology.libraries.manager.LibsProxy`: .. code-block:: python class MyEngineNode(BaseNode): def __init__(self, identifier, **kwargs): super(MyEngineNode, self).__init__(identifier, **kwargs) # Add support for communication libraries self.libs = LibsProxy(self)