blob: 35f32ed997e2c0426396f2962a108f2867352359 [file] [log] [blame]
// Copyright 2019 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 power
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"net/url"
)
// PDUConn encapsultes PDU connection settings.
type PDUConn struct {
// Client is the client used to perform all requests.
Client *http.Client
// Host is the network hostname of the PDU.
Host string
// Username is the username by which we can log in to the PDU.
Username string
// Password is the password by which we can log in to the PDU.
Password string
// token is the authentication token obtained by Login.
token string
}
type message struct {
ID int `json:"msgid,omitempty"`
Reply string `json:"reply,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
Error struct {
Name string `json:"name"`
Message string `json:"message"`
Status int `json:"status"`
} `json:"error,omitempty"`
}
// NewPDUConn initializes and returns a new PDUConn struct.
func NewPDUConn(host string, uname string, pwd string) *PDUConn {
return &PDUConn{
Client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
},
Host: host,
Username: uname,
Password: pwd,
}
}
// Login performs a login and obtains authorization token used for other calls.
func (c *PDUConn) Login() error {
q := make(url.Values)
q.Set("username", c.Username)
q.Set("password", c.Password)
u := url.URL{
Scheme: "https",
Host: c.Host,
Path: "/api/AuthenticationControllers/login",
RawQuery: q.Encode(),
}
l := struct {
Username string `json:"username"`
Password string `json:"password"`
}{
Username: c.Username,
Password: c.Password,
}
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(l); err != nil {
return err
}
req, err := http.NewRequest("POST", u.String(), b)
req.Header.Add("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
if err != nil {
return err
}
resp, err := c.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
var msg message
err = json.NewDecoder(resp.Body).Decode(&msg)
if resp.StatusCode != http.StatusCreated {
if err != nil {
return fmt.Errorf("request failed: %d", resp.StatusCode)
} else {
return fmt.Errorf("request failed: %s", msg.Error.Message)
}
}
if token, ok := resp.Header["Authorization"]; ok {
c.token = token[0]
}
return nil
}
// PDUReboot powercycles the given outlet.
func PDUReboot(outlet int, host string, uname string, pwd string) error {
conn := NewPDUConn(host, uname, pwd)
if err := conn.Login(); err != nil {
return err
}
defer conn.Logout()
return conn.Loads(outlet, "Cycle")
}
// Loads changes the outlet state.
func (c *PDUConn) Loads(outlet int, state string) error {
u := url.URL{
Scheme: "https",
Host: c.Host,
Path: fmt.Sprintf("/api/device/loads/%d", outlet),
}
s := struct {
LoadFireState string `json:"loadFireState"`
}{
LoadFireState: state,
}
b := new(bytes.Buffer)
if err := json.NewEncoder(b).Encode(s); err != nil {
return err
}
req, err := http.NewRequest("PUT", u.String(), b)
if err != nil {
return err
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Authorization", c.token)
req.Header.Set("Content-Type", "application/json")
resp, err := c.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
var msg message
err = json.NewDecoder(resp.Body).Decode(&msg)
if resp.StatusCode != http.StatusOK {
if err != nil {
return fmt.Errorf("request failed: %d", resp.StatusCode)
} else {
return fmt.Errorf("request failed: %s", msg.Error.Message)
}
}
return nil
}
// Logout performs a logout and discards the authorization token.
func (c *PDUConn) Logout() error {
u := url.URL{
Scheme: "https",
Host: c.Host,
Path: "/api/AuthenticationControllers/logout",
}
req, err := http.NewRequest("POST", u.String(), nil)
req.Header.Add("Accept", "application/json")
req.Header.Add("Authorization", c.token)
req.Header.Set("Content-Type", "application/json")
if err != nil {
return err
}
resp, err := c.Client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
var msg message
err = json.NewDecoder(resp.Body).Decode(&msg)
if resp.StatusCode != http.StatusOK {
if err != nil {
return fmt.Errorf("request failed: %d", resp.StatusCode)
} else {
return fmt.Errorf("request failed: %s", msg.Error.Message)
}
}
c.token = ""
return nil
}
type PDUPowerManager struct {
devicePort int
pduIp string
pduUser string
pduPwd string
}
func NewPDUPowerManager(ip string, user string, pwd string, port int) *PDUPowerManager {
return &PDUPowerManager{
devicePort: port,
pduIp: ip,
pduUser: user,
pduPwd: pwd,
}
}
func (p *PDUPowerManager) Powercycle(ctx context.Context) error {
return PDUReboot(p.devicePort, p.pduIp, p.pduUser, p.pduPwd)
}