| package powercycle |
| |
| import ( |
| "bytes" |
| "fmt" |
| "io" |
| "strings" |
| |
| "go.skia.org/infra/go/sklog" |
| "go.skia.org/infra/go/util" |
| "golang.org/x/crypto/ssh" |
| ) |
| |
| // For details about the Ubiquiti EdgeSwitch see |
| // https://dl.ubnt.com/guides/edgemax/EdgeSwitch_CLI_Command_Reference_UG.pdf |
| |
| const ( |
| // EdgeSwitch default user and password. |
| DEFAULT_USER = "ubnt" |
| ) |
| |
| // The CommandRunner interface adds a layer of abstraction around a device |
| // that can have commands executed on it. |
| type CommandRunner interface { |
| // ExecCmds executes a series of commands and returns the accumulated |
| // output of all commands. |
| ExecCmds(cmds []string) ([]string, error) |
| } |
| |
| // EdgeSwitchClient implements the CommandRunner interface. |
| type EdgeSwitchClient struct { |
| ipaddress string |
| } |
| |
| // NewEdgeSwitchClient connects to the EdgeSwitch identified by the given |
| // configuration and returns a new instance of EdgeSwitchClient. |
| func NewEdgeSwitchClient(ipaddress string) *EdgeSwitchClient { |
| return &EdgeSwitchClient{ipaddress: ipaddress} |
| } |
| |
| // newClient returns a new ssh client. |
| func (e *EdgeSwitchClient) newClient() (*ssh.Client, error) { |
| sshConfig := &ssh.ClientConfig{ |
| User: DEFAULT_USER, |
| Auth: []ssh.AuthMethod{ssh.Password(DEFAULT_USER)}, |
| HostKeyCallback: ssh.InsecureIgnoreHostKey(), |
| } |
| |
| client, err := ssh.Dial("tcp", e.ipaddress, sshConfig) |
| if err != nil { |
| return nil, err |
| } |
| return client, nil |
| } |
| |
| // ExecCmds, see CommandRunner |
| func (e *EdgeSwitchClient) ExecCmds(cmds []string) ([]string, error) { |
| // The EdgeSwitch server doesn't like to re-use a client. So we create |
| // a new connection for every series of commands. |
| client, err := e.newClient() |
| if err != nil { |
| return nil, err |
| } |
| defer util.Close(client) |
| |
| session, err := client.NewSession() |
| if err != nil { |
| return nil, err |
| } |
| defer util.Close(session) |
| |
| // Set a terminal with many lines so we are not paginated. |
| if err := session.RequestPty("xterm", 80, 5000, nil); err != nil { |
| return nil, fmt.Errorf("Error: Could not retrieve pseudo terminal: %s", err) |
| } |
| |
| stdinPipe, err := session.StdinPipe() |
| if err != nil { |
| return nil, err |
| } |
| |
| stdoutPipe, err := session.StdoutPipe() |
| if err != nil { |
| return nil, err |
| } |
| |
| if err := session.Shell(); err != nil { |
| return nil, err |
| } |
| |
| // Switch to exec mode. |
| if _, err := stdinPipe.Write([]byte("enable\n")); err != nil { |
| return nil, err |
| } |
| |
| // Execute the commands. |
| for _, cmd := range cmds { |
| sklog.Infof("Executing: %s", cmd) |
| if _, err := stdinPipe.Write([]byte(cmd + "\n")); err != nil { |
| return nil, err |
| } |
| } |
| |
| // Switch out of exec mode and leave the shell. |
| if _, err := stdinPipe.Write([]byte("exit\nexit\n")); err != nil { |
| return nil, err |
| } |
| |
| // Get the output and return it. |
| var buf bytes.Buffer |
| if _, err := io.Copy(&buf, stdoutPipe); err != nil { |
| return nil, err |
| } |
| |
| // Strip out empty lines and all lines with the prompt. |
| lines := strings.Split(buf.String(), "\n") |
| ret := make([]string, 0, len(lines)) |
| for _, line := range lines { |
| oneLine := strings.TrimSpace(line) |
| if (oneLine == "") || (strings.HasPrefix(oneLine, "(UBNT EdgeSwitch)")) { |
| continue |
| } |
| ret = append(ret, oneLine) |
| } |
| return ret, nil |
| } |
| |
| // The following are convenience methods for common functionality and |
| // act as example usage. |
| |
| // TurnOffPort disables PoE at the given port. |
| func TurnOffPort(client CommandRunner, port int) error { |
| _, err := client.ExecCmds([]string{ |
| "configure", |
| "interface " + fmt.Sprintf("0/%d", port), |
| "poe opmode shutdown", |
| "exit", // leave the interface config mode (entered via 'interface ...') |
| "exit", // leave the global configuration mode (entered via 'configure') |
| }) |
| return err |
| } |
| |
| // TurnOnPort enables PoE at the given port. |
| func TurnOnPort(client CommandRunner, port int) error { |
| _, err := client.ExecCmds([]string{ |
| "configure", |
| "interface " + fmt.Sprintf("0/%d", port), |
| "poe opmode auto", |
| "exit", // leave the interface config mode (entered via 'interface ...') |
| "exit", // leave the global configuration mode (entered via 'configure') |
| }) |
| return err |
| } |
| |
| // Ping runs a simple command to make sure the connection works. |
| func Ping(client CommandRunner) error { |
| sklog.Infof("Executing ping.") |
| output, err := client.ExecCmds([]string{ |
| "show clock", |
| }) |
| sklog.Infof("OUT:%s", strings.Join(output, "\n")) |
| return err |
| } |