Adding DUT Server impl + Tests
Splitting up exec and fetch as two portions to have a working tester
earlier. Note that tests require that we create a wrapper for most ssh
items as they are structs and not interfaces and as such cannot be
mocked.
BUG=None
TEST=None
Change-Id: Ieb779551acf36ded1a88b049e0d0699f1595880e
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/2946488
Reviewed-by: C Shapiro <shapiroc@chromium.org>
Reviewed-by: Andrew Lamb <andrewlamb@chromium.org>
Reviewed-by: Jaques Clapauch <jaquesc@google.com>
Commit-Queue: Jaques Clapauch <jaquesc@google.com>
Tested-by: Jaques Clapauch <jaquesc@google.com>
Auto-Submit: Jaques Clapauch <jaquesc@google.com>
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutserver.go b/src/chromiumos/test/dut/cmd/dutserver/dutserver.go
index a38576d..3d65cc3 100644
--- a/src/chromiumos/test/dut/cmd/dutserver/dutserver.go
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutserver.go
@@ -6,41 +6,124 @@
package main
import (
- "chromiumos/lro"
+ "bytes"
+ "context"
+ "errors"
"log"
"net"
"go.chromium.org/chromiumos/config/go/test/api"
+ "golang.org/x/crypto/ssh"
"google.golang.org/grpc"
+
+ "chromiumos/test/dut/cmd/dutserver/dutssh"
)
// DutServiceServer implementation of dut_service.proto
type DutServiceServer struct {
- Manager *lro.Manager
- logger *log.Logger
+ logger *log.Logger
+ connection dutssh.ClientInterface
}
-// Create a new provision service server to listen to rpc requests.
-func newDutServiceServer(l net.Listener, logger *log.Logger) (*grpc.Server, error) {
+// newDutServiceServer creates a new dut service server to listen to rpc requests.
+func newDutServiceServer(l net.Listener, logger *log.Logger, conn dutssh.ClientInterface) *grpc.Server {
s := &DutServiceServer{
- Manager: lro.New(),
- logger: logger,
+ logger: logger,
+ connection: conn,
}
- defer s.Manager.Close()
+
server := grpc.NewServer()
api.RegisterDutServiceServer(server, s)
logger.Println("dutservice listen to request at ", l.Addr().String())
- return server, nil
+ return server
}
-// Remotely execute a command on the DUT.
+// ExecCommand remotely executes a command on the DUT.
func (s *DutServiceServer) ExecCommand(req *api.ExecCommandRequest, stream api.DutService_ExecCommandServer) error {
s.logger.Println("Received api.ExecCommandRequest: ", *req)
- return stream.Send(&api.ExecCommandResponse{})
+ resp := s.runCmd(req.Command)
+ return stream.Send(resp)
}
-// Remotely fetch crashes from the DUT.
+// FetchCrashes remotely fetches crashes from the DUT.
func (s *DutServiceServer) FetchCrashes(req *api.FetchCrashesRequest, stream api.DutService_FetchCrashesServer) error {
s.logger.Println("Received api.FetchCrashesRequest: ", *req)
return stream.Send(&api.FetchCrashesResponse{})
}
+
+// GetConnection resolves the dut address name to ip address and ssh into it
+func GetConnection(ctx context.Context, dutAddress string, wiringAddress string) (*ssh.Client, error) {
+ addr, err := dutssh.GetSSHAddr(ctx, dutAddress, wiringAddress)
+ if err != nil {
+ return nil, err
+ }
+
+ return ssh.Dial("tcp", addr, dutssh.GetSSHConfig())
+}
+
+// runCmd run remote command returning return value, stdout, stderr, and error if any
+func (s *DutServiceServer) runCmd(cmd string) *api.ExecCommandResponse {
+ session, err := s.connection.NewSession()
+ if err != nil {
+ return &api.ExecCommandResponse{
+ ExitInfo: createFailedToStartExitInfo(err),
+ }
+ }
+ defer session.Close()
+
+ var stdOut bytes.Buffer
+ var stdErr bytes.Buffer
+ session.SetStdout(&stdOut)
+ session.SetStderr(&stdErr)
+ err = session.Run(cmd)
+
+ return &api.ExecCommandResponse{
+ Stdout: stdOut.Bytes(),
+ Stderr: stdErr.Bytes(),
+ ExitInfo: getExitInfo(err),
+ }
+}
+
+// getExitInfo extracts exit info from Session Run's error
+func getExitInfo(runError error) *api.ExecCommandResponse_ExitInfo {
+ // If no error, command succeeded
+ if runError == nil {
+ return createCommandSucceededExitInfo()
+ }
+
+ // If ExitError, command ran but did not succeed
+ var ee *ssh.ExitError
+ if errors.As(runError, &ee) {
+ return createCommandFailedExitInfo(ee)
+ }
+
+ // Otherwise we assume command failed to start
+ return createFailedToStartExitInfo(runError)
+}
+
+func createFailedToStartExitInfo(err error) *api.ExecCommandResponse_ExitInfo {
+ return &api.ExecCommandResponse_ExitInfo{
+ Status: 42, // Contract dictates arbitrary response, thus 42 is as good as any number
+ Signaled: false,
+ Started: false,
+ ErrorMessage: err.Error(),
+ }
+}
+
+func createCommandSucceededExitInfo() *api.ExecCommandResponse_ExitInfo {
+ return &api.ExecCommandResponse_ExitInfo{
+ Status: 0,
+ Signaled: false,
+ Started: true,
+ ErrorMessage: "",
+ }
+}
+
+func createCommandFailedExitInfo(err *ssh.ExitError) *api.ExecCommandResponse_ExitInfo {
+ return &api.ExecCommandResponse_ExitInfo{
+ Status: int32(err.ExitStatus()),
+ Signaled: true,
+ Started: true,
+ ErrorMessage: "",
+ }
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutserver_test.go b/src/chromiumos/test/dut/cmd/dutserver/dutserver_test.go
index 7baeb4a..e5e52fc 100644
--- a/src/chromiumos/test/dut/cmd/dutserver/dutserver_test.go
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutserver_test.go
@@ -6,15 +6,50 @@
import (
"bytes"
+ "chromiumos/test/dut/cmd/dutserver/dutssh"
"context"
+ "errors"
+ "io"
"log"
"net"
"testing"
"go.chromium.org/chromiumos/config/go/test/api"
+ "golang.org/x/crypto/ssh"
"google.golang.org/grpc"
)
+type MockSession_Success struct {
+ Stdout io.Writer
+ Stderr io.Writer
+}
+
+func (s *MockSession_Success) Close() error {
+ return nil
+}
+func (s *MockSession_Success) SetStdout(writer io.Writer) {
+ s.Stdout = writer
+}
+func (s *MockSession_Success) SetStderr(writer io.Writer) {
+ s.Stderr = writer
+}
+
+func (s *MockSession_Success) Run(cmd string) error {
+ s.Stdout.Write([]byte("success!"))
+ s.Stderr.Write([]byte("not failed!"))
+ return nil
+}
+
+type MockConnection_Success struct{}
+
+func (c *MockConnection_Success) Close() error {
+ return nil
+}
+
+func (c *MockConnection_Success) NewSession() (dutssh.SessionInterface, error) {
+ return &MockSession_Success{}, nil
+}
+
// Tests if DutServiceServer can handle empty request without problem.
func TestDutServiceServer_Empty(t *testing.T) {
var logBuf bytes.Buffer
@@ -24,7 +59,8 @@
}
ctx := context.Background()
- srv, err := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC))
+ dutConn := &MockConnection_Success{}
+ srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
if err != nil {
t.Fatalf("Failed to start DutServiceServer: %v", err)
}
@@ -42,3 +78,294 @@
t.Fatalf("Failed at api.ExecCommand: %v", err)
}
}
+
+// Tests that a command executes successfully
+func TestDutServiceServer_CommandWorks(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)
+ }
+
+ ctx := context.Background()
+ dutConn := &MockConnection_Success{}
+ srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
+ if err != nil {
+ t.Fatalf("Failed to start DutServiceServer: %v", err)
+ }
+ go srv.Serve(l)
+ defer srv.Stop()
+
+ conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
+ if err != nil {
+ t.Fatalf("Failed to dial: %v", err)
+ }
+ defer conn.Close()
+
+ cl := api.NewDutServiceClient(conn)
+ stream, err := cl.ExecCommand(ctx, &api.ExecCommandRequest{})
+ if err != nil {
+ t.Fatalf("Failed at api.ExecCommand: %v", err)
+ }
+
+ resp := &api.ExecCommandResponse{}
+ err = stream.RecvMsg(resp)
+ if err != nil {
+ t.Fatalf("Failed at api.ExecCommand: %v", err)
+ }
+
+ if resp.ExitInfo.Status != 0 {
+ t.Fatalf("Expecting return type to be 0, instead got: %v", resp.ExitInfo.Status)
+ }
+
+ if string(resp.Stderr) != "not failed!" {
+ t.Fatalf("Expecting stderr to be \"not failed\", instead got %v", string(resp.Stderr))
+ }
+
+ if string(resp.Stdout) != "success!" {
+ t.Fatalf("Expecting stderr to be \"success\", instead got %v", string(resp.Stderr))
+ }
+
+ if resp.ExitInfo.Signaled {
+ t.Fatalf("Signalled should not be set!")
+ }
+
+ if !resp.ExitInfo.Started {
+ t.Fatalf("Started should be set!")
+ }
+}
+
+type MockSession_CommandFailed struct {
+ Stdout io.Writer
+ Stderr io.Writer
+}
+
+func (s *MockSession_CommandFailed) Close() error {
+ return nil
+}
+func (s *MockSession_CommandFailed) SetStdout(writer io.Writer) {
+ s.Stdout = writer
+}
+func (s *MockSession_CommandFailed) SetStderr(writer io.Writer) {
+ s.Stderr = writer
+}
+
+func (s *MockSession_CommandFailed) Run(cmd string) error {
+ s.Stdout.Write([]byte("not success!"))
+ s.Stderr.Write([]byte("failure!"))
+ wm := ssh.Waitmsg{}
+ return &ssh.ExitError{
+ Waitmsg: wm,
+ }
+}
+
+type MockConnection_CommandFailed struct{}
+
+func (c *MockConnection_CommandFailed) Close() error {
+ return nil
+}
+
+func (c *MockConnection_CommandFailed) NewSession() (dutssh.SessionInterface, error) {
+ return &MockSession_CommandFailed{}, nil
+}
+
+// Tests that a command does not execute successfully
+func TestDutServiceServer_CommandFails(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)
+ }
+
+ ctx := context.Background()
+ dutConn := &MockConnection_CommandFailed{}
+ srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
+ if err != nil {
+ t.Fatalf("Failed to start DutServiceServer: %v", err)
+ }
+ go srv.Serve(l)
+ defer srv.Stop()
+
+ conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
+ if err != nil {
+ t.Fatalf("Failed to dial: %v", err)
+ }
+ defer conn.Close()
+
+ cl := api.NewDutServiceClient(conn)
+ stream, err := cl.ExecCommand(ctx, &api.ExecCommandRequest{})
+ if err != nil {
+ t.Fatalf("Failed at api.ExecCommand: %v", err)
+ }
+
+ resp := &api.ExecCommandResponse{}
+ err = stream.RecvMsg(resp)
+ if err != nil {
+ t.Fatalf("Failed at api.ExecCommand: %v", err)
+ }
+
+ if string(resp.Stderr) != "failure!" {
+ t.Fatalf("Expecting stderr to be \"not success\", instead got %v", string(resp.Stderr))
+ }
+
+ if string(resp.Stdout) != "not success!" {
+ t.Fatalf("Expecting stderr to be \"failure\", instead got %v", string(resp.Stderr))
+ }
+
+ if !resp.ExitInfo.Signaled {
+ t.Fatalf("Signalled should be set!")
+ }
+
+ if !resp.ExitInfo.Started {
+ t.Fatalf("Started should be set!")
+ }
+}
+
+type MockSession_PreCommandFailure struct {
+ Stdout io.Writer
+ Stderr io.Writer
+}
+
+func (s *MockSession_PreCommandFailure) Close() error {
+ return nil
+}
+func (s *MockSession_PreCommandFailure) SetStdout(writer io.Writer) {
+ s.Stdout = writer
+}
+func (s *MockSession_PreCommandFailure) SetStderr(writer io.Writer) {
+ s.Stderr = writer
+}
+
+func (s *MockSession_PreCommandFailure) Run(cmd string) error {
+ s.Stdout.Write([]byte(""))
+ s.Stderr.Write([]byte(""))
+ return &ssh.ExitMissingError{}
+}
+
+type MockConnection_PreCommandFailure struct{}
+
+func (c *MockConnection_PreCommandFailure) Close() error {
+ return nil
+}
+
+func (c *MockConnection_PreCommandFailure) NewSession() (dutssh.SessionInterface, error) {
+ return &MockSession_PreCommandFailure{}, nil
+}
+
+// Tests that a command does not execute
+func TestDutServiceServer_PreCommandFails(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)
+ }
+
+ ctx := context.Background()
+ dutConn := &MockConnection_PreCommandFailure{}
+ srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
+ if err != nil {
+ t.Fatalf("Failed to start DutServiceServer: %v", err)
+ }
+ go srv.Serve(l)
+ defer srv.Stop()
+
+ conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
+ if err != nil {
+ t.Fatalf("Failed to dial: %v", err)
+ }
+ defer conn.Close()
+
+ cl := api.NewDutServiceClient(conn)
+ stream, err := cl.ExecCommand(ctx, &api.ExecCommandRequest{})
+ if err != nil {
+ t.Fatalf("Failed at api.ExecCommand: %v", err)
+ }
+
+ resp := &api.ExecCommandResponse{}
+ err = stream.RecvMsg(resp)
+ if err != nil {
+ t.Fatalf("Failed at api.ExecCommand: %v", err)
+ }
+
+ if string(resp.Stderr) != "" {
+ t.Fatalf("Expecting stderr to be empty, instead got %v", string(resp.Stderr))
+ }
+
+ if string(resp.Stdout) != "" {
+ t.Fatalf("Expecting stderr to be empty, instead got %v", string(resp.Stderr))
+ }
+
+ if resp.ExitInfo.Signaled {
+ t.Fatalf("Signalled should not be set!")
+ }
+
+ if resp.ExitInfo.Started {
+ t.Fatalf("Started should not be set!")
+ }
+}
+
+type MockConnection_NewSessionFailure struct{}
+
+func (c *MockConnection_NewSessionFailure) Close() error {
+ return nil
+}
+
+func (c *MockConnection_NewSessionFailure) NewSession() (dutssh.SessionInterface, error) {
+ return nil, errors.New("Session failed.")
+}
+
+// Tests that a session fails
+func TestDutServiceServer_NewSessionFails(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)
+ }
+
+ ctx := context.Background()
+ dutConn := &MockConnection_NewSessionFailure{}
+ srv := newDutServiceServer(l, log.New(&logBuf, "", log.LstdFlags|log.LUTC), dutConn)
+ if err != nil {
+ t.Fatalf("Failed to start DutServiceServer: %v", err)
+ }
+ go srv.Serve(l)
+ defer srv.Stop()
+
+ conn, err := grpc.Dial(l.Addr().String(), grpc.WithInsecure())
+ if err != nil {
+ t.Fatalf("Failed to dial: %v", err)
+ }
+ defer conn.Close()
+
+ cl := api.NewDutServiceClient(conn)
+ stream, err := cl.ExecCommand(ctx, &api.ExecCommandRequest{})
+ if err != nil {
+ t.Fatalf("Failed at api.ExecCommand: %v", err)
+ }
+
+ resp := &api.ExecCommandResponse{}
+ err = stream.RecvMsg(resp)
+ if err != nil {
+ t.Fatalf("Failed at api.ExecCommand: %v", err)
+ }
+
+ if string(resp.Stderr) != "" {
+ t.Fatalf("Expecting stderr to be empty, instead got %v", string(resp.Stderr))
+ }
+
+ if string(resp.Stdout) != "" {
+ t.Fatalf("Expecting stderr to be empty, instead got %v", string(resp.Stderr))
+ }
+
+ if resp.ExitInfo.Signaled {
+ t.Fatalf("Signalled should not be set!")
+ }
+
+ if resp.ExitInfo.Started {
+ t.Fatalf("Started should not be set!")
+ }
+
+ if resp.ExitInfo.ErrorMessage != "Session failed." {
+ t.Fatalf("Error message should be session failed, instead got %v", resp.ExitInfo.ErrorMessage)
+ }
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutssh/connection.go b/src/chromiumos/test/dut/cmd/dutserver/dutssh/connection.go
new file mode 100644
index 0000000..3e50619
--- /dev/null
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutssh/connection.go
@@ -0,0 +1,60 @@
+// Copyright 2021 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 dutssh
+
+import (
+ "io"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// This file only exists because go cannot mock structs and the ssh client
+// library does not provide interfaces for testing.
+type ClientInterface interface {
+ Close() error
+ NewSession() (SessionInterface, error)
+}
+
+type SSHClient struct {
+ *ssh.Client
+}
+
+func (c *SSHClient) Close() error {
+ return c.Client.Close()
+}
+
+func (c *SSHClient) NewSession() (SessionInterface, error) {
+ session, err := c.Client.NewSession()
+ if err != nil {
+ return nil, err
+ }
+ return &SSHSession{Session: session}, nil
+}
+
+type SessionInterface interface {
+ Close() error
+ SetStdout(writer io.Writer)
+ SetStderr(writer io.Writer)
+ Run(cmd string) error
+}
+
+type SSHSession struct {
+ *ssh.Session
+}
+
+func (s *SSHSession) Close() error {
+ return s.Session.Close()
+}
+func (s *SSHSession) SetStdout(writer io.Writer) {
+ s.Session.Stdout = writer
+}
+
+func (s *SSHSession) SetStderr(writer io.Writer) {
+ s.Session.Stderr = writer
+}
+
+func (s *SSHSession) Run(cmd string) error {
+ return s.Session.Run(cmd)
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutssh/network.go b/src/chromiumos/test/dut/cmd/dutserver/dutssh/network.go
new file mode 100644
index 0000000..d90ebb8
--- /dev/null
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutssh/network.go
@@ -0,0 +1,55 @@
+// Copyright 2021 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 dutssh
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "go.chromium.org/chromiumos/config/go/api/test/tls"
+ "golang.org/x/crypto/ssh"
+ "google.golang.org/grpc"
+)
+
+// GetSSHAddr returns the SSH address to use for the DUT, through the wiring service.
+func GetSSHAddr(ctx context.Context, name string, wiringAddress string) (string, error) {
+ c, err := createWiringClient(wiringAddress)
+ if err != nil {
+ return "", err
+ }
+ resp, err := c.OpenDutPort(ctx, &tls.OpenDutPortRequest{
+ Name: name,
+ Port: 22,
+ })
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("%s:%d", resp.GetAddress(), resp.GetPort()), nil
+}
+
+// GetSSHConfig construct a static ssh config
+func GetSSHConfig() *ssh.ClientConfig {
+ return &ssh.ClientConfig{
+ User: "root",
+ // We don't care about the host key for DUTs.
+ // Attackers intercepting our connections to DUTs is not part
+ // of our attack profile.
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+ Timeout: 5 * time.Second,
+ // Use the well known testing RSA key as the default SSH auth
+ // method.
+ Auth: []ssh.AuthMethod{ssh.PublicKeys(testingSSHSigner)},
+ }
+}
+
+// createWiringClient creates a client to wiring service
+func createWiringClient(wiringAddress string) (tls.WiringClient, error) {
+ conn, err := grpc.Dial(wiringAddress, grpc.WithInsecure())
+ if err != nil {
+ return nil, err
+ }
+ return tls.WiringClient(tls.NewWiringClient(conn)), nil
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/dutssh/ssh_keys.go b/src/chromiumos/test/dut/cmd/dutserver/dutssh/ssh_keys.go
new file mode 100644
index 0000000..bf7e8b1
--- /dev/null
+++ b/src/chromiumos/test/dut/cmd/dutserver/dutssh/ssh_keys.go
@@ -0,0 +1,54 @@
+// Copyright 2021 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 dutssh
+
+import (
+ "fmt"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// The provided key is copied from
+// https://chromium.googlesource.com/chromiumos/chromite/+/master/ssh_keys/testing_rsa
+// It's a well known "private" key used widely in Chrome OS testing.
+const sshKeyContent = `
+-----BEGIN RSA PRIVATE KEY-----
+MIIEoAIBAAKCAQEAvsNpFdK5lb0GfKx+FgsrsM/2+aZVFYXHMPdvGtTz63ciRhq0
+Jnw7nln1SOcHraSz3/imECBg8NHIKV6rA+B9zbf7pZXEv20x5Ul0vrcPqYWC44PT
+tgsgvi8s0KZUZN93YlcjZ+Q7BjQ/tuwGSaLWLqJ7hnHALMJ3dbEM9fKBHQBCrG5H
+OaWD2gtXj7jp04M/WUnDDdemq/KMg6E9jcrJOiQ39IuTpas4hLQzVkKAKSrpl6MY
+2etHyoNarlWhcOwitArEDwf3WgnctwKstI/MTKB5BTpO2WXUNUv4kXzA+g8/l1al
+jIG13vtd9A/IV3KFVx/sLkkjuZ7z2rQXyNKuJwIBIwKCAQA79EWZJPh/hI0CnJyn
+16AEXp4T8nKDG2p9GpCiCGnq6u2Dvz/u1pZk97N9T+x4Zva0GvJc1vnlST7objW/
+Y8/ET8QeGSCT7x5PYDqiVspoemr3DCyYTKPkADKn+cLAngDzBXGHDTcfNP4U6xfr
+Qc5JK8BsFR8kApqSs/zCU4eqBtp2FVvPbgUOv3uUrFnjEuGs9rb1QZ0K6o08L4Cq
+N+e2nTysjp78blakZfqlurqTY6iJb0ImU2W3T8sV6w5GP1NT7eicXLO3WdIRB15a
+evogPeqtMo8GcO62wU/D4UCvq4GNEjvYOvFmPzXHvhTxsiWv5KEACtleBIEYmWHA
+POwrAoGBAOKgNRgxHL7r4bOmpLQcYK7xgA49OpikmrebXCQnZ/kZ3QsLVv1QdNMH
+Rx/ex7721g8R0oWslM14otZSMITCDCMWTYVBNM1bqYnUeEu5HagFwxjQ2tLuSs8E
+SBzEr96JLfhwuBhDH10sQqn+OQG1yj5acs4Pt3L4wlYwMx0vs1BxAoGBANd9Owro
+5ONiJXfKNaNY/cJYuLR+bzGeyp8oxToxgmM4UuA4hhDU7peg4sdoKJ4XjB9cKMCz
+ZGU5KHKKxNf95/Z7aywiIJEUE/xPRGNP6tngRunevp2QyvZf4pgvACvk1tl9B3HH
+7J5tY/GRkT4sQuZYpx3YnbdP5Y6Kx33BF7QXAoGAVCzghVQR/cVT1QNhvz29gs66
+iPIrtQnwUtNOHA6i9h+MnbPBOYRIpidGTaqEtKTTKisw79JjJ78X6TR4a9ML0oSg
+c1K71z9NmZgPbJU25qMN80ZCph3+h2f9hwc6AjLz0U5wQ4alP909VRVIX7iM8paf
+q59wBiHhyD3J16QAxhsCgYBu0rCmhmcV2rQu+kd4lCq7uJmBZZhFZ5tny9MlPgiK
+zIJkr1rkFbyIfqCDzyrU9irOTKc+iCUA25Ek9ujkHC4m/aTU3lnkNjYp/OFXpXF3
+XWZMY+0Ak5uUpldG85mwLIvATu3ivpbyZCTFYM5afSm4StmaUiU5tA+oZKEcGily
+jwKBgBdFLg+kTm877lcybQ04G1kIRMf5vAXcConzBt8ry9J+2iX1ddlu2K2vMroD
+1cP/U/EmvoCXSOGuetaI4UNQwE/rGCtkpvNj5y4twVLh5QufSOl49V0Ut0mwjPXw
+HfN/2MoO07vQrjgsFylvrw9A79xItABaqKndlmqlwMZWc9Ne
+-----END RSA PRIVATE KEY-----
+`
+
+var testingSSHSigner ssh.Signer
+
+func init() {
+ var err error
+ testingSSHSigner, err = ssh.ParsePrivateKey([]byte(sshKeyContent))
+ if err != nil {
+ panic(fmt.Sprintf("The well known RSA key: %v", err.Error()))
+ }
+}
diff --git a/src/chromiumos/test/dut/cmd/dutserver/main.go b/src/chromiumos/test/dut/cmd/dutserver/main.go
index 0747181..51cb7a2 100644
--- a/src/chromiumos/test/dut/cmd/dutserver/main.go
+++ b/src/chromiumos/test/dut/cmd/dutserver/main.go
@@ -7,6 +7,8 @@
package main
import (
+ "chromiumos/test/dut/cmd/dutserver/dutssh"
+ "context"
"flag"
"fmt"
"io"
@@ -46,14 +48,25 @@
func main() {
os.Exit(func() int {
- version := flag.Bool("version", false, "print version and exit")
+ flag.NewFlagSet("version", flag.ExitOnError)
+
+ dutAddress := flag.String("dut_address", "", "Dut to connect to")
+ wiringAddress := flag.String("wiring_address", "", "Address to TLW")
flag.Parse()
- if *version {
+ if os.Args[1] == "version" {
fmt.Println("dutservice version ", Version)
return 0
}
+ if *dutAddress != "" {
+ fmt.Println("A DUT address must be valid")
+ }
+
+ if *wiringAddress != "" {
+ fmt.Println("A Wiring address must be valid")
+ }
+
logFile, err := createLogFile()
if err != nil {
log.Fatalln("Failed to create log file: ", err)
@@ -67,10 +80,16 @@
logger.Fatalln("Failed to create a net listener: ", err)
return 2
}
- server, err := newDutServiceServer(l, logger)
+
+ ctx := context.Background()
+ conn, err := GetConnection(ctx, *dutAddress, *wiringAddress)
if err != nil {
- logger.Fatalln("Failed to start dutserver: ", err)
+ logger.Fatalln("Failed to connect to dut: ", err)
+ return 2
}
+ defer conn.Close()
+
+ server := newDutServiceServer(l, logger, &dutssh.SSHClient{Client: conn})
err = server.Serve(l)
if err != nil {