mirror of
https://github.com/ClusterCockpit/cc-metric-collector.git
synced 2026-03-25 17:37:29 +01:00
ipmi: refactor and add sudo support
This commit is contained in:
@@ -65,48 +65,58 @@ func (m *IpmiCollector) Init(config json.RawMessage) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.config.IpmitoolPath) != 0 && len(m.config.IpmisensorsPath) != 0 {
|
m.ipmitool = m.config.IpmitoolPath
|
||||||
return fmt.Errorf("ipmitool_path and ipmisensors_path cannot be used at the same time. Please disable one of them")
|
m.ipmisensors = m.config.IpmisensorsPath
|
||||||
}
|
|
||||||
|
|
||||||
// Test if the configured commands actually work
|
// Test if any of the supported backends work
|
||||||
if len(m.config.IpmitoolPath) != 0 {
|
var dummyChan chan lp.CCMessage
|
||||||
dummyChan := make(chan lp.CCMessage)
|
dummyConsumer := func() {
|
||||||
go func() {
|
|
||||||
for range dummyChan {
|
for range dummyChan {
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
err := m.readIpmiTool(dummyChan)
|
|
||||||
close(dummyChan)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Cannot execute '%s' (sudo=%t): %v", m.config.IpmitoolPath, m.config.Sudo, err)
|
|
||||||
}
|
|
||||||
} else if len(m.config.IpmisensorsPath) != 0 {
|
|
||||||
dummyChan := make(chan lp.CCMessage)
|
|
||||||
go func() {
|
|
||||||
for range dummyChan {
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
err := m.readIpmiSensors(dummyChan)
|
|
||||||
close(dummyChan)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Cannot execute '%s' (sudo=%t): %v", m.config.IpmisensorsPath, m.config.Sudo, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("IpmiCollector enabled, but neither ipmitool nor ipmi-sensors are configured.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test if ipmi-sensors works (preferred over ipmitool, because it's faster)
|
||||||
|
var ipmiSensorsErr error
|
||||||
|
if _, ipmiSensorsErr = exec.LookPath(m.ipmisensors); ipmiSensorsErr == nil {
|
||||||
|
dummyChan = make(chan lp.CCMessage)
|
||||||
|
go dummyConsumer()
|
||||||
|
ipmiSensorsErr = m.readIpmiSensors(dummyChan)
|
||||||
|
close(dummyChan)
|
||||||
|
if ipmiSensorsErr == nil {
|
||||||
|
cclog.ComponentDebugf(m.name, "Using ipmi-sensors for ipmistat collector")
|
||||||
m.init = true
|
m.init = true
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cclog.ComponentDebugf(m.name, "Unable to use ipmi-sensors for ipmistat collector: %v", ipmiSensorsErr)
|
||||||
|
m.ipmisensors = ""
|
||||||
|
|
||||||
|
// Test if ipmitool works (may be very slow)
|
||||||
|
var ipmiToolErr error
|
||||||
|
if _, ipmiToolErr = exec.LookPath(m.ipmitool); ipmiToolErr == nil {
|
||||||
|
dummyChan = make(chan lp.CCMessage)
|
||||||
|
go dummyConsumer()
|
||||||
|
ipmiToolErr = m.readIpmiTool(dummyChan)
|
||||||
|
close(dummyChan)
|
||||||
|
if ipmiToolErr == nil {
|
||||||
|
cclog.ComponentDebugf(m.name, "Using ipmitool for ipmistat collector")
|
||||||
|
m.init = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.ipmitool = ""
|
||||||
|
cclog.ComponentDebugf(m.name, "Unable to use ipmitool for ipmistat collector: %v", ipmiToolErr)
|
||||||
|
|
||||||
|
return fmt.Errorf("unable to init neither ipmitool (%w) nor ipmi-sensors (%w)", ipmiToolErr, ipmiSensorsErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *IpmiCollector) readIpmiTool(output chan lp.CCMessage) error {
|
func (m *IpmiCollector) readIpmiTool(output chan lp.CCMessage) error {
|
||||||
// Setup ipmitool command
|
// Setup ipmitool command
|
||||||
argv := make([]string, 0)
|
argv := make([]string, 0)
|
||||||
if m.config.Sudo {
|
if m.config.Sudo {
|
||||||
argv = append(argv, "sudo")
|
argv = append(argv, "sudo", "-n")
|
||||||
}
|
}
|
||||||
argv = append(argv, m.config.IpmitoolPath, "sensor")
|
argv = append(argv, m.ipmitool, "sensor")
|
||||||
command := exec.Command(argv[0], argv[1:]...)
|
command := exec.Command(argv[0], argv[1:]...)
|
||||||
stdout, _ := command.StdoutPipe()
|
stdout, _ := command.StdoutPipe()
|
||||||
errBuf := new(bytes.Buffer)
|
errBuf := new(bytes.Buffer)
|
||||||
@@ -114,7 +124,7 @@ func (m *IpmiCollector) readIpmiTool(output chan lp.CCMessage) error {
|
|||||||
|
|
||||||
// start command
|
// start command
|
||||||
if err := command.Start(); err != nil {
|
if err := command.Start(); err != nil {
|
||||||
return fmt.Errorf("Failed to start command '%s': %v", command.String(), err)
|
return fmt.Errorf("failed to start command '%s': %w", command.String(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read command output
|
// Read command output
|
||||||
@@ -124,6 +134,12 @@ func (m *IpmiCollector) readIpmiTool(output chan lp.CCMessage) error {
|
|||||||
if len(lv) < 3 {
|
if len(lv) < 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(lv[1]) == "0x0" || strings.TrimSpace(lv[1]) == "na" {
|
||||||
|
// Ignore known non-float values
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
v, err := strconv.ParseFloat(strings.TrimSpace(lv[1]), 64)
|
v, err := strconv.ParseFloat(strings.TrimSpace(lv[1]), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentErrorf(m.name, "Failed to parse float '%s': %v", lv[1], err)
|
cclog.ComponentErrorf(m.name, "Failed to parse float '%s': %v", lv[1], err)
|
||||||
@@ -154,7 +170,7 @@ func (m *IpmiCollector) readIpmiTool(output chan lp.CCMessage) error {
|
|||||||
// Wait for command end
|
// Wait for command end
|
||||||
if err := command.Wait(); err != nil {
|
if err := command.Wait(); err != nil {
|
||||||
errMsg, _ := io.ReadAll(errBuf)
|
errMsg, _ := io.ReadAll(errBuf)
|
||||||
return fmt.Errorf("Failed to complete command '%s': %v (stderr: %s)", command.String(), err, strings.TrimSpace(string(errMsg)))
|
return fmt.Errorf("failed to complete command '%s': %w (stderr: %s)", command.String(), err, strings.TrimSpace(string(errMsg)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -164,9 +180,9 @@ func (m *IpmiCollector) readIpmiSensors(output chan lp.CCMessage) error {
|
|||||||
// Setup ipmisensors command
|
// Setup ipmisensors command
|
||||||
argv := make([]string, 0)
|
argv := make([]string, 0)
|
||||||
if m.config.Sudo {
|
if m.config.Sudo {
|
||||||
argv = append(argv, "sudo")
|
argv = append(argv, "sudo", "-n")
|
||||||
}
|
}
|
||||||
argv = append(argv, m.config.IpmisensorsPath, "--comma-separated-output", "--sdr-cache-recreate")
|
argv = append(argv, m.ipmisensors, "--comma-separated-output", "--sdr-cache-recreate")
|
||||||
command := exec.Command(argv[0], argv[1:]...)
|
command := exec.Command(argv[0], argv[1:]...)
|
||||||
stdout, _ := command.StdoutPipe()
|
stdout, _ := command.StdoutPipe()
|
||||||
errBuf := new(bytes.Buffer)
|
errBuf := new(bytes.Buffer)
|
||||||
@@ -174,7 +190,7 @@ func (m *IpmiCollector) readIpmiSensors(output chan lp.CCMessage) error {
|
|||||||
|
|
||||||
// start command
|
// start command
|
||||||
if err := command.Start(); err != nil {
|
if err := command.Start(); err != nil {
|
||||||
return fmt.Errorf("Failed to start command '%s': %v", command.String(), err)
|
return fmt.Errorf("failed to start command '%s': %w", command.String(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read command output
|
// Read command output
|
||||||
@@ -184,7 +200,11 @@ func (m *IpmiCollector) readIpmiSensors(output chan lp.CCMessage) error {
|
|||||||
if len(lv) <= 3 {
|
if len(lv) <= 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v, err := strconv.ParseFloat(lv[3], 64)
|
if lv[3] == "N/A" || lv[3] == "Reading" {
|
||||||
|
// Ignore known non-float values
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v, err := strconv.ParseFloat(strings.TrimSpace(lv[3]), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentErrorf(m.name, "Failed to parse float '%s': %v", lv[3], err)
|
cclog.ComponentErrorf(m.name, "Failed to parse float '%s': %v", lv[3], err)
|
||||||
continue
|
continue
|
||||||
@@ -204,7 +224,7 @@ func (m *IpmiCollector) readIpmiSensors(output chan lp.CCMessage) error {
|
|||||||
// Wait for command end
|
// Wait for command end
|
||||||
if err := command.Wait(); err != nil {
|
if err := command.Wait(); err != nil {
|
||||||
errMsg, _ := io.ReadAll(errBuf)
|
errMsg, _ := io.ReadAll(errBuf)
|
||||||
return fmt.Errorf("Failed to complete command '%s': %v (stderr: %s)", command.String(), err, strings.TrimSpace(string(errMsg)))
|
return fmt.Errorf("failed to complete command '%s': %w (stderr: %s)", command.String(), err, strings.TrimSpace(string(errMsg)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -216,16 +236,16 @@ func (m *IpmiCollector) Read(interval time.Duration, output chan lp.CCMessage) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.config.IpmitoolPath) > 0 {
|
if len(m.ipmisensors) > 0 {
|
||||||
err := m.readIpmiTool(output)
|
|
||||||
if err != nil {
|
|
||||||
cclog.ComponentErrorf(m.name, "readIpmiTool() failed: %v", err)
|
|
||||||
}
|
|
||||||
} else if len(m.config.IpmisensorsPath) > 0 {
|
|
||||||
err := m.readIpmiSensors(output)
|
err := m.readIpmiSensors(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.ComponentErrorf(m.name, "readIpmiSensors() failed: %v", err)
|
cclog.ComponentErrorf(m.name, "readIpmiSensors() failed: %v", err)
|
||||||
}
|
}
|
||||||
|
} else if len(m.ipmitool) > 0 {
|
||||||
|
err := m.readIpmiTool(output)
|
||||||
|
if err != nil {
|
||||||
|
cclog.ComponentErrorf(m.name, "readIpmiTool() failed: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,25 @@ hugo_path: docs/reference/cc-metric-collector/collectors/ipmi.md
|
|||||||
```json
|
```json
|
||||||
"ipmistat": {
|
"ipmistat": {
|
||||||
"ipmitool_path": "/path/to/ipmitool",
|
"ipmitool_path": "/path/to/ipmitool",
|
||||||
"ipmisensors_path": "/path/to/ipmi-sensors"
|
"ipmisensors_path": "/path/to/ipmi-sensors",
|
||||||
|
"use_sudo": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `ipmistat` collector reads data from `ipmitool` (`ipmitool sensor`) or `ipmi-sensors` (`ipmi-sensors --sdr-cache-recreate --comma-separated-output`).
|
The `ipmistat` collector reads data from `ipmitool` (`ipmitool sensor`) or `ipmi-sensors` (`ipmi-sensors --sdr-cache-recreate --comma-separated-output`).
|
||||||
|
|
||||||
The metrics depend on the output of the underlying tools but contain temperature, power and energy metrics.
|
The metrics depend on the output of the underlying tools but contain temperature, power and energy metrics.
|
||||||
|
|
||||||
|
ipmitool and ipmi-sensors typically require root to run.
|
||||||
|
In order to cc-metric-collector without root priviliges, you can enable `use_sudo`.
|
||||||
|
Add a file like this in /etc/sudoers.d/ to allow cc-metric-collector to run this command:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Do not log the following sudo commands from monitoring, since this causes a lot of log spam.
|
||||||
|
# However keep log_denied enabled, to detect failures
|
||||||
|
Defaults: monitoring !log_allowed, !pam_session
|
||||||
|
|
||||||
|
# Allow to use ipmitool and ipmi-sensors
|
||||||
|
monitoring ALL = (root) NOPASSWD:/usr/bin/ipmitool sensor
|
||||||
|
monitoring ALL = (root) NOPASSWD:/usr/sbin/ipmi-sensors --comma-separated-output --sdr-cache-recreate
|
||||||
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user