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).

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:

Entry Point

The entry point to provide a new Platform Engine is topology_platform_<api_version>, currently topology_platform_10.

To extend topology to support your platform first implement in your package a subclass of topology.platforms.platform.BasePlatform.

It is recommended, but not required, that your package is called topology_<platform_name> and your sublcass is called <PlatformName>Platform.

For example, in your module topology_my.platform:

from topology.platforms.platform import BasePlatform
class MyPlatform(BasePlatform):
    pass

Then specify in your setup.py:

# Entry points
entry_points={
    'topology_platform_10': ['my = topology_my.platform:MyPlatform']
}

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 topology.platforms.platform.BasePlatform class.

The following sequence diagram shows the interaction between the 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 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.

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_<api_version>, 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_<library_name>.

For example, in your module topology_lib_my.library:

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:

# 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:

>>> 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 topology.libraries.utils.stateprovider() allows to easily implement this pattern:

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 topology.platforms.node.BaseNode.available_shells(). See 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..

Supporting communication libraries

Most of the nodes you use will derive from the common topology.platforms.node.CommonNode. If for some reason you require to use a different base node, say topology.platforms.node.BaseNode and you still want to support communication libraries make sure to create and attribute libs with an instance of topology.libraries.manager.LibsProxy:

class MyEngineNode(BaseNode):
    def __init__(self, identifier, **kwargs):
        super(MyEngineNode, self).__init__(identifier, **kwargs)

        # Add support for communication libraries
        self.libs = LibsProxy(self)