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