blob: de010e9e1e53a2c66f253193d76cf2ccd4e49ce4 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"reflect"
"regexp"
"strings"
"text/tabwriter"
)
const labstationTelemetryCmds = "grep guado_labstation-release /etc/lsb-release; " +
"printf \"\n\"; " +
"uptime; " +
"printf \"\n\"; " +
"mosys eventlog list | tail -n 10; " +
"printf \"\n\"; " +
"pgrep --list-full update_engine; " +
"printf \"\n\";"
const warningMessage = "This utility asssumes many things, like that you have " +
"atest in the environment in which it is run, that you have cros in " +
"your DNS search path, and that you have configured your environment to " +
"use the testing_rsa key on lab DUTs. This is just a convenience " +
"utility for firmware qual test environment health triage. Feel free " +
"and encouraged to extend and enhance it, but in the long term, it " +
"should be mostly obviated by monitoring and alerting."
type dut struct {
Hostname, Port, Labstation, Board, Model, Status, LockStatus, LockReason string
}
func newDut(hostname string) dut {
d := dut{Hostname: hostname}
regexMap := map[string]string{
"Port": `servo_port : (?P<Port>.*)`,
"Labstation": `servo_host : (?P<Labstation>.*)`,
"Board": `board:(?P<Board>.*)`,
"Model": `model:(?P<Model>.*)`,
"Status": `Status: (?P<Status>.*)`,
"LockStatus": `Locked: (?P<LockStatus>.*)`,
"LockReason": `Lock Reason: (?P<LockReason>.*)`,
}
cmd := exec.Command("atest", "host", "stat", hostname)
out, _ := cmd.Output()
for field, re := range regexMap {
match := regexp.MustCompile(re).FindStringSubmatch(string(out))
// The LockReason field can be empty if the DUT is not locked.
if len(match) != 2 && field != "LockReason" {
log.Printf("Skipping %s on %s. This could be ok if a DUT is only partially through the deployment checklist.", field, hostname)
continue
} else {
reflect.ValueOf(&d).Elem().FieldByName(field).SetString(match[1])
}
}
return d
}
func sendSSHCommand(host, remoteCmd string) (outs string, err error) {
cmd := exec.Command("ssh", "-o", "StrictHostKeyChecking=no", "root@"+host, remoteCmd)
out, err := cmd.Output()
outs = string(out)
return
}
func main() {
duts := []dut{}
poolPtr := flag.String("pool", "faft-cr50", "A pool of DUTs to operate on.")
flag.Parse()
fmt.Println(warningMessage)
log.Print("Gathering DUT info via atest...")
cmd := exec.Command("atest", "host", "list", "--hostnames-only", "--label=pool:"+*poolPtr)
out, err := cmd.Output()
if err != nil {
log.Fatalf("<atest host list> encountered: %s", err)
}
// Removing hostnames that don't begin with "chromeos1-" removes those that are not in the firmware lab.
for _, hostname := range strings.Fields(string(out)) {
if strings.HasPrefix(hostname, "chromeos1-") {
duts = append(duts, newDut(hostname))
}
}
log.Print("Summarizing DUT info...")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "Hostname\tBoard\tModel\tStatus\tLabstation\tPort\tLockStatus\tLockReason")
for _, dut := range duts {
fmt.Fprintln(w, dut.Hostname+"\t"+dut.Board+"\t"+dut.Model+"\t"+dut.Status+"\t"+dut.Labstation+"\t"+
dut.Port+"\t"+dut.LockStatus+"\t"+dut.LockReason+"\t")
}
w.Flush()
log.Print("Gathering and displaying key telemetry for labstations.")
// TODO(kmshelton): Do this without keeping two approximately-identical memos.
labstations := []string{}
labstationsSeen := make(map[string]bool)
for _, dut := range duts {
if _, ok := labstationsSeen[dut.Labstation]; !ok && dut.Labstation != "" {
labstations = append(labstations, dut.Labstation)
labstationsSeen[dut.Labstation] = true
}
}
// TODO(kmshelton): Migrate to using x/crypto/ssh (here and eleswehere) and handle network errors.
for _, labstation := range labstations {
fmt.Println("Operating on ", labstation)
out, err := sendSSHCommand(labstation, labstationTelemetryCmds)
if err != nil {
log.Fatalf("Gathering labstation telemetry encountered: %s when interfacing with %s. Is the labstation pingable? Do you have lab ssh credentials setup?", err, labstation)
}
fmt.Println("\n", out)
}
log.Print("Querying servos for their versions (note this depends on the servo consoles being in a functional state): ")
for _, dut := range duts {
if dut.Labstation == "" || dut.Port == "" {
continue
}
out, err := sendSSHCommand(dut.Labstation, "dut-control -p "+dut.Port+" servo_micro_version; dut-control -p "+dut.Port+" servo_v4_version;")
if err != nil {
log.Fatalf("Querying servos encountered: %s when interfacing with %s. Is the labstation pingable? Do you have lab ssh credentials setup?", err, dut.Labstation)
}
fmt.Println(dut.Hostname, ": ", out)
}
}