blob: d41ecf2a72f389edf2b92a8d22d0b54b83d25d9f [file] [log] [blame] [edit]
// 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"
"time"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/ffx"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
type DeviceResolver interface {
// NodeNames returns a list of nodenames for a device.
NodeNames() []string
// Resolve the device's nodename into a hostname.
ResolveName(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
oldNodeName string
host string
}
// NewConstantAddressResolver constructs a fixed host.
func NewConstantHostResolver(ctx context.Context, nodeName string, host string) ConstantHostResolver {
oldNodeName, err := translateToOldNodeName(nodeName)
if err == nil {
logger.Infof(ctx, "translated device name %q to old nodename %q", nodeName, oldNodeName)
} else {
logger.Warningf(ctx, "could not translate %q into old device name: %v", nodeName, err)
oldNodeName = ""
}
return ConstantHostResolver{
nodeName: nodeName,
oldNodeName: oldNodeName,
host: host,
}
}
func (r ConstantHostResolver) NodeNames() []string {
if r.oldNodeName == "" {
return []string{r.nodeName}
} else {
return []string{r.nodeName, r.oldNodeName}
}
}
func (r ConstantHostResolver) ResolveName(ctx context.Context) (string, error) {
return r.host, 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
}
// DeviceFinderResolver uses `device-finder` to resolve a nodename into a hostname.
// The logic here should match get-fuchsia-device-addr (//tools/devshell/lib/vars.sh).
type DeviceFinderResolver struct {
deviceFinder *DeviceFinder
nodeName string
oldNodeName string
}
// NewDeviceFinderResolver constructs a new `DeviceFinderResolver` for the specific
func NewDeviceFinderResolver(ctx context.Context, deviceFinder *DeviceFinder, nodeName string) (*DeviceFinderResolver, error) {
if nodeName == "" {
entries, err := deviceFinder.List(ctx, Mdns)
if err != nil {
return nil, fmt.Errorf("failed to list devices: %w", err)
}
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
}
var oldNodeName string
// FIXME(http://fxbug.dev/71146) we can switch back to the resolver
// resolving a single name after we no longer support testing builds
// older than 2021-02-22.
if name, err := translateToOldNodeName(nodeName); err == nil {
logger.Infof(ctx, "translated device name %q to old nodename %q", nodeName, name)
oldNodeName = name
} else {
logger.Warningf(ctx, "could not translate %q into old device name: %v", nodeName, err)
}
return &DeviceFinderResolver{
deviceFinder: deviceFinder,
nodeName: nodeName,
oldNodeName: oldNodeName,
}, nil
}
func (r *DeviceFinderResolver) NodeNames() []string {
return []string{r.nodeName, r.oldNodeName}
}
func (r *DeviceFinderResolver) ResolveName(ctx context.Context) (string, error) {
entry, err := r.deviceFinder.Resolve(ctx, Mdns, r.NodeNames())
if err != nil {
return "", err
}
return entry.Address, nil
}
func (r *DeviceFinderResolver) WaitToFindDeviceInNetboot(ctx context.Context) (string, error) {
nodeNames := r.NodeNames()
// Wait for the device to be listening in netboot.
logger.Infof(ctx, "waiting for the to be listening on the nodenames: %v", nodeNames)
attempt := 0
for {
attempt += 1
if entry, err := r.deviceFinder.Resolve(ctx, Netboot, nodeNames); err == nil {
logger.Infof(ctx, "device %v is listening on %q", nodeNames, entry)
return entry.NodeName, nil
} else {
logger.Infof(ctx, "attempt %d failed to resolve nodenames %v: %v", attempt, nodeNames, err)
}
}
}
// 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, ffx *ffx.FFXTool, nodeName string) (*FfxResolver, error) {
if nodeName == "" {
entries, err := ffx.TargetList(ctx)
if err != nil {
return nil, fmt.Errorf("failed to list devices: %w", err)
}
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
}
var oldNodeName string
// FIXME(http://fxbug.dev/71146) we can switch back to the resolver
// resolving a single name after we no longer support testing builds
// older than 2021-02-22.
if name, err := translateToOldNodeName(nodeName); err == nil {
logger.Infof(ctx, "translated device name %q to old nodename %q", nodeName, name)
oldNodeName = name
} else {
logger.Warningf(ctx, "could not translate %q into old device name: %v", nodeName, err)
}
return &FfxResolver{
ffx: ffx,
nodeName: nodeName,
oldNodeName: oldNodeName,
}, nil
}
func (r *FfxResolver) NodeNames() []string {
return []string{r.nodeName, r.oldNodeName}
}
func (r *FfxResolver) ResolveName(ctx context.Context) (string, error) {
nodeNames := r.NodeNames()
logger.Infof(ctx, "resolving the nodenames %v", nodeNames)
targets, err := r.ffx.TargetListForNode(ctx, nodeNames)
if err != nil {
return "", err
}
logger.Infof(ctx, "resolved the nodenames %v to %v", nodeNames, targets)
if len(targets) == 0 {
return "", fmt.Errorf("no addresses found for nodenames: %v", nodeNames)
}
if len(targets) > 1 {
return "", fmt.Errorf("multiple addresses found for nodenames %v: %v", nodeNames, targets)
}
return targets[0].Addresses[0], nil
}
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
}
nodeNames := r.NodeNames()
// Wait for the device to be listening in netboot.
logger.Infof(ctx, "waiting for the to be listening on the nodenames: %v", nodeNames)
attempt := 0
for {
attempt += 1
if entries, err := r.ffx.TargetListForNode(ctx, nodeNames); 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 nodenames %v: %v", attempt, nodeNames, err)
}
}
}