blob: 5076f9439fa684222ba0f20db262ad4f803cf378 [file] [log] [blame]
package devices
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"syscall"
"time"
"go.fuchsia.dev/infra/devices/power"
"go.fuchsia.dev/tools/netboot"
)
// Vim represents a VIM2 (and eventually a VIM3)
type Vim struct {
fuchsiaDevice
}
// NewVim initializes a Vim for use by other infra utilities.
func NewVim(config DeviceConfig, bootserverCmdStub []string) (*Vim, error) {
v := &Vim{
fuchsiaDevice{
power: power.NewWOLPowerManager(config.Network.Mac),
},
}
if err := initFuchsiaDevice(&v.fuchsiaDevice, config, bootserverCmdStub); err != nil {
return nil, err
}
// All transitions in Vim attempt to get the device into zedboot.
transitionMap := map[DeviceState]*Transition{
Initial: &Transition{
PerformAction: v.sshRebootRecovery,
Validate: v.checkTransitionSuccess,
SuccessState: Healthy,
FailureState: "ssh_failed",
},
"ssh_failed": &Transition{
PerformAction: v.serialRebootRecovery,
Validate: v.checkTransitionSuccess,
SuccessState: Healthy,
FailureState: "serial_failed",
},
"serial_failed": &Transition{
PerformAction: v.Powercycle,
Validate: v.checkTransitionSuccess,
SuccessState: Healthy,
FailureState: Unrecoverable,
},
}
v.transitionMap = transitionMap
return v, nil
}
// checkTransitionSuccess is the validation function used by all transitions
// performed by Vim. It checks that the device is in zedboot.
func (v *Vim) checkTransitionSuccess(ctx context.Context) error {
// Give the device time to transition.
time.Sleep(1 * time.Minute)
client := netboot.NewClient(netbootTimeout)
return deviceInZedboot(client, v.nodename)
}
// ToTaskState puts the Vim into a state in which it can run a test task.
func (v *Vim) ToTaskState(ctx context.Context) error {
cmd := exec.Command(v.BootserverCmd[0], v.BootserverCmd[1:]...)
// Spin up handler to exit subprocess cleanly on sigterm.
processDone := make(chan bool, 1)
go func() {
select {
case <-processDone:
case <-ctx.Done():
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
log.Printf("exited cmd with error %v", err)
}
}
}()
// Ensure the context still exists before running the subprocess.
if ctx.Err() != nil {
return nil
}
// Run the bootserver.
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
processDone <- true
if !cmd.ProcessState.Success() {
return fmt.Errorf("bootserver for %s exited with exit code %d", v.nodename, cmd.ProcessState.ExitCode())
}
return nil
}