Documentation of Thola / Adding a new device / Writing a code communicator

Writing a code communicator

The configuration language used in the device classes is pretty complex and so it also has a lot of possibilities to specify the logic for communication with a network device. But the problem is that some devices have some special cases that are not possible to implement with the configuration language.

That is where code communicators are used. Normally a deviceClassCommunicator is created through the device classes, but there is also the possibility to define a code communicator for each device. This code communicator can then implement the communicator interface. The communicator interface looks like the following:

communicator
<interface>
----------------------------------------------------------------
GetVendor(ctx *context.Context) (string, error)
GetModel(ctx *context.Context) (string, error)
GetModelSeries(ctx *context.Context) (string, error)
GetSerialNumber(ctx *context.Context) (string, error)
GetOSVersion(ctx *context.Context) (string, error)
GetInterfaces(ctx *context.Context) (string, error)
GetCountInterfaces(ctx *context.Context) (string, error)

To implement the functions you first have to define a struct that inherits from the “baseCommunicator”. On that struct, you can implement the communicator interface. A code communicator might look like this:

package communicator

import (
    "context"
    "github.com/inexio/thola/core/network"
    "github.com/pkg/errors"
    "strconv"
)

type iosCommunicator struct {
    baseCommunicator
}

func (c *iosCommunicator) GetCPUComponentCPULoad(ctx context.Context) ([]float64, error) {
    con, ok := network.DeviceConnectionFromContext(ctx)
    if !ok || con.SNMP == nil {
        return nil, errors.New("no device connection available")
    }
    var cpus []float64

    res, _ := con.SNMP.SnmpClient.SNMPWalk(ctx, "1.3.6.1.4.1.9.9.109.1.1.1.1.5")
    for _, snmpres := range res {
        s, err := snmpres.GetValueString()
        if err != nil {
            return nil, err
        }
        f, err := strconv.ParseFloat(s, 64)
        if err != nil {
            return nil, errors.Wrap(err, "failed to parse snmp response to float64")
        }
        cpus = append(cpus, f)
    }
    return cpus, nil
}

When you created the codeCommunicator file, then the getCodeCommunicator function has to be modified. The function takes the class of a device and returns the correct communicator. After modifying the function for our code communicator example, it looks like the following:

func getCodeCommunicator(class string) (communicator, error) {
    switch class {
    case " generic ":
        return &genericCodeCommunicator{}, nil
╔═══════════════════════════════════════════════════╗ 
║   case " ceraos/ip10 ":                           ║ 
║       return &ceraosIP10CodeCommunicator{}, nil   ║ 
║   }                                               ║ 
╚═══════════════════════════════════════════════════╝
    return nil, errors.New("no communicator found") 
}