[devices] Add SSH and separate powercycle
This change does two things:
1) I removed SSH reboot to avoid some catalyst errors. I realize
now that these are errors we should surface, so I added it back.
2) I separated out of band reboot functions into their own powercycle
function.
Change-Id: I1cdb7abea56fed765680656072e3b193badf71a8
diff --git a/devices/device.go b/devices/device.go
index ec73983..a760618 100644
--- a/devices/device.go
+++ b/devices/device.go
@@ -144,13 +144,21 @@
defer t.serial.Close()
}
if t.config.Power != nil {
- if err := t.config.Power.RebootDevice(t.Nodename(), t.serial); err != nil {
+ if err := t.config.Power.RebootDevice(t.signers, t.Nodename(), t.serial); err != nil {
return fmt.Errorf("failed to reboot the device: %v", err)
}
}
return nil
}
+// Powercycle uses an out of band method to powercycle the target.
+func (t *DeviceTarget) Powercycle(ctx context.Context) error {
+ if t.config.Power != nil {
+ return t.config.Power.Powercycle(t.Nodename())
+ }
+ return nil
+}
+
func parseOutSigners(keyPaths []string) ([]ssh.Signer, error) {
if len(keyPaths) == 0 {
return nil, errors.New("must supply SSH keys in the config")
diff --git a/devices/power.go b/devices/power.go
index db4bd34..e6006e0 100644
--- a/devices/power.go
+++ b/devices/power.go
@@ -5,9 +5,13 @@
package devices
import (
+ "context"
"errors"
"fmt"
"io"
+
+ "go.fuchsia.dev/fuchsia/tools/net/sshutil"
+ "golang.org/x/crypto/ssh"
)
// TODO(IN-977) Clean this up per suggestions in go/fxr/251550
@@ -47,19 +51,29 @@
reboot() error
}
+type SshRebooter struct {
+ nodename string
+ signers []ssh.Signer
+}
+
type SerialRebooter struct {
serial io.ReadWriter
}
// RebootDevice attempts to reboot the specified device into recovery, and
// additionally uses the given configuration to reboot the device if specified.
-func (c PowerClient) RebootDevice(nodename string, serial io.ReadWriter) error {
+func (c PowerClient) RebootDevice(signers []ssh.Signer, nodename string, serial io.ReadWriter) error {
var rebooter Rebooter
if serial != nil {
rebooter = NewSerialRebooter(serial)
- return rebooter.reboot()
+ } else {
+ rebooter = NewSSHRebooter(nodename, signers)
}
+ return rebooter.reboot()
+}
+// Powercycle the device using an out of band method
+func (c PowerClient) Powercycle(nodename string) error {
switch c.Type {
case "amt":
return AMTReboot(c.Host, c.Username, c.Password)
@@ -68,7 +82,7 @@
case "pdu":
return PDUReboot(c.PDUPort, c.Host, c.Username, c.Password)
default:
- return errors.New(fmt.Sprintf("%v does not have serial, AMT, or WOL support. Cannot reboot.", nodename))
+ return errors.New(fmt.Sprintf("%v does not AMT, WOL, or PDU support. Cannot powercycle.", nodename))
}
}
@@ -83,3 +97,47 @@
return err
}
+func NewSSHRebooter(nodename string, signers []ssh.Signer) *SshRebooter {
+ return &SshRebooter{
+ nodename: nodename,
+ signers: signers,
+ }
+}
+
+func (s *SshRebooter) reboot() error {
+ config, err := sshutil.DefaultSSHConfigFromSigners(s.signers...)
+ if err != nil {
+ return err
+ }
+
+ client, err := sshutil.ConnectToNode(context.Background(), s.nodename, config)
+ if err != nil {
+ return err
+ }
+
+ defer client.Close()
+
+ session, err := client.NewSession()
+ if err != nil {
+ return err
+ }
+
+ defer session.Close()
+
+ // Invoke `dm reboot-recovery` with a 2 second delay in the background, then exit the SSH shell.
+ // This prevents the SSH connection from hanging waiting for `dm reboot-recovery` to return.
+ err = session.Start("{ sleep 2; dm reboot-recovery; } >/dev/null & exit")
+ if err != nil {
+ return err
+ }
+
+ done := make(chan error)
+ go func() {
+ done <- session.Wait()
+ }()
+
+ select {
+ case err := <-done:
+ return err
+ }
+}