blob: 39f973d39ef595a4adbc045bc9f971b6fde7ddfa [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 cli
import (
"context"
"flag"
"fmt"
"os"
"time"
"golang.org/x/crypto/ssh"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/device"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/ffx"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/util"
"go.fuchsia.dev/fuchsia/tools/botanist/constants"
"go.fuchsia.dev/fuchsia/tools/lib/retry"
)
type DeviceResolverMode = string
const (
// Resolve devices with ffx.
FfxResolver = "ffx"
)
type DeviceConfig struct {
sshKeyFile string
deviceFinderPath string
ffxPath string
ffx *ffx.FFXTool
deviceName string
deviceHostname string
deviceResolverMode DeviceResolverMode
deviceSshPort int
repoPort int
sshPrivateKey ssh.Signer
SerialSocketPath string
connectTimeout time.Duration
WorkaroundBrokenTimeSkip bool
testDataPath string
}
func NewDeviceConfig(fs *flag.FlagSet, testDataPath string) *DeviceConfig {
c := &DeviceConfig{}
c.testDataPath = testDataPath
fs.StringVar(&c.sshKeyFile, "ssh-private-key", os.Getenv(constants.SSHKeyEnvKey), "SSH private key file that can access the device")
fs.StringVar(&c.deviceName, "device", os.Getenv(constants.NodenameEnvKey), "device name")
fs.StringVar(&c.deviceHostname, "device-hostname", os.Getenv(constants.DeviceAddrEnvKey), "device hostname or IPv4/IPv6 address")
fs.StringVar(&c.deviceResolverMode, "device-resolver", FfxResolver, "device resolver (default: ffx)")
fs.StringVar(&c.ffxPath, "ffx-path", "host-tools/ffx", "ffx tool path")
fs.IntVar(&c.deviceSshPort, "device-ssh-port", 22, "device port")
fs.StringVar(&c.deviceFinderPath, "device-finder-path", "", "device-finder tool path")
fs.StringVar(&c.SerialSocketPath, "device-serial", "", "device serial path")
fs.DurationVar(&c.connectTimeout, "device-connect-timeout", 5*time.Second, "device connection timeout (default 5 seconds)")
fs.BoolVar(&c.WorkaroundBrokenTimeSkip, "workaround-broken-time-skip", false,
"whether to sleep for 15 seconds after pave and then reconnect, to work around a known networking bug, https://fxbug.dev/42154590")
fs.IntVar(&c.repoPort, "repo-port", 0, "default port to serve the repository")
environmentSerialPath := os.Getenv(constants.SerialSocketEnvKey)
if c.SerialSocketPath == "" && environmentSerialPath != "" {
c.SerialSocketPath = environmentSerialPath
}
return c
}
func (c *DeviceConfig) Validate() error {
for _, s := range []string{
c.sshKeyFile,
c.ffxPath,
c.SerialSocketPath,
} {
if err := util.ValidatePath(s); err != nil {
return err
}
}
return nil
}
func (c *DeviceConfig) FFXTool(ffxIsolateDir ffx.IsolateDir) (*ffx.FFXTool, error) {
if c.ffx == nil {
ffx, err := ffx.NewFFXTool(c.ffxPath, ffxIsolateDir)
if err != nil {
return nil, err
}
c.ffx = ffx
}
return c.ffx, nil
}
func (c *DeviceConfig) deviceResolver(
ctx context.Context,
ffxIsolateDir ffx.IsolateDir,
) (device.DeviceResolver, error) {
if c.deviceHostname != "" {
return device.NewConstantHostResolver(
ctx,
c.deviceName,
c.deviceHostname,
c.deviceSshPort,
), nil
}
switch c.deviceResolverMode {
case FfxResolver:
ffx, err := c.FFXTool(ffxIsolateDir)
if err != nil {
return nil, err
}
return device.NewFfxResolver(ctx, ffx, c.deviceName)
default:
return nil, fmt.Errorf("Invalid device-resolver mode %v", c.deviceResolverMode)
}
}
func (c *DeviceConfig) SSHPrivateKey() (ssh.Signer, error) {
if c.sshPrivateKey == nil {
if c.sshKeyFile == "" {
return nil, fmt.Errorf("ssh private key cannot be empty")
}
key, err := os.ReadFile(c.sshKeyFile)
if err != nil {
return nil, err
}
privateKey, err := ssh.ParsePrivateKey(key)
if err != nil {
return nil, err
}
c.sshPrivateKey = privateKey
}
return c.sshPrivateKey, nil
}
func (c *DeviceConfig) NewDeviceClient(
ctx context.Context,
ffxIsolateDir ffx.IsolateDir,
) (*device.Client, error) {
deviceResolver, err := c.deviceResolver(ctx, ffxIsolateDir)
if err != nil {
return nil, err
}
sshPrivateKey, err := c.SSHPrivateKey()
if err != nil {
return nil, err
}
connectBackoff := retry.NewConstantBackoff(c.connectTimeout)
var serialConn *device.SerialConn
if c.SerialSocketPath != "" {
serialConn, err = device.NewSerialConn(c.SerialSocketPath)
if err != nil {
return nil, err
}
}
client, err := device.NewClient(
ctx,
c.repoPort,
deviceResolver,
sshPrivateKey,
connectBackoff,
c.WorkaroundBrokenTimeSkip,
serialConn,
ffxIsolateDir,
)
if err != nil {
return nil, err
}
return client, nil
}