Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support redfish #734

Merged
merged 14 commits into from
Jul 18, 2023
11 changes: 10 additions & 1 deletion cmd/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/sustainable-computing-io/kepler/pkg/model"
"github.com/sustainable-computing-io/kepler/pkg/power/accelerator"
"github.com/sustainable-computing-io/kepler/pkg/power/components"
"github.com/sustainable-computing-io/kepler/pkg/power/platform"
kversion "github.com/sustainable-computing-io/kepler/pkg/version"

"github.com/prometheus/client_golang/prometheus"
Expand Down Expand Up @@ -63,6 +64,7 @@ var (
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file, if empty we use the in-cluster configuration")
apiserverEnabled = flag.Bool("apiserver", true, "if apiserver is disabled, we collect pod information from kubelet")
kernelSourceDirPath = flag.String("kernel-source-dir", "", "path to the kernel source directory")
redfishCredFilePath = flag.String("redfish-cred-file-path", "", "path to the redfish credential file")
)

func healthProbe(w http.ResponseWriter, req *http.Request) {
Expand Down Expand Up @@ -154,9 +156,10 @@ func main() {
config.SetEnabledHardwareCounterMetrics(*exposeHardwareCounterMetrics)
config.SetEnabledGPU(*enableGPU)
config.EnabledMSR = *enabledMSR

config.SetKubeConfig(*kubeconfig)
config.SetEnableAPIServer(*apiserverEnabled)
if *kernelSourceDirPath != "" {
if kernelSourceDirPath != nil && len(*kernelSourceDirPath) > 0 {
if err := config.SetKernelSourceDir(*kernelSourceDirPath); err != nil {
klog.Warningf("failed to set kernel source dir to %q: %v", *kernelSourceDirPath, err)
}
Expand All @@ -170,9 +173,15 @@ func main() {
}
klog.Infof("EnabledBPFBatchDelete: %v", config.EnabledBPFBatchDelete)

// set redfish credential file path
if *redfishCredFilePath != "" {
config.SetRedfishCredFilePath(*redfishCredFilePath)
}

config.LogConfigs()

components.InitPowerImpl()
platform.InitPowerImpl()

collector_metric.InitAvailableParamAndMetrics()

Expand Down
8 changes: 5 additions & 3 deletions pkg/collector/prometheus_collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/sustainable-computing-io/kepler/pkg/bpfassets/attacher"
collector_metric "github.com/sustainable-computing-io/kepler/pkg/collector/metric"
"github.com/sustainable-computing-io/kepler/pkg/config"
"github.com/sustainable-computing-io/kepler/pkg/power/platform"
)

const (
Expand Down Expand Up @@ -210,6 +211,7 @@ func (p *PrometheusCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- p.nodeDesc.nodePackageJoulesTotal
ch <- p.nodeDesc.nodePlatformJoulesTotal
ch <- p.nodeDesc.nodeOtherComponentsJoulesTotal

if config.EnabledGPU {
ch <- p.nodeDesc.nodeGPUJoulesTotal
}
Expand Down Expand Up @@ -646,20 +648,20 @@ func (p *PrometheusCollector) updateNodeMetrics(wg *sync.WaitGroup, ch chan<- pr
idlePower,
collector_metric.NodeName, "idle",
)

powerSource := platform.GetPowerSource()
dynPower = (float64(p.NodeMetrics.GetSumAggrDynEnergyFromAllSources(collector_metric.PLATFORM)) / miliJouleToJoule)
ch <- prometheus.MustNewConstMetric(
p.nodeDesc.nodePlatformJoulesTotal,
prometheus.CounterValue,
dynPower,
collector_metric.NodeName, "acpi", "dynamic",
collector_metric.NodeName, powerSource, "dynamic",
)
idlePower = (float64(p.NodeMetrics.GetSumAggrIdleEnergyromAllSources(collector_metric.PLATFORM)) / miliJouleToJoule)
ch <- prometheus.MustNewConstMetric(
p.nodeDesc.nodePlatformJoulesTotal,
prometheus.CounterValue,
idlePower,
collector_metric.NodeName, "acpi", "idle",
collector_metric.NodeName, powerSource, "idle",
)

if config.EnabledGPU {
Expand Down
34 changes: 34 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ var (
// dir of kernel sources for bcc
kernelSourceDirs = []string{}

// redfish cred file path
redfishCredFilePath string
redfishProbeIntervalInSeconds = getConfig("REDFISH_PROBE_INTERVAL_IN_SECONDS", "60")
redfishSkipSSLVerify = getBoolConfig("REDFISH_SKIP_SSL_VERIFY", true)

rootfs marked this conversation as resolved.
Show resolved Hide resolved
////////////////////////////////////
ModelServerEnable = getBoolConfig("MODEL_SERVER_ENABLE", false)
ModelServerEndpoint = SetModelServerReqEndpoint()
Expand Down Expand Up @@ -188,6 +193,35 @@ func GetKernelSourceDirs() []string {
return kernelSourceDirs
}

func SetRedfishCredFilePath(credFilePath string) {
redfishCredFilePath = credFilePath
}

func GetRedfishCredFilePath() string {
return redfishCredFilePath
}

func SetRedfishProbeIntervalInSeconds(interval string) {
redfishProbeIntervalInSeconds = interval
}

func GetRedfishProbeIntervalInSeconds() int {
// convert string "redfishProbeIntervalInSeconds" to int
probeInterval, err := strconv.Atoi(redfishProbeIntervalInSeconds)
if err != nil {
klog.Warning("failed to convert redfishProbeIntervalInSeconds to int", err)
return 60
}
return probeInterval
}
func SetRedfishSkipSSLVerify(skipSSLVerify bool) {
redfishSkipSSLVerify = skipSSLVerify
}

func GetRedfishSkipSSLVerify() bool {
return redfishSkipSSLVerify
}

rootfs marked this conversation as resolved.
Show resolved Hide resolved
func SetModelServerReqEndpoint() (modelServerReqEndpoint string) {
modelServerURL := getConfig("MODEL_SERVER_URL", modelServerService)
if modelServerURL == modelServerService {
Expand Down
116 changes: 116 additions & 0 deletions pkg/nodecred/csv_cred.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
Copyright 2023.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package nodecred

import (
"encoding/csv"
"fmt"
"os"

"k8s.io/klog/v2"
)

// csvNodeCredImpl is the implementation of NodeCred using on disk file
// the file is in the format of
// node1,admin,password,localhost
// node2,admin,password,localhost
// node3,admin,password,localhost
type csvNodeCred struct {
}

var (
credMap map[string]string
)

func (c csvNodeCred) GetNodeCredByNodeName(nodeName, target string) (map[string]string, error) {
if credMap == nil {
return nil, fmt.Errorf("credential is not set")
} else if target == "redfish" {
cred := make(map[string]string)
cred["redfish_username"] = credMap["redfish_username"]
cred["redfish_password"] = credMap["redfish_password"]
cred["redfish_host"] = credMap["redfish_host"]
if cred["redfish_username"] == "" || cred["redfish_password"] == "" || cred["redfish_host"] == "" {
return nil, fmt.Errorf("no credential found")
}
return cred, nil
}

return nil, fmt.Errorf("no credential found for target %s", target)
}

func (c csvNodeCred) IsSupported(info map[string]string) bool {
// read redfish_cred_file_path from info
filePath := info["redfish_cred_file_path"]
if filePath == "" {
return false
} else {
nodeName := getNodeName()
// read file from filePath
userName, password, host, err := readCSVFile(filePath, nodeName)
if err != nil {
klog.V(5).Infof("failed to read csv file: %v", err)
return false
}
klog.V(5).Infof("read csv file successfully")
credMap = make(map[string]string)
credMap["redfish_username"] = userName
credMap["redfish_password"] = password
credMap["redfish_host"] = host
}
return true
}

func getNodeName() string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this function?

func getNodeName() string {
?

nodeName := os.Getenv("NODE_NAME")
if nodeName == "" {
nodeName = "localhost"
}
return nodeName
}

func readCSVFile(filePath, nodeName string) (userName, password, host string, err error) {
// Open the CSV file
file, err := os.Open(filePath)
if err != nil {
fmt.Println("Error opening the file:", err)
return
}
defer file.Close()

// Create a new CSV reader
reader := csv.NewReader(file)

// Read all rows from the CSV file
rows, err := reader.ReadAll()
if err != nil {
fmt.Println("Error reading CSV:", err)
return
}

// Iterate over each row and check if the node name matches
for _, row := range rows {
if row[0] == nodeName && len(row) >= 4 {
userName = row[1]
password = row[2]
host = row[3]
return userName, password, host, nil
}
}
err = fmt.Errorf("node name %s not found in file %s", nodeName, filePath)
return
}
132 changes: 132 additions & 0 deletions pkg/nodecred/csv_cred_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
Copyright 2023.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package nodecred

import (
"os"
"testing"
)

func TestGetNodeCredByNodeName(t *testing.T) {
credMap = map[string]string{
"redfish_username": "admin",
"redfish_password": "password",
"redfish_host": "node1",
}
c := csvNodeCred{}

// Test with target "redfish"
result, err := c.GetNodeCredByNodeName("node1", "redfish")
if err != nil {
t.Errorf("Expected nil error, got: %v", err)
}
expected := credMap
if !mapStringStringEqual(result, expected) {
t.Errorf("Expected credMap: %v, got: %v", expected, result)
}

// Test with unsupported target
_, err = c.GetNodeCredByNodeName("node1", "unsupported")
if err == nil {
t.Errorf("Expected an error, got nil")
}

// Test with nil credMap
credMap = nil
_, err = c.GetNodeCredByNodeName("node1", "redfish")
if err == nil {
t.Errorf("Expected an error, got nil")
}
}

func TestIsSupported(t *testing.T) {
// Test when redfish_cred_file_path is missing
info := map[string]string{}
c := csvNodeCred{}
result := c.IsSupported(info)
if result {
t.Errorf("Expected false, got: %v", result)
}

// Test when redfish_cred_file_path is empty
info = map[string]string{
"redfish_cred_file_path": "",
}
result = c.IsSupported(info)
if result {
t.Errorf("Expected false, got: %v", result)
}

// create a temp csv file with the following content:
// node1,admin,password,localhost
// node2,admin,password,localhost
// node3,admin,password,localhost
file, err := os.CreateTemp("", "test.csv")
if err != nil {
t.Errorf("Expected nil error, got: %v", err)
}
defer os.Remove(file.Name())
_, err = file.WriteString("node1,admin,password,localhost\nnode2,admin,password,localhost\nnode3,admin,password,localhost\n")
if err != nil {
t.Errorf("Expected nil error, got: %v", err)
}

// Test with valid redfish_cred_file_path
info = map[string]string{
"redfish_cred_file_path": file.Name(),
}

// set ENV variable NODE_NAME to "node1"
os.Setenv("NODE_NAME", "node1")
// check if getNodeName() returns "node1"
nodeName := getNodeName()
if nodeName != "node1" {
t.Errorf("Expected nodeName: node1, got: %v", nodeName)
}
// readCSVFile should return the credentials for node1
userName, password, host, err := readCSVFile(file.Name(), nodeName)
if err != nil {
t.Errorf("Expected nil error, got: %v", err)
}
if host != "localhost" {
t.Errorf("Expected host: localhost, got: %v", host)
}
if userName != "admin" {
t.Errorf("Expected userName: admin, got: %v", userName)
}
if password != "password" {
t.Errorf("Expected password: password, got: %v", password)
}
result = c.IsSupported(info)
if !result {
t.Errorf("Expected true, got: %v", result)
}
}

// Helper function to compare two maps of strings
func mapStringStringEqual(a, b map[string]string) bool {
if len(a) != len(b) {
return false
}
for key, valA := range a {
valB, ok := b[key]
if !ok || valA != valB {
return false
}
}
return true
}