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 has a lot of possibilities to specify the logic for communication with a network device. But some devices have some special cases that still are not possible to implement with the configuration language.

That is where code communicators are used. There is the possibility to define a code communicator for each device class that overwrites function from the yaml device class files. This code communicator needs to implement the communicator.Functions interface, which defines all operations Thola can perform on a network device. This interface looks like the following:

communicator.Functions
<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 create a new code communicator, you need to create a new file inside the code communicator directory core/communicator/codecommunicator and create a new struct-based type for your device class. This type needs to embed the codecommunicator.codecommunicator type to ensure it satisfies the codecommunicator.Functions interface. After that, you can write the methods you want to define as code for this particular device class, like shown in the following example (ios):

package codecommunicator

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

type iosCodeCommunicator struct {
    codecommunicator
}

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
}

The last thing that needs to be done in order for a code communicator to work, is assigning it to its device class. This is done through a mapping in core/communicator/codecommunicator/code_communicator.go. The new code communicator needs to be included in the GetCodeCommunicator function. It contains a switch case which maps a device class name to its code communicator:

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

After this is done, Thola will use the newly defined code communicator for executing the operations it defines.