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)