Make more robust to SNMP failures
This commit is contained in:
parent
fd2bd06fed
commit
075ba1614b
|
@ -1 +1,3 @@
|
||||||
config.toml
|
config.toml
|
||||||
|
apc2mqtt
|
||||||
|
.direnv
|
||||||
|
|
46
flake.lock
46
flake.lock
|
@ -1,5 +1,26 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"devshell": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": [
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1649691969,
|
||||||
|
"narHash": "sha256-nY1aUWIyh3TcGVo3sn+3vyCh+tOiEZL4JtMX3aOZSeY=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"rev": "e22633b05fec2fe196888c593d4d9b3f4f648a25",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1637014545,
|
"lastModified": 1637014545,
|
||||||
|
@ -17,11 +38,27 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1637156900,
|
"lastModified": 1643381941,
|
||||||
"narHash": "sha256-nusyaSsL1RLyUEWufUUywDrGKMXw+4ugSSZ3ss8TSuw=",
|
"narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "12fc0f19fefa9dff68bc3e0938b815ab8d89df90",
|
"rev": "5efc8ca954272c4376ac929f4c5ffefcc20551d5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1675153841,
|
||||||
|
"narHash": "sha256-EWvU3DLq+4dbJiukfhS7r6sWZyJikgXn6kNl7eHljW8=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "ea692c2ad1afd6384e171eabef4f0887d2b882d3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -31,8 +68,9 @@
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"devshell": "devshell",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs_2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
14
flake.nix
14
flake.nix
|
@ -2,10 +2,18 @@
|
||||||
description = "Samw's APC PDU MQTT bridge";
|
description = "Samw's APC PDU MQTT bridge";
|
||||||
|
|
||||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
inputs.devshell = {
|
||||||
|
url = "github:numtide/devshell";
|
||||||
|
inputs.flake-utils.follows = "flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
outputs = { self, nixpkgs, flake-utils, devshell }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let pkgs = import nixpkgs { inherit system; };
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ devshell.overlay ];
|
||||||
|
};
|
||||||
in rec {
|
in rec {
|
||||||
packages.apc2mqtt = pkgs.buildGoModule {
|
packages.apc2mqtt = pkgs.buildGoModule {
|
||||||
name = "apc2mqtt";
|
name = "apc2mqtt";
|
||||||
|
@ -16,5 +24,7 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
defaultPackage = packages.apc2mqtt;
|
defaultPackage = packages.apc2mqtt;
|
||||||
|
devShell =
|
||||||
|
pkgs.devshell.mkShell { packages = with pkgs; [ go gopls mosquitto ]; };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
142
main.go
142
main.go
|
@ -35,6 +35,8 @@ type config struct {
|
||||||
MQTT struct {
|
MQTT struct {
|
||||||
Host string
|
Host string
|
||||||
Port uint16
|
Port uint16
|
||||||
|
User string
|
||||||
|
Pass string
|
||||||
}
|
}
|
||||||
Targets []targetConfig
|
Targets []targetConfig
|
||||||
}
|
}
|
||||||
|
@ -76,6 +78,38 @@ type PDUCommand struct {
|
||||||
State bool
|
State bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPDUState(snmp *gosnmp.GoSNMP) (*PDUState, error) {
|
||||||
|
res, err := snmp.Get([]string{sPDUMasterConfigPDUName, sPDUIdentSerialNumber, sPDUIdentModelNumber})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get PDU info: %w", err)
|
||||||
|
}
|
||||||
|
state := PDUState{
|
||||||
|
Name: string(res.Variables[0].Value.([]byte)),
|
||||||
|
Serial: string(res.Variables[1].Value.([]byte)),
|
||||||
|
Model: string(res.Variables[2].Value.([]byte)),
|
||||||
|
}
|
||||||
|
|
||||||
|
outletNames, err := snmp.WalkAll(sPDUOutletName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("walk PDU outlet names: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range outletNames {
|
||||||
|
state.Outlets = append(state.Outlets, Outlet{
|
||||||
|
Name: string(val.Value.([]byte)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
outletStates, err := snmp.WalkAll(sPDUOutletCtl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("walk PDU outlet states: %w", err)
|
||||||
|
}
|
||||||
|
for i, val := range outletStates {
|
||||||
|
state.Outlets[i].State = val.Value.(int) == 1
|
||||||
|
}
|
||||||
|
return &state, nil
|
||||||
|
}
|
||||||
|
|
||||||
func runSNMP(host string, port uint16, stateCh chan PDUState, commandCh chan PDUCommand) {
|
func runSNMP(host string, port uint16, stateCh chan PDUState, commandCh chan PDUCommand) {
|
||||||
snmp := &gosnmp.GoSNMP{
|
snmp := &gosnmp.GoSNMP{
|
||||||
Target: host,
|
Target: host,
|
||||||
|
@ -86,51 +120,45 @@ func runSNMP(host string, port uint16, stateCh chan PDUState, commandCh chan PDU
|
||||||
Version: gosnmp.Version1,
|
Version: gosnmp.Version1,
|
||||||
Retries: 3,
|
Retries: 3,
|
||||||
}
|
}
|
||||||
err := snmp.Connect()
|
// Connect loop
|
||||||
check(err)
|
connect_loop:
|
||||||
defer snmp.Conn.Close()
|
|
||||||
poll := time.NewTicker(1 * time.Second)
|
|
||||||
for {
|
for {
|
||||||
select {
|
log.Info("Opening SNMP connection")
|
||||||
case <-poll.C:
|
if err := snmp.Connect(); err != nil {
|
||||||
res, err := snmp.Get([]string{sPDUMasterConfigPDUName, sPDUIdentSerialNumber, sPDUIdentModelNumber})
|
log.Warnf("Error connecting SNMP target %s: `%s`. Sleeping...", host, err.Error())
|
||||||
check(err)
|
time.Sleep(time.Second * 5)
|
||||||
state := PDUState{
|
continue // Try again
|
||||||
Name: string(res.Variables[0].Value.([]byte)),
|
}
|
||||||
Serial: string(res.Variables[1].Value.([]byte)),
|
defer snmp.Conn.Close()
|
||||||
Model: string(res.Variables[2].Value.([]byte)),
|
poll := time.NewTicker(1 * time.Second)
|
||||||
}
|
for {
|
||||||
|
select {
|
||||||
outletNames, err := snmp.WalkAll(sPDUOutletName)
|
case <-poll.C:
|
||||||
check(err)
|
state, err := getPDUState(snmp)
|
||||||
|
if err != nil {
|
||||||
for _, val := range outletNames {
|
log.Errorf("getting pdu state: %s", err.Error())
|
||||||
state.Outlets = append(state.Outlets, Outlet{
|
continue connect_loop // Reconnect
|
||||||
Name: string(val.Value.([]byte)),
|
}
|
||||||
})
|
log.Debugf("Got state: %s", state)
|
||||||
}
|
stateCh <- *state
|
||||||
|
case cmd := <-commandCh:
|
||||||
outletStates, err := snmp.WalkAll(sPDUOutletCtl)
|
var value int
|
||||||
check(err)
|
if cmd.State {
|
||||||
for i, val := range outletStates {
|
value = 1
|
||||||
state.Outlets[i].State = val.Value.(int) == 1
|
} else {
|
||||||
}
|
value = 2
|
||||||
stateCh <- state
|
}
|
||||||
case cmd := <-commandCh:
|
res, err := snmp.Set([]gosnmp.SnmpPDU{{
|
||||||
var value int
|
Value: value,
|
||||||
if cmd.State {
|
Name: fmt.Sprintf("%s.%d", sPDUOutletCtl, cmd.Outlet),
|
||||||
value = 1
|
Type: gosnmp.Integer,
|
||||||
} else {
|
}})
|
||||||
value = 2
|
if err != nil {
|
||||||
}
|
log.Errorf("setting pdu state: %s", err.Error())
|
||||||
res, err := snmp.Set([]gosnmp.SnmpPDU{{
|
}
|
||||||
Value: value,
|
if res.Error != gosnmp.NoError {
|
||||||
Name: fmt.Sprintf("%s.%d", sPDUOutletCtl, cmd.Outlet),
|
log.Errorf("error in snmp set: %s", res.Error)
|
||||||
Type: gosnmp.Integer,
|
}
|
||||||
}})
|
|
||||||
check(err)
|
|
||||||
if res.Error != gosnmp.NoError {
|
|
||||||
log.Errorf("error in snmp set: %s", res.Error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,21 +230,33 @@ func spawnTarget(target targetConfig, mqttClient mqtt.Client) {
|
||||||
}
|
}
|
||||||
func main() {
|
func main() {
|
||||||
var configpath = flag.String("conf", "config.toml", "Path to toml config file")
|
var configpath = flag.String("conf", "config.toml", "Path to toml config file")
|
||||||
|
var verbose = flag.Bool("v", false, "Enable debug logging")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
if *verbose {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
}
|
||||||
log.Infof("Starting version %s", version)
|
log.Infof("Starting version %s", version)
|
||||||
conf, err := parseConfig(*configpath)
|
conf, err := parseConfig(*configpath)
|
||||||
check(err)
|
check(err)
|
||||||
|
|
||||||
mqttOpts := mqtt.NewClientOptions().AddBroker(
|
mqttOpts := mqtt.NewClientOptions().AddBroker(
|
||||||
fmt.Sprintf("tcp://%s:%d", conf.MQTT.Host, conf.MQTT.Port),
|
fmt.Sprintf("tcp://%s:%d", conf.MQTT.Host, conf.MQTT.Port),
|
||||||
).SetClientID("apc2mqtt")
|
).SetClientID("apc2mqtt").SetConnectTimeout(time.Second * 5)
|
||||||
|
if conf.MQTT.User != "" {
|
||||||
|
mqttOpts.SetUsername(conf.MQTT.User)
|
||||||
|
}
|
||||||
|
if conf.MQTT.Pass != "" {
|
||||||
|
mqttOpts.SetPassword(conf.MQTT.Pass)
|
||||||
|
}
|
||||||
mqttClient := mqtt.NewClient(mqttOpts)
|
mqttClient := mqtt.NewClient(mqttOpts)
|
||||||
if token := mqttClient.Connect(); token.Wait() && token.Error() != nil {
|
for {
|
||||||
panic(token.Error())
|
if token := mqttClient.Connect(); token.Wait() && token.Error() != nil {
|
||||||
}
|
log.Fatalf("connect mqtt: %s", token.Error())
|
||||||
|
}
|
||||||
|
|
||||||
for _, target := range conf.Targets {
|
for _, target := range conf.Targets {
|
||||||
go spawnTarget(target, mqttClient)
|
go spawnTarget(target, mqttClient)
|
||||||
|
}
|
||||||
|
select {}
|
||||||
}
|
}
|
||||||
select {}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue