blob: 2c91f01f324052a3299f40797fa68c7b601963b9 [file] [log] [blame]
package main
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"regexp"
"strings"
"time"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/sklog"
)
// The BotNameGetter interface abstracts the logic to collect all information
// about the bots (e.g. Name, IPv4 Address) except the EdgeSwitch ports.
type BotNameGetter interface {
// Collects and returns a list of Bot with nearly all the
// information filled out. This can be a very expensive call
// (2 minutes or more).
GetBotNamesAddresses(context.Context) ([]Bot, error)
}
// Implements BotNameGetter using Ansible.
type ansibleBotSource struct {
scriptDir string
outputFile string
}
// NewAnsibleBotNameGetter returns an Ansible implemented version of BotNameGetter
func NewAnsibleBotNameGetter(scriptDir, outputFile string) *ansibleBotSource {
return &ansibleBotSource{
scriptDir: scriptDir,
outputFile: outputFile,
}
}
// GetBotNamesAddresses, fulfills BotNameGetter
func (a *ansibleBotSource) GetBotNamesAddresses(ctx context.Context) ([]Bot, error) {
if err := os.Remove(a.outputFile); err != nil {
sklog.Warningf("Could not clear out file: %s", err)
}
output := bytes.Buffer{}
// This command scans all hosts on this network (specified by sys/all-hosts)
// and records the hostname, mac address and ip address of them all.
err := exec.Run(ctx, &exec.Command{
Name: "ansible-playbook",
Args: []string{"-i", "all-hosts", "enumerate_hostnames.yml",
"--extra-vars", "output_file=" + a.outputFile, "-vv"},
Dir: a.scriptDir,
CombinedOutput: &output,
// The task usually takes about 2 minutes. 10 is very generous in case of network
// or load congestion.
Timeout: 10 * time.Minute,
})
sklog.Warningf("Possible error while running ansible command: %v", err)
sklog.Infof("Output from ansible command: %s", output.String())
content, err := ioutil.ReadFile(a.outputFile)
if err != nil {
return nil, fmt.Errorf("Could not get output from ansible: %s", err)
}
return parseAnsibleResult(string(content)), nil
}
var ansibleLine = regexp.MustCompile(`^(?P<hostname>\S+)\s+(?P<ipv4_address>[0-9\.]+)\s+(?P<mac_address>[0-9A-Fa-f\:]+)`)
// parseAnsibleResult looks at the output from the enumerate_hostnames.yml call
// which is simply a space separated tuple of hostname, ip address, and mac address
func parseAnsibleResult(input string) []Bot {
lines := strings.Split(input, "\n")
bots := []Bot{}
for _, l := range lines {
if matches := ansibleLine.FindStringSubmatch(l); matches != nil {
bots = append(bots, Bot{Hostname: matches[1], IPV4Address: matches[2], MACAddress: strings.ToUpper(matches[3])})
}
}
return bots
}