blob: 82c894d78270b3384f50d267c70c2ec75de182f4 [file] [log] [blame]
package main
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/skolo/go/powercycle"
)
type edgeswitchPortGetter struct {
client powercycle.CommandRunner
}
// newSwitchPortGetter returns an implementation of addressPortGetter
func newSwitchPortGetter(address, user, password string) *edgeswitchPortGetter {
target := fmt.Sprintf("%s@%s", user, address)
return &edgeswitchPortGetter{
client: powercycle.PasswordSSHCommandRunner(password, target),
}
}
// GetDevicePortsAddresses implements addressPortGetter
func (e *edgeswitchPortGetter) GetDevicePortsAddresses(ctx context.Context) ([]poeDevice, error) {
// ignore error because when talking to the edgeswitch, sshpass always returns an error
out, _ := e.client.ExecCmds(ctx,
"enable",
"show mac-addr-table all",
"mmmmmmmmm", // by default, the table only shows the first 20 entries. If we hit m, we see
// another 20. This is enough to see 200 entries, which should be plenty.
)
// It's hard to tell, but the raw output from the edgeswitch is full of carriage returns (\r),
// which can mess up regex searching. Treating them as newlines for the purpose of processing
// makes that easier.
out = strings.Replace(out, "\r", "\n", -1)
bots, err := parseSSHResult(strings.Split(out, "\n"))
if err != nil || len(bots) == 0 {
return nil, skerr.Wrapf(err, "parsing output from (possibly failed command): \n%s\n", out)
}
return dedupeBots(bots), nil
}
// Example line with named groups annotated:
// 1 b8:27:eb:83:06:91 0/42 42 Learned
// <mac_address > <interface>
var edgeswitchLine = regexp.MustCompile(`^\S+\s+(?P<mac_address>[0-9A-Fa-f:]+)\s+\S+\s+(?P<interface>\d+)\s+\S+`)
// parseSSHResult looks at the lines output by the EdgeSwitchClient. These are
// already split by \n. It then parses the lines into the various components.
// See the unit tests for an example of what this data looks like.
func parseSSHResult(lines []string) ([]poeDevice, error) {
var devices []poeDevice
for _, l := range lines {
if matches := edgeswitchLine.FindStringSubmatch(l); matches != nil {
port, err := strconv.ParseInt(matches[2], 10, 0)
if err != nil {
return nil, skerr.Wrapf(err, "formatting error. %s is not an int", matches[2])
}
devices = append(devices, poeDevice{
MACAddress: strings.ToUpper(matches[1]),
POEPort: int(port),
})
}
}
return devices, nil
}
// dedupeBots filters out the list of bots such that only bots whose port
// assignments are unique are in the list. Bots with duplicate ports are
// likely not directly attached to this switch.
func dedupeBots(bots []poeDevice) []poeDevice {
uniquePorts := map[int]bool{}
for _, b := range bots {
if _, ok := uniquePorts[b.POEPort]; ok {
uniquePorts[b.POEPort] = false
} else {
uniquePorts[b.POEPort] = true
}
}
var unique []poeDevice
for _, b := range bots {
if uniquePorts[b.POEPort] {
unique = append(unique, b)
}
}
return unique
}
var _ addressPortGetter = (*edgeswitchPortGetter)(nil)