blob: 6b503a607282ae2829d65f82ec1a58b816f3a9d5 [file] [log] [blame]
// Copyright 2021 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 device
import (
"context"
"fmt"
"net"
"strconv"
"time"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/ffx"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
type DeviceResolver interface {
// NodeName returns a nodename for a device.
NodeName() string
// Resolve the device's nodename into an ssh address.
ResolveSshAddress(ctx context.Context) (string, error)
// Block until the device appears to be in fastboot.
WaitToFindDeviceInFastboot(ctx context.Context) (string, error)
// Block until the device appears to be in netboot.
WaitToFindDeviceInNetboot(ctx context.Context) (string, error)
}
// ConstnatAddressResolver returns a fixed hostname for the specified nodename.
type ConstantHostResolver struct {
nodeName string
host string
sshPort int
}
// NewConstantAddressResolver constructs a fixed host.
func NewConstantHostResolver(
ctx context.Context,
nodeName string,
host string,
sshPort int,
) ConstantHostResolver {
return ConstantHostResolver{
nodeName: nodeName,
host: host,
sshPort: sshPort,
}
}
func (r ConstantHostResolver) NodeName() string {
return r.nodeName
}
func (r ConstantHostResolver) ResolveSshAddress(ctx context.Context) (string, error) {
return net.JoinHostPort(r.host, strconv.Itoa(r.sshPort)), nil
}
func (r ConstantHostResolver) WaitToFindDeviceInFastboot(ctx context.Context) (string, error) {
// We have no way to tell if the device is in fastboot, so just exit.
logger.Warningf(ctx, "ConstantHostResolver cannot tell if device is in fastboot, assuming nodename is %s", r.nodeName)
return r.nodeName, nil
}
func (r ConstantHostResolver) WaitToFindDeviceInNetboot(ctx context.Context) (string, error) {
// We have no way to tell if the device is in netboot, so just exit.
logger.Warningf(ctx, "ConstantHostResolver cannot tell if device is in netboot, assuming nodename is %s", r.nodeName)
return r.nodeName, nil
}
// FfxResolver uses `ffx target list` to resolve a nodename into a hostname.
type FfxResolver struct {
ffx *ffx.FFXTool
nodeName string
oldNodeName string
}
// NewFffResolver constructs a new `FfxResolver` for the specific nodename.
func NewFfxResolver(
ctx context.Context,
ffxTool *ffx.FFXTool,
nodeName string,
) (*FfxResolver, error) {
if nodeName == "" {
var entries []ffx.TargetEntry
var err error
for i := 0; i < 10; i++ {
entries, err = ffxTool.TargetList(ctx)
if err != nil {
return nil, fmt.Errorf("failed to list devices: %w", err)
}
if len(entries) > 0 {
break
}
logger.Infof(ctx, "no devices found, retrying")
time.Sleep(5 * time.Second)
}
if len(entries) == 0 {
return nil, fmt.Errorf("no devices found")
}
if len(entries) != 1 {
return nil, fmt.Errorf("cannot use empty nodename with multiple devices: %v", entries)
}
nodeName = entries[0].NodeName
}
return &FfxResolver{
ffx: ffxTool,
nodeName: nodeName,
}, nil
}
func (r *FfxResolver) NodeName() string {
return r.nodeName
}
func (r *FfxResolver) ResolveName(ctx context.Context) (string, error) {
nodeName := r.NodeName()
logger.Infof(ctx, "resolving the nodename %v hostname", nodeName)
targets, err := r.ffx.TargetListForNode(ctx, nodeName)
if err != nil {
return "", err
}
logger.Infof(ctx, "resolved the nodename %v to %v", nodeName, targets)
if len(targets) == 0 {
return "", fmt.Errorf("no addresses found for nodename: %v", nodeName)
}
if len(targets) > 1 {
return "", fmt.Errorf("multiple addresses found for nodename %v: %v", nodeName, targets)
}
target := targets[0]
if len(target.Addresses) == 0 {
return "", fmt.Errorf("no address found for nodename %v: %v", nodeName, target)
}
return target.Addresses[0], nil
}
func (r *FfxResolver) ResolveSshAddress(ctx context.Context) (string, error) {
nodeName := r.NodeName()
logger.Infof(ctx, "resolving the nodename %v ssh address", nodeName)
addr, err := r.ffx.TargetGetSshAddress(ctx, nodeName)
if err != nil {
return "", fmt.Errorf("no ssh address found for nodename %v: %w", nodeName, err)
}
return addr, nil
}
func (r *FfxResolver) WaitToFindDeviceInFastboot(ctx context.Context) (string, error) {
nodeName := r.NodeName()
// Wait for the device to be listening in netboot.
logger.Infof(ctx, "waiting for the device to be listening on the nodename: %v", nodeName)
attempt := 0
for {
attempt += 1
if entries, err := r.ffx.TargetListForNode(ctx, nodeName); err == nil {
for _, entry := range entries {
logger.Infof(ctx, "device is in %v", entry.TargetState)
if entry.TargetState == "Fastboot" {
logger.Infof(ctx, "device %v is listening on %q", entry.NodeName, entry)
return entry.NodeName, nil
}
}
logger.Infof(ctx, "attempt %d waiting for device to boot into fastboot", attempt)
time.Sleep(5 * time.Second)
} else {
logger.Infof(ctx, "attempt %d failed to resolve nodename %v: %v", attempt, nodeName, err)
}
}
}
func (r *FfxResolver) WaitToFindDeviceInNetboot(ctx context.Context) (string, error) {
// Exit early if ffx is not configured to listen for devices in zedboot.
supportsZedbootDiscovery, err := r.ffx.SupportsZedbootDiscovery(ctx)
if err != nil {
return "", err
}
if !supportsZedbootDiscovery {
logger.Warningf(ctx, "ffx not configured to listen for devices in zedboot, assuming nodename is %s", r.nodeName)
return r.nodeName, nil
}
nodeName := r.NodeName()
// Wait for the device to be listening in netboot.
logger.Infof(ctx, "waiting for the to be listening on the nodename: %v", nodeName)
attempt := 0
for {
attempt += 1
if entries, err := r.ffx.TargetListForNode(ctx, nodeName); err == nil {
for _, entry := range entries {
logger.Infof(ctx, "device is in %v", entry.TargetState)
if entry.TargetState == "Zedboot (R)" {
logger.Infof(ctx, "device %v is listening on %q", entry.NodeName, entry)
return entry.NodeName, nil
}
}
logger.Infof(ctx, "attempt %d waiting for device to boot into zedboot", attempt)
time.Sleep(5 * time.Second)
} else {
logger.Infof(ctx, "attempt %d failed to resolve nodename %v: %v", attempt, nodeName, err)
}
}
}