Inter-Switch Messaging Bus Overview¶
The exos.ismb_manager
module provides the infrastructure for switches to
discover and communicate together.
For discovery, exos.ismb_manager
uses Multicast DNS (mDNS) to find other
switches. The switch will maintain a list of the discovered switches (peers),
which will be available to applications that want to use it. The messaging is
done using XMPP.
ISMB is enabled/disabled on a per Virtual Router (VR) basis. It is enabled by default on VR-Mgmt.
Addtionally, the Switch Control client is bundled with ISMB on VR-Mgmt only. Switch Control allows for executing CLI commands and transfering files to other peers.
With that said, a client module is provided in exos.api.ismb_client
and that is where the exciting stuff is.
ISMB Client Module & SleekXMPP¶
Since ISMB is based around XMPP, we bundled sleekxmpp
as a client library on the
switch. To automate some of the XMPP basics and provide easier access to the
discovery list and file transfer mechanisms, there is also an EXOS specific
Mixin/Helper class to be inherited with sleekxmpp.ClientXMPP
class.
Lets look at an example of a class that wants to be an ISMB client.
import sleekxmpp
from exos.ismb_client import ISMBClientMixin
class ExampleClient(ISMBClientMixin,sleekxmpp.ClientXMPP):
def __init__(self, name, password,vr):
#NOTE: Order is important!
ISMBClientMixin.__init__(self,name,password,vr)
#Sleek needs the JID, not the name!
sleekxmpp.ClientXMPP.__init__(self,self.auto_jid,password)
All classes that want to be ISMB clients will be declared like this and probably
initialize in a similar manner. The 3 parameters to __init__()
are
required by the two inherited classes and are a minimum. The order that each
inherited class is initialized is important, as ISMBClientMixin
generates the JID and also creates it on the Server. We will cover these in
detail. The last thing is to ensure ISMB is enabled and running on this VR,
otherwise the client will fail to connect.
Lets look at the example line by line.
class ExampleClient(ISMBClientMixin,sleekxmpp.ClientXMPP):
The first line is the class declaration. The class inherits two other classes;
exos.ismb_client.ISMBClientMixin
and sleekxmpp.ClientXMPP
.
ISMBClientMixin
is the helper class and automates some of the
initialization for you and later, serves as the interface to exos features.
def __init__(self, name, password,vr):
The next line is the __init__()
declaration and requires 3 parameters.
The name and password are required for creating the user on the XMPP server.
The VR identifies the server that the client will connect to. If you want the
same client on multiple VRs, then creating multiple instances will be required.
Since you are writing this client, you may reserve the option to hard code
and/or remove any of these parameters. The important part is knowing that the
two inherited classes will require these 3 parameters at some point!
ISMBClientMixin.__init__(self,name,password,vr)
The next line is the initialization call for ISMBClientMixin.__init__()
.
This call will create the user on the XMPP server that is available on the VR
that is passed in. It also automatically creates the JID for you and stores it
in the instance attribute auto_jid
. The JID is the xmpp equivalent of a
username, which is required for sleekxmpp.ClientXMPP
.
sleekxmpp.ClientXMPP.__init__(self,self.auto_jid,password)
The last line initializes the sleekxmpp.ClientXMPP
class. This will
actually connect to the XMPP server and initializes everything that
ClientXMPP
needs to run.
XMPP Jabber Identifier (JID)¶
A JID is XMPP’s concept of a username. It is used to identify each user and how to reach them. I won’t cover everything about it here, but it consists of 3 sections:
<node-part>@<domain-part>/<resource>
The node-part is basically the name. This is generated from the
name
parameter in ISMBClientMixin
’s Initialization.
The domain-part is automatically generated. You can get the domain-part of the
local server by calling exos.ismb_client.get_domain_part()
.
The resource identifies the specific location of a user. In our use case, users may only be logged into one machine, but for XMPP, a user maybe logged into his laptop and phone for example. The resource identifies the phone from the laptop.
Unfortunately, the resource is required for transfering files and can add some complexity, which will be covered under Roster & Presence later in this document.
You can find more details in the JID XEP.
Peers List & Other client’s JID¶
The peers list is available by calling ISMBClientMixin.get_peer_domains()
method.
def get_peer_domains(self,include_ip=False):
It will return a set
of all the domain-parts found, including the local server.
The include_ip
is the only parameter and will allow you to request the
ip address of the server along with name. If it is set to true, the set
will contain tuples of (<domain-part>,<ip>).
The domain-part is a section of the JID. To get the node-part, you must already know the recipient’s name. Generally, clients will speak to their counter part on another server, so its trivial to figure out the node-part.
Once the domain-part is received and the node-part is known, you can generate the JID by combining the two with an “@” in the middle. For example, a client with a node-part of “example” and a domain-part of “test.001122” would have a JID of
example@test.001122
ClientXMPP’s events¶
ClientXMPP
is predominately event driven. Events are generated for
just about everything, but you must have a handler signed up to be notified.
Handling events requires the event name and a handler method:
self.add_event_handler("message", self.message)
You can see more details about event handling and a list of events in the sleekxmpp docs.
Logging¶
SleekXMPP uses the standard logging methods in Python. If you want to log everything from sleek, you can create a Trace Buffer with “sleekxmpp” as the name.
You can log individual modules by creating a tracebuffer with the __name__ of the module. For example if you want to log everything from clientxmpp.py, you would name the trace buffer “sleekxmpp.clientxmpp”.
You can see more details about heirarchical logging in the python docs.
XMPP Roster & Presence¶
You may also use ClientXMPP
methods to update and get your
roster. Inside your roster will be all the peers that have been added, either
through presence subscription or automatically by ClientXMPP
during
messaging.
The resource section of the JID is only available through the roster and requires subscribing to that client’s presence. Fortunately, most of the complexity can be automatically managed. The suggested way is to do the presence subscription in your “session_start” event handler. An example code snippet:
peers = self.get_peer_domains()
for peer in peers:
pname="switch@"+peer
logger.debug("Presence sub to Peer: %s",pname)
self.send_presence(pto=pname,ptype='subscribe')
You can see more details about roster and presence in the sleekxmpp docs.
Switch Control ISMB Client¶
ISMB comes with one already pre-enabled client called Switch Control (SC). This client is available at “switch@<server_name>”. This client is intended to help propagate upgrades and/or features without having to update the switch’s image.
SC will execute messages received as CLI commands. There are a few rules here for the message to be accepted.
- The type must be ‘normal’
- The subject must be ‘command’
- The body must be a stringified python list of CLI commands. ex. str([‘create vlan test’,’delete vlan test’])
SC will respond to the message either with an error or the CLI’s text response. Providing the CLI text response allows for some rudamentry use of show commands, but this use is highly discouraged. You can get this data as objects through a more standard interface.
There are two errors that SC will respond with. PythonSyntaxError will occur if the formatting of the python list fails to process correctly. CLICommandError will occur if the CLi doesn’t understand the syntax in its commands. Error messages will come with this format.
- The type will be ‘error’
- The error string will be in the ‘body’
- The error will be in tags. ex, <CLICommandError>I have failed!</CLICommandError>
- For CLICommandErrors, the error string will be included in the tags
- For PythonSyntaxErrors, there is no error string in the tags
Another capability of SC is to transfer files to other switches. Note that this requires your client to have requested presence of “switch@<server_name>”. Please see the section above on presence for more detail.
Once you have requested and received presence, you can send the file to multiple
switches by using ISMBClientMixin.send_file_to_peers()
. You can get an
event when presence arrives, by using clientxmpp.add_event_handler()
for
the “presence_available” event.
def send_file_to_peers(self,fname,peer_list,sent_name=None,overwrite=True):
fname
is the path to the file from ‘/usr/local’. peer_list
is
the list of peers. The peers in the list will be looked up in the roster to get
the resource, so you don’t need to do that. You must have subscribed for
presence (and received it) first though. sent_name
is not required and
will determine the name and/or path at the receiving server. overwrite
will allow you to overwrite a file if there is a name conflict. To be clear
files are restricted to ‘/usr/local’.