Local multi-dut config support
Allows local dut topology configs to be managed in protojson form and
then passed into inventoryserver, which allows complex dut setups
(beyond basic ssh config) like multi-dut, peripherals, etc...
BUG=b:188712103
TEST=unit
Change-Id: I207477257b7ec788552c6bf9b053f275c7210214
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/3042331
Tested-by: C Shapiro <shapiroc@chromium.org>
Auto-Submit: C Shapiro <shapiroc@chromium.org>
Reviewed-by: Jaques Clapauch <jaquesc@google.com>
Commit-Queue: Jaques Clapauch <jaquesc@google.com>
diff --git a/src/chromiumos/test/lab/local/cmd/inventoryserver/inventoryserver.go b/src/chromiumos/test/lab/local/cmd/inventoryserver/inventoryserver.go
index db1721b..121e5ed 100644
--- a/src/chromiumos/test/lab/local/cmd/inventoryserver/inventoryserver.go
+++ b/src/chromiumos/test/lab/local/cmd/inventoryserver/inventoryserver.go
@@ -6,9 +6,14 @@
package main
import (
+ "bytes"
+ "errors"
+ "io/ioutil"
"log"
"net"
+ "github.com/golang/protobuf/jsonpb"
+ "github.com/golang/protobuf/proto"
"go.chromium.org/chromiumos/config/go/test/lab/api"
"google.golang.org/grpc"
)
@@ -23,12 +28,36 @@
type Options struct {
DutAddress string
DutPort int
+ // File path to a serialized jsonproto payload of DutTopology.
+ // This allows local complex lab setups (e.g. multi-dut) for local testing.
+ DutTopologyConfigPath string
+}
+
+// readJsonpb reads the jsonpb at path into m.
+func readJsonpb(path string, m proto.Message) error {
+ b, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ return jsonpb.Unmarshal(bytes.NewReader(b), m)
}
// newInventoryServer creates a new inventory service server to listen to rpc requests.
func newInventoryServer(l net.Listener, logger *log.Logger, options *Options) (*grpc.Server, error) {
dutTopology := &api.DutTopology{}
- if len(options.DutAddress) != 0 {
+
+ dutAddress := len(options.DutAddress) != 0
+ dutTopoConfig := len(options.DutTopologyConfigPath) != 0
+
+ if dutAddress && dutTopoConfig {
+ return nil, errors.New("DutAddress and DutTopologyConfigOptions options are mutally exclusive")
+ }
+
+ if dutTopoConfig {
+ if err := readJsonpb(options.DutTopologyConfigPath, dutTopology); err != nil {
+ return nil, err
+ }
+ } else if dutAddress {
dutTopology = &api.DutTopology{
Id: &api.DutTopology_Id{
Value: options.DutAddress,
diff --git a/src/chromiumos/test/lab/local/cmd/inventoryserver/inventoryserver_test.go b/src/chromiumos/test/lab/local/cmd/inventoryserver/inventoryserver_test.go
index a482763..b2c8150 100644
--- a/src/chromiumos/test/lab/local/cmd/inventoryserver/inventoryserver_test.go
+++ b/src/chromiumos/test/lab/local/cmd/inventoryserver/inventoryserver_test.go
@@ -7,10 +7,14 @@
import (
"bytes"
"context"
+ "io/ioutil"
"log"
"net"
+ "os"
+ "strings"
"testing"
+ "github.com/golang/protobuf/jsonpb"
"go.chromium.org/chromiumos/config/go/test/lab/api"
"google.golang.org/grpc"
)
@@ -73,3 +77,58 @@
t.Fatalf("Expected address: %s and port: %d; Got: %s", dutAddress, dutPort, ssh.String())
}
}
+
+// InventoryServer handles DutTopology config from file
+func TestInventoryServer_DutTopologyConfigOption(t *testing.T) {
+ dutAddress := "fake-hostname"
+ tmpFile, _ := ioutil.TempFile(os.TempDir(), "fakeduttopoconfig-")
+ marshal := &jsonpb.Marshaler{EmitDefaults: true, Indent: " "}
+ jsonOutput, _ := marshal.MarshalToString(&api.DutTopology{
+ Dut: &api.Dut{
+ DutType: &api.Dut_Chromeos{
+ Chromeos: &api.Dut_ChromeOS{
+ Ssh: &api.IpEndpoint{
+ Address: dutAddress,
+ },
+ },
+ },
+ },
+ })
+
+ dutTopoConfig := []byte(jsonOutput)
+ tmpFile.Write(dutTopoConfig)
+ tmpFile.Close()
+
+ defer os.Remove(tmpFile.Name())
+
+ dutTopology := getTopology(t, &Options{
+ DutTopologyConfigPath: tmpFile.Name(),
+ })
+
+ ssh := dutTopology.Dut.GetChromeos().GetSsh()
+
+ if ssh.Address != dutAddress {
+ t.Fatalf("Failed to load config from file %s", tmpFile.Name())
+ }
+}
+
+// InventoryServer errors on conflicting config/address options
+func TestInventoryServer_DutConfig_DutAddress_Exclusive(t *testing.T) {
+ var logBuf bytes.Buffer
+ l, err := net.Listen("tcp", ":0")
+ if err != nil {
+ t.Fatal("Failed to create a net listener: ", err)
+ }
+
+ svr, err := newInventoryServer(
+ l,
+ log.New(&logBuf, "", log.LstdFlags|log.LUTC),
+ &Options{
+ DutAddress: "fake",
+ DutTopologyConfigPath: "somepath",
+ },
+ )
+ if svr != nil || err == nil || !strings.Contains(err.Error(), "exclusive") {
+ t.Fatalf("Expected error for invalid server options")
+ }
+}
diff --git a/src/chromiumos/test/lab/local/cmd/inventoryserver/main.go b/src/chromiumos/test/lab/local/cmd/inventoryserver/main.go
index a346fde..b8721de 100644
--- a/src/chromiumos/test/lab/local/cmd/inventoryserver/main.go
+++ b/src/chromiumos/test/lab/local/cmd/inventoryserver/main.go
@@ -50,6 +50,7 @@
version := flag.Bool("version", false, "print version and exit")
dutAddress := flag.String("dut_address", "", "DUT address to connect to (see ip_endpoint.proto for format requirements)")
dutPort := flag.Int("dut_port", defaultSshPort, fmt.Sprintf("SSH port for the target DUT (default: %d)", defaultSshPort))
+ dutTopologyConfigPath := flag.String("dut_config", "", "Path to jsonproto serialized DutTopology config")
flag.Parse()
if *version {
@@ -74,8 +75,9 @@
l,
logger,
&Options{
- DutAddress: *dutAddress,
- DutPort: *dutPort,
+ DutAddress: *dutAddress,
+ DutPort: *dutPort,
+ DutTopologyConfigPath: *dutTopologyConfigPath,
})
if err != nil {
logger.Fatalln("Failed to start inventoryservice server: ", err)