| 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 |
| } |