github.com/netdata/go.d.plugin@v0.58.1/modules/upsd/client.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package upsd 4 5 import ( 6 "encoding/csv" 7 "errors" 8 "fmt" 9 "strings" 10 11 "github.com/netdata/go.d.plugin/pkg/socket" 12 ) 13 14 const ( 15 commandUsername = "USERNAME %s" 16 commandPassword = "PASSWORD %s" 17 commandListUPS = "LIST UPS" 18 commandListVar = "LIST VAR %s" 19 commandLogout = "LOGOUT" 20 ) 21 22 // https://github.com/networkupstools/nut/blob/81fca30b2998fa73085ce4654f075605ff0b9e01/docs/net-protocol.txt#L647 23 var errUpsdCommand = errors.New("upsd command error") 24 25 type upsUnit struct { 26 name string 27 vars map[string]string 28 } 29 30 func newUpsdConn(conf Config) upsdConn { 31 return &upsdClient{conn: socket.New(socket.Config{ 32 ConnectTimeout: conf.Timeout.Duration, 33 ReadTimeout: conf.Timeout.Duration, 34 WriteTimeout: conf.Timeout.Duration, 35 Address: conf.Address, 36 })} 37 } 38 39 type upsdClient struct { 40 conn socket.Client 41 } 42 43 func (c *upsdClient) connect() error { 44 return c.conn.Connect() 45 } 46 47 func (c *upsdClient) disconnect() error { 48 _, _ = c.sendCommand(commandLogout) 49 return c.conn.Disconnect() 50 } 51 52 func (c *upsdClient) authenticate(username, password string) error { 53 cmd := fmt.Sprintf(commandUsername, username) 54 resp, err := c.sendCommand(cmd) 55 if err != nil { 56 return err 57 } 58 if resp[0] != "OK" { 59 return errors.New("authentication failed: invalid username") 60 } 61 62 cmd = fmt.Sprintf(commandPassword, password) 63 resp, err = c.sendCommand(cmd) 64 if err != nil { 65 return err 66 } 67 if resp[0] != "OK" { 68 return errors.New("authentication failed: invalid password") 69 } 70 71 return nil 72 } 73 74 func (c *upsdClient) upsUnits() ([]upsUnit, error) { 75 resp, err := c.sendCommand(commandListUPS) 76 if err != nil { 77 return nil, err 78 } 79 80 var upsNames []string 81 82 for _, v := range resp { 83 if !strings.HasPrefix(v, "UPS ") { 84 continue 85 } 86 parts := splitLine(v) 87 if len(parts) < 2 { 88 continue 89 } 90 name := parts[1] 91 upsNames = append(upsNames, name) 92 } 93 94 var upsUnits []upsUnit 95 96 for _, name := range upsNames { 97 cmd := fmt.Sprintf(commandListVar, name) 98 resp, err := c.sendCommand(cmd) 99 if err != nil { 100 return nil, err 101 } 102 103 ups := upsUnit{ 104 name: name, 105 vars: make(map[string]string), 106 } 107 108 upsUnits = append(upsUnits, ups) 109 110 for _, v := range resp { 111 if !strings.HasPrefix(v, "VAR ") { 112 continue 113 } 114 parts := splitLine(v) 115 if len(parts) < 4 { 116 continue 117 } 118 n, v := parts[2], parts[3] 119 ups.vars[n] = v 120 } 121 } 122 123 return upsUnits, nil 124 } 125 126 func (c *upsdClient) sendCommand(cmd string) ([]string, error) { 127 var resp []string 128 var errMsg string 129 endLine := getEndLine(cmd) 130 131 err := c.conn.Command(cmd+"\n", func(bytes []byte) bool { 132 line := string(bytes) 133 resp = append(resp, line) 134 135 if strings.HasPrefix(line, "ERR ") { 136 errMsg = strings.TrimPrefix(line, "ERR ") 137 } 138 139 return line != endLine && errMsg == "" 140 }) 141 if err != nil { 142 return nil, err 143 } 144 if errMsg != "" { 145 return nil, fmt.Errorf("%w: %s (cmd: '%s')", errUpsdCommand, errMsg, cmd) 146 } 147 148 return resp, nil 149 } 150 151 func getEndLine(cmd string) string { 152 px, _, _ := strings.Cut(cmd, " ") 153 154 switch px { 155 case "USERNAME", "PASSWORD", "VER": 156 return "OK" 157 } 158 return fmt.Sprintf("END %s", cmd) 159 } 160 161 func splitLine(s string) []string { 162 r := csv.NewReader(strings.NewReader(s)) 163 r.Comma = ' ' 164 165 parts, _ := r.Read() 166 167 return parts 168 }