diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index cf52edddd..d2db8ebd4 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -26,6 +26,9 @@ jobs: - dir: "./bmc-reverse-proxy" container-image: "bmc-reverse-proxy" make-targets: "setup check-generate test" + - dir: "./bmc-log-collector" + container-image: "bmc-log-collector" + make-targets: "setup check-generate test" - dir: "./bpf-map-pressure-exporter" container-image: "bpf-map-pressure-exporter" make-targets: "check-generate test" diff --git a/bmc-log-collector/BRANCH b/bmc-log-collector/BRANCH new file mode 100644 index 000000000..b63ba696b --- /dev/null +++ b/bmc-log-collector/BRANCH @@ -0,0 +1 @@ +0.9 diff --git a/bmc-log-collector/Dockerfile b/bmc-log-collector/Dockerfile new file mode 100644 index 000000000..5109bfbfa --- /dev/null +++ b/bmc-log-collector/Dockerfile @@ -0,0 +1,18 @@ +# Stage1: build from source +FROM ghcr.io/cybozu/golang:1.22-jammy AS build + +COPY . /work +WORKDIR /work + +RUN CGO_ENABLED=0 go install -ldflags="-w -s" + +# Stage2: setup runtime container +FROM scratch + +LABEL org.opencontainers.image.source="https://github.com/cybozu/neco-containers" + +COPY --from=build /go/bin / + +USER 10000:10000 + +ENTRYPOINT ["/log-collector"] diff --git a/bmc-log-collector/Makefile b/bmc-log-collector/Makefile new file mode 100644 index 000000000..48ee1c320 --- /dev/null +++ b/bmc-log-collector/Makefile @@ -0,0 +1,28 @@ +.PHONY: all +all: check-generate test + +.PHONY: setup +setup: + go install github.com/cybozu-go/golang-custom-analyzer/cmd/custom-checker@latest + go install honnef.co/go/tools/cmd/staticcheck@latest + go install github.com/onsi/ginkgo/v2/ginkgo + +.PHONY: check-generate +check-generate: + go mod tidy + git diff --exit-code --name-only + +.PHONY: test +test: clean + test -z "$$(gofmt -s -l . | tee /dev/stderr)" + staticcheck ./... + test -z "$$(custom-checker -restrictpkg.packages=html/template ./... 2>&1 | tee /dev/stderr)" + go vet ./... + ginkgo -v --race -p . + +.PHONY: clean +clean: + rm -f testdata/output_main_test/* + rm -fr testdata/pointers_get_machines + rm -fr testdata/pointers_log_collector + rm -fr testdata/pointers_main_test diff --git a/bmc-log-collector/README.md b/bmc-log-collector/README.md new file mode 100644 index 000000000..2350d60c6 --- /dev/null +++ b/bmc-log-collector/README.md @@ -0,0 +1,53 @@ +bmc-log-collector +============================ + +`bmc-log-collector` collects hardware logs from Baseboard Management Controller (BMC) and output own stdout. + +The following products are assumed as BMC. +- DELL integrated Dell Remote Access Controller (iDRAC) + +This program reads the "machineslist.json" and retrieves the System Event Log from each BMC. "bmc-log-collector" adds the serial and the node IP to own STD output. + +## Referenced file + +#### User and password of BMC + +``` +{ + "USERID-TO-BE-REPLACE": { + "password": { + "raw": "PASSWORD-STRING-TO-BE-REPLACE" + } + }, + // Repeat +} +``` + +#### Target "machineslist.json" of log scraping + +``` +[ + { + serial: "ABC1234", // Uniq serial ID of the server hardware + bmc_ipv4: "192.168.1.1" // BMC IP address + node_ipv4: "192.168.10.1" // Server IP address + }, + // Repeat +] +``` + + +## Usage + +bmc-log-collector command provides the usage in following. + +``` +$ bmc-log-collector --help + +Usage of ./bmc-log-collector: + --bmc-user-json string User and password of BMC (default "/users/neco/bmc-user.json") + --machine-list-json string Target machines list of log scraping (default "/config/machineslist.json") + --pointer-dir-path string Data directory of pointer management (default "/data/pointers") + --scraping-interval-timer int Timer(sec) of scraping interval time (default 300) + --user-id string User ID of bmc-user-json JSON file (default "support") +``` diff --git a/bmc-log-collector/TAG b/bmc-log-collector/TAG new file mode 100644 index 000000000..ac39a106c --- /dev/null +++ b/bmc-log-collector/TAG @@ -0,0 +1 @@ +0.9.0 diff --git a/bmc-log-collector/bmc-user.go b/bmc-log-collector/bmc-user.go new file mode 100644 index 000000000..a3f97460c --- /dev/null +++ b/bmc-log-collector/bmc-user.go @@ -0,0 +1,42 @@ +package main + +import ( + "encoding/json" + "os" +) + +// BMCPassword represents password for a BMC user. +type BMCPassword struct { + Raw string `json:"raw"` + Hash string `json:"hash"` + Salt string `json:"salt"` +} + +// Credentials represents credentials of a BMC user. +type Credentials struct { + Password BMCPassword `json:"password"` +} + +// UserConfig represents a set of BMC user credentials in JSON format. +type UserConfig struct { + Root Credentials `json:"root"` + Repair Credentials `json:"repair"` + Power Credentials `json:"power"` + Support Credentials `json:"support"` +} + +// LoadConfig loads UserConfig. +func LoadBMCUserConfig(userFile string) (*UserConfig, error) { + fd, err := os.Open(userFile) + if err != nil { + return nil, err + } + defer fd.Close() + + bmcUsers := new(UserConfig) + err = json.NewDecoder(fd).Decode(bmcUsers) + if err != nil { + return nil, err + } + return bmcUsers, nil +} diff --git a/bmc-log-collector/bmc-user_test.go b/bmc-log-collector/bmc-user_test.go new file mode 100644 index 000000000..a46f28faf --- /dev/null +++ b/bmc-log-collector/bmc-user_test.go @@ -0,0 +1,28 @@ +package main + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Get User from bmc-user.json", Ordered, func() { + Context("Normal", func() { + It("Read JSON file", func() { + user, err := LoadBMCUserConfig("testdata/etc/bmc-user.json") + Expect(err).NotTo(HaveOccurred()) + Expect(user.Support.Password.Raw).To(Equal(basicAuthPassword)) + }) + }) + + Context("Abnormal", func() { + It("Read no existing file", func() { + _, err := LoadBMCUserConfig("testdata/etc/no-exist.json") + Expect(err).To(HaveOccurred()) + }) + + It("no support user in json file", func() { + _, err := LoadBMCUserConfig("testdata/etc/bmc-user-err.json") + Expect(err).To(HaveOccurred()) + }) + }) +}) diff --git a/bmc-log-collector/docs/design.md b/bmc-log-collector/docs/design.md new file mode 100644 index 000000000..82ad89ba4 --- /dev/null +++ b/bmc-log-collector/docs/design.md @@ -0,0 +1,112 @@ +# BMC Log Collector Design + +“BMC Log Collector” collects Hareware Error from Baseboard Management Controller (BMC) and output to own stdout. +In case of DELL hardware, “BMC Log Collector” collects System Event Log (SEL) from iDRAC. +The first case of collecting is DELL. + +“BMC Log Collector” has the following features +1. Retrieve the IP address and ID of the BMC to be collected from a JSON file +2. Access the IP address of the BMC and retrieve a hardware error logs from the Redfish REST service +3. Output the collected logs that eliminated duplication to STDOUT + +Redfish is a standard for server management and provides information as REST API service. +We can get the event of hardware in Server. + +## Architecture of BMC Log Collector + +The following figure shows an architectural diagram of BMC Log Collector. + +```mermaid +flowchart TB + PS[(Pointer File Store)] <---> LC + CF[JSON File]-->LC[Log Collector] + BMC[BMC]-->LC + LC-->LS[Stdout] +``` + +1. "JSON File" has BMC IP list, server identity string, server's IP address. +2. "Pointer File Store" has latest pointer information for each BMC. +5. "BMC" is Baseboard Management Controller that has IP address and serves Redfish API. + +## How “BMC Log Collector” works + +1. Get a list of IP addresses of the BMC in a JSON file +2. Access the Redfish path of the BMC's IP address to get the hardware event log +3. Use `/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries` as the path to RedFish. +4. Convert the received JSON data into a Go language structure and inspect for duplicates. +5. Compare the ID of the log received last time with the ID of the log received this time. If the ID of the log received this time is larger, it is considered the latest event. +6. If the ID is smaller than the previous one, the timestamp is compared, and if it is larger than the previous timestamp, it is considered as the latest log. +7. Write ID, type stamp, and identification string in the file. The file name is the identification string, and a separate file is created for each BMC. +8. The latest events in the event log are output in JSON format to standard output. +9. Perform tasks 1 through 8 above, at intervals of a few minutes. +10. Continue this cycle while the “BMC Log Collector” is running. + + +## Architectural Decisions + +### ADR1. Obtaining the BMC machine list + +There are two ways to obtain the latest BMC listings. + +- Method 1: Get the BMC list from the server's database. +- Method 2: Create a JSON file by adding to the existing functions. + +#### Advantages of using Method 1 +- Reduced risk of failure due to fewer dependent components + +#### Disadvantages of using Method 1 +- Complex processes to retrieve data from databases, serfs, etc. must be written. + +#### Advantages of adopting Method 2 +- Programming and testing can be reduced, and the construction period can be shortened. + +#### Disadvantages of adopting Method 2 +- If an existing function fails, the main function stops. + +### Decision and Reason +Adopt method 2. +As a countermeasure against failure, minimize the impact by using the last updated JSON file when the existing function stops. + + +### ADR2. Control of processing load + +There are two possible methods to obtain event logs from the BMC. + +- Method 1: The list is stored in the processing queue and worker tasks retrieve it sequentially. + +- Method 2: Go routines are started in a number of BMCs, and they are all retrieved at the same time. + + +#### Advantages of Method 1 +- Workload can be controlled by the number of worker tasks + +#### Disadvantages of Method 1 +- Management of queues and worker processes becomes complicated + +#### Advantages of Method 2 +- Simplifies Go programming + +#### Disadvantages of Method 2 +- Load surges occur at the beginning of each collection cycle when there are many target BMCs,Unable to control the load. + +### Decision and Reason +Method 2 +With the current number of BMCs, load is not a problem. + + + +## Risks + +1. There is a concern that log output may be mixed under multi-threaded execution. There is a possibility that this is not thread-safe. + - Countermeasures + - Put exclusion control before and after the output to make it thread-safe. + +2. Concerns that workload surges will adversely affect others + - Countermeasures + - If a problem is discovered, initially limit the number of Go routines by semaphore. If the problem cannot be resolved, consider a queue. + + + +## Usage + +please refer [README.md](../README.md) diff --git a/bmc-log-collector/go.mod b/bmc-log-collector/go.mod new file mode 100644 index 000000000..d507a13d7 --- /dev/null +++ b/bmc-log-collector/go.mod @@ -0,0 +1,32 @@ +module log-collector + +go 1.22.5 + +require ( + github.com/onsi/ginkgo/v2 v2.19.0 + github.com/onsi/gomega v1.33.1 + github.com/prometheus/client_golang v1.20.0 + github.com/prometheus/common v0.55.0 + github.com/spf13/pflag v1.0.5 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.23.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/bmc-log-collector/go.sum b/bmc-log-collector/go.sum new file mode 100644 index 000000000..06058f2b6 --- /dev/null +++ b/bmc-log-collector/go.sum @@ -0,0 +1,60 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/bmc-log-collector/log-collector.go b/bmc-log-collector/log-collector.go new file mode 100644 index 000000000..6a54cdb21 --- /dev/null +++ b/bmc-log-collector/log-collector.go @@ -0,0 +1,155 @@ +package main + +import ( + "context" + "encoding/json" + "log/slog" + "net/http" + "path" + "strconv" + "time" +) + +type SystemEventLog struct { + Od_Id string `json:"@odata.id"` + Od_Type string `json:"@odata.type"` + Create string `json:"Created"` + Description string `json:"Description"` + EntryCode string `json:"EntryCode"` + EntryType string `json:"EntryType"` + GeneratorId string `json:"GeneratorId"` + Id string `json:"Id"` + Message string `json:"Message"` + MessageArgs []string `json:"MessageArgs"` + OdCnt_MessageArgs int `json:"MessageArgs@odata.count"` + MessageId string `json:"MessageId"` + Name string `json:"Name"` + SensorNumber int `json:"SensorNumber"` + SensorType string `json:"SensorType"` + Severity string `json:"Severity"` + Serial string + NodeIP string + BmcIP string +} + +type RedfishJsonSchema struct { + Name string `json:"Name"` + Count int `json:"Members@odata.count"` + Context string `json:"@odata.context"` + Id string `json:"@odata.id"` + Type string `json:"@odata.type"` + Description string `json:"Descriptionta"` + Sel []SystemEventLog `json:"Members"` +} + +// SEL(System Event Log) Collector +type selCollector struct { + machinesListDir string // Directory of the machines list + rfSelPath string // SEL path of Redfish API address + ptrDir string // Pointer store + username string // iDRAC username + password string // iDRAC password + httpClient *http.Client // to reuse HTTP transport + intervalTime time.Duration // interval (sec) time of scraping +} + +func (c *selCollector) collectSystemEventLog(ctx context.Context, m Machine, logWriter bmcLogWriter) { + const layout = "2006-01-02T15:04:05Z07:00" + var createUnixtime int64 + var lastId int + filePath := path.Join(c.ptrDir, m.Serial) + + err := checkAndCreatePointerFile(filePath) + if err != nil { + slog.Error("can't check a pointer file.", "err", err, "serial", m.Serial, "ptrDir", c.ptrDir) + return + } + + lastPtr, err := readLastPointer(filePath) + if err != nil { + slog.Error("can't read a pointer file.", "err", err, "serial", m.Serial, "ptrDir", c.ptrDir) + return + } + + bmcUrl := "https://" + m.BmcIP + c.rfSelPath + byteJSON, statusCode, err := requestToBmc(ctx, c.username, c.password, c.httpClient, bmcUrl) + if statusCode != 200 || err != nil { + // Increment the failed counter + counterRequestFailed.WithLabelValues(m.Serial).Inc() + + // Prevent log output by the same error code or httpStatus + if statusCode != 200 && statusCode != lastPtr.LastHttpStatusCode { + slog.Error("failed access to iDRAC on HTTP level.", "url", bmcUrl, "httpStatusCode", statusCode) + } + lastPtr.LastHttpStatusCode = statusCode + + if err != nil && lastPtr.LastError != err { + slog.Error("failed access to iDRAC at TCP/IP level.", "err", err, "BMC IP", m.BmcIP) + } + lastPtr.LastError = err + + err = updateLastPointer(lastPtr, filePath) + if err != nil { + slog.Error("failed to write a pointer file.", "err", err, "serial", m.Serial, "createUnixtime", createUnixtime, "LastReadId", lastId, "ptrDir", c.ptrDir) + } + return + } + + // Increment the success counter + counterRequestSuccess.WithLabelValues(m.Serial).Inc() + + var members RedfishJsonSchema + if err := json.Unmarshal(byteJSON, &members); err != nil { + slog.Error("failed to translate JSON to go struct.", "err", err, "serial", m.Serial, "ptrDir", c.ptrDir) + return + } + + for i := len(members.Sel) - 1; i >= 0; i-- { + t, _ := time.Parse(layout, members.Sel[i].Create) + createUnixtime = t.Unix() + lastId, _ = strconv.Atoi(members.Sel[i].Id) + members.Sel[i].Serial = m.Serial + members.Sel[i].BmcIP = m.BmcIP + members.Sel[i].NodeIP = m.NodeIP + + if lastPtr.LastReadId < lastId { + // Normal case without iDRAC log reset. + bmcByteJsonLog, err := json.Marshal(members.Sel[i]) + if err != nil { + slog.Error("failed to marshal the system event log", "err", err, "serial", m.Serial, "createUnixtime", createUnixtime, "LastReadId", lastId, "ptrDir", c.ptrDir) + } + err = logWriter.write(string(bmcByteJsonLog), m.Serial) + if err != nil { + slog.Error("failed to output log", "err", err, "serial", m.Serial, "createUnixtime", createUnixtime, "LastReadId", lastId, "ptrDir", c.ptrDir) + } + lastPtr.LastReadId = lastId + lastPtr.LastReadTime = createUnixtime + } else { + // If the log is reset in iDRAC, the ID starts from 1. + // In that case, determine if the log has already been + // issued based on the time it was generated. + if lastPtr.LastReadTime < createUnixtime { + bmcByteJsonLog, err := json.Marshal(members.Sel[i]) + if err != nil { + slog.Error("failed to convert JSON", "err", err, "serial", m.Serial, "createUnixtime", createUnixtime, "LastReadId", lastId, "ptrDir", c.ptrDir) + } + err = logWriter.write(string(bmcByteJsonLog), m.Serial) + if err != nil { + slog.Error("failed to output log", "err", err, "serial", m.Serial, "createUnixtime", createUnixtime, "LastReadId", lastId, "ptrDir", c.ptrDir) + } + lastPtr.LastReadId = lastId + lastPtr.LastReadTime = createUnixtime + } + } + } + + err = updateLastPointer(LastPointer{ + LastReadTime: createUnixtime, + LastReadId: lastId, + LastError: nil, + }, filePath) + if err != nil { + slog.Error("failed to write a pointer file.", "err", err, "serial", m.Serial, "createUnixtime", createUnixtime, "LastReadId", lastId, "ptrDir", c.ptrDir) + return + } +} diff --git a/bmc-log-collector/log-collector_test.go b/bmc-log-collector/log-collector_test.go new file mode 100644 index 000000000..20357f234 --- /dev/null +++ b/bmc-log-collector/log-collector_test.go @@ -0,0 +1,204 @@ +package main + +import ( + "bufio" + "context" + "crypto/tls" + "encoding/json" + "net" + "net/http" + "os" + "path" + "sync" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +/* +Read the machines list and access iDRAC mock, and eliminate duplicated entry. +*/ +var _ = Describe("gathering up logs", Ordered, func() { + var lc selCollector + var cl *http.Client + var testOutputDir = "testdata/output_log_collector" + var testPointerDir = "testdata/pointers_log_collector" + var serial = "683FPQ3" + var metricsPath = "/testmetrics2" + var metricsPort = ":29000" + + // Start iDRAC Stub + BeforeAll(func(ctx SpecContext) { + os.Remove(path.Join(testOutputDir, serial)) + os.Remove(path.Join(testPointerDir, serial)) + err := os.MkdirAll(testOutputDir, 0755) + Expect(err).NotTo(HaveOccurred()) + err = os.MkdirAll(testPointerDir, 0755) + Expect(err).NotTo(HaveOccurred()) + GinkgoWriter.Println("Start iDRAC Stub") + bm1 := bmcMock{ + host: "127.0.0.1:8180", + resDir: "testdata/redfish_response", + files: []string{"683FPQ3-1.json", "683FPQ3-2.json", "683FPQ3-3.json"}, + accessCounter: make(map[string]int), + responseFiles: make(map[string][]string), + responseDir: make(map[string]string), + isInitmap: true, + } + bm1.startMock() + + // Wait starting stub servers + By("Test stub web access" + bm1.host) + Eventually(func(ctx SpecContext) error { + req, _ := http.NewRequest("GET", "http://"+bm1.host+"/", nil) + client := &http.Client{Timeout: time.Duration(3) * time.Second} + _, err := client.Do(req) + return err + }).WithContext(ctx).Should(Succeed()) + + // Must start metrics exporter, if not it will get SIGSEGV + go func() { + metrics(metricsPath, metricsPort) + }() + }, NodeTimeout(10*time.Second)) + + Context("SEL collector test", func() { + var machinesList []Machine + var err error + var file *os.File + var reader *bufio.Reader + + cl = &http.Client{ + Timeout: time.Duration(10) * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DisableKeepAlives: true, + TLSHandshakeTimeout: 20 * time.Second, + DialContext: (&net.Dialer{ + Timeout: 15 * time.Second, + }).DialContext, + }, + } + lc = selCollector{ + machinesListDir: "testdata/configmap/log-collector-test.json", + rfSelPath: "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries", + ptrDir: testPointerDir, + username: "support", + password: basicAuthPassword, + httpClient: cl, + } + + It("get machine list", func() { + machinesList, err = readMachineList(lc.machinesListDir) + Expect(err).NotTo(HaveOccurred()) + GinkgoWriter.Println("Machine List = ", machinesList) + }) + + It("collect iDRAC log (run1)", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + var wg sync.WaitGroup + + // Choice the test logWriter to write a local file + logWriter := logTest{outputDir: testOutputDir} + for _, m := range machinesList { + wg.Add(1) + go func() { + lc.collectSystemEventLog(ctx, m, logWriter) + Expect(err).NotTo(HaveOccurred()) + wg.Done() + }() + } + wg.Wait() + }) + + It("verify output (run1)", func(ctx SpecContext) { + var result SystemEventLog + file, err = OpenTestResultLog(path.Join(testOutputDir, serial)) + Expect(err).ToNot(HaveOccurred()) + + reader = bufio.NewReaderSize(file, 4096) + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + err = json.Unmarshal([]byte(stringJSON), &result) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("---- serial = ", string(result.Serial)) + GinkgoWriter.Println("-------- id = ", string(result.Id)) + Expect(result.Serial).To(Equal(serial)) + Expect(result.Id).To(Equal("1")) + }, SpecTimeout(10*time.Second)) + + It("collect iDRAC log (run2)", func() { + ctx, cancel := context.WithCancel(context.Background()) + var wg sync.WaitGroup + GinkgoWriter.Println("------ ", machinesList) + + // Choice the test logWriter to write a local file + logWriter := logTest{outputDir: testOutputDir} + for _, m := range machinesList { + wg.Add(1) + go func() { + lc.collectSystemEventLog(ctx, m, logWriter) + Expect(err).NotTo(HaveOccurred()) + wg.Done() + }() + } + defer cancel() + wg.Wait() + }) + + It("verify output (run2)", func(ctx SpecContext) { + var result SystemEventLog + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + err = json.Unmarshal([]byte(stringJSON), &result) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("---- serial = ", string(result.Serial)) + GinkgoWriter.Println("-------- id = ", string(result.Id)) + Expect(result.Serial).To(Equal(serial)) + Expect(result.Id).To(Equal("2")) + + }, SpecTimeout(10*time.Second)) + + It("collect iDRAC log (run3)", func() { + ctx, cancel := context.WithCancel(context.Background()) + var wg sync.WaitGroup + + // Choice the test logWriter to write local file + logWriter := logTest{outputDir: testOutputDir} + for _, m := range machinesList { + wg.Add(1) + go func() { + lc.collectSystemEventLog(ctx, m, logWriter) + Expect(err).NotTo(HaveOccurred()) + wg.Done() + }() + } + defer cancel() + wg.Wait() + }) + + It("verify output (run3)", func(ctx SpecContext) { + var result SystemEventLog + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + err = json.Unmarshal([]byte(stringJSON), &result) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("---- serial = ", string(result.Serial)) + GinkgoWriter.Println("-------- id = ", string(result.Id)) + Expect(result.Serial).To(Equal(serial)) + Expect(result.Id).To(Equal("3")) + file.Close() + }, SpecTimeout(10*time.Second)) + }) +}) diff --git a/bmc-log-collector/log-pointer.go b/bmc-log-collector/log-pointer.go new file mode 100644 index 000000000..0413f345b --- /dev/null +++ b/bmc-log-collector/log-pointer.go @@ -0,0 +1,125 @@ +package main + +import ( + "encoding/json" + "io" + "log/slog" + "os" + "path" + "path/filepath" +) + +type LastPointer struct { + LastReadTime int64 + LastReadId int + LastError error + LastHttpStatusCode int +} + +func checkAndCreatePointerFile(filePath string) error { + var lptr LastPointer + + //If there is no the pointer file then create a new one. + _, err := os.Stat(filePath) + if !os.IsNotExist(err) { + return nil + } + + byteJSON, err := json.Marshal(lptr) + if err != nil { + return err + } + + fd, err := os.Create(filePath) + if err != nil { + return err + } + defer fd.Close() + + _, err = fd.WriteString(string(byteJSON)) + if err != nil { + return err + } + return err +} + +func updateLastPointer(lptr LastPointer, filePath string) error { + byteJSON, err := json.Marshal(lptr) + if err != nil { + return err + } + + fileName := filepath.Base(filePath) + dirName := filepath.Dir(filePath) + tmpPath := dirName + "/_" + fileName + fd, err := os.Create(tmpPath) + if err != nil { + return err + } + defer fd.Close() + + _, err = fd.WriteString(string(byteJSON)) + if err != nil { + return err + } + + err = fd.Sync() + if err != nil { + return err + } + + err = os.Rename(tmpPath, filePath) + if err != nil { + return err + } + return nil +} + +func readLastPointer(filePath string) (LastPointer, error) { + var lptr LastPointer + fd, err := os.Open(filePath) + if err != nil { + return lptr, err + } + defer fd.Close() + byteJSON, err := io.ReadAll(fd) + if err != nil { + return lptr, err + } + err = json.Unmarshal(byteJSON, &lptr) + if err != nil { + return lptr, err + } + return lptr, err +} + +// Delete pointer files when disappear at machines list which from ConfigMap +func deletePtrFileDisappearedSerial(ptrDir string, machinesList []Machine) error { + machineExist := make(map[string]bool, len(machinesList)) + for _, m := range machinesList { + machineExist[m.Serial] = true + } + + files, err := os.ReadDir(ptrDir) + if err != nil { + return err + } + + for _, file := range files { + if file.IsDir() { + continue + } + // Remove the pointer file and metrics + if !machineExist[file.Name()] { + // Remove the pointer file + filePath := path.Join(ptrDir, file.Name()) + err = os.Remove(filePath) + if err != nil { + slog.Error("failed to remove the pointer file", "err", err, "file", filePath) + } + // Clear metrics counter + deleteMetrics(file.Name()) + } + } + return nil +} diff --git a/bmc-log-collector/log-pointer_test.go b/bmc-log-collector/log-pointer_test.go new file mode 100644 index 000000000..40c8c0547 --- /dev/null +++ b/bmc-log-collector/log-pointer_test.go @@ -0,0 +1,140 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Get Machines List", Ordered, func() { + var ptr LastPointer + var err error + var testPointerDir = "testdata/pointers_get_machines" + var serialNormal = "ABCDEF" + var serialForDelete = "WITHDRAWED" + var ml []Machine + + BeforeAll(func() { + err := os.Mkdir(testPointerDir, 0766) + Expect(err).NotTo(HaveOccurred()) + os.Remove(path.Join(testPointerDir, serialNormal)) + os.Remove(path.Join(testPointerDir, serialForDelete)) + + // Create pointer file for delete test + fd, _ := os.Create(path.Join(testPointerDir, serialForDelete)) + lptr := LastPointer{ + LastReadTime: 0, + LastReadId: 0, + } + byteJSON, _ := json.Marshal(lptr) + _, err = fd.WriteString(string(byteJSON)) + Expect(err).NotTo(HaveOccurred()) + fd.Close() + + //file, _ = os.Create(path.Join(testPointerDir, "_"+serialForDelete)) + //_, err = file.WriteString(string(byteJSON)) + //Expect(err).NotTo(HaveOccurred()) + //file.Close() + + // Create machines list for delete test + m0 := Machine{ + Serial: "ABCDEF", + BmcIP: "10.0.0.1", + NodeIP: "10.1.0.1", + } + ml = append(ml, m0) + }) + + Context("create the pointer file", func() { + //var filePath string + filePath := path.Join(testPointerDir, serialNormal) + + It("check and create pointer file", func() { + err := checkAndCreatePointerFile(filePath) + Expect(err).NotTo(HaveOccurred()) + }) + It("read ptr file", func() { + ptr, err = readLastPointer(filePath) + Expect(err).NotTo(HaveOccurred()) + Expect(ptr.LastReadTime).To(Equal(int64(0))) + Expect(ptr.LastReadId).To(Equal(0)) + GinkgoWriter.Println(ptr) + }) + It("update ptr", func() { + ptr.LastReadTime = 1 + ptr.LastReadId = 1 + err := updateLastPointer(ptr, filePath) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + Context("update the pointer file", func() { + filePath := path.Join(testPointerDir, serialNormal) + + It("check and create pointer file", func() { + err := checkAndCreatePointerFile(filePath) + Expect(err).NotTo(HaveOccurred()) + }) + It("read ptr file", func() { + ptr, err = readLastPointer(filePath) + Expect(err).NotTo(HaveOccurred()) + Expect(ptr.LastReadTime).To(Equal(int64(1))) + Expect(ptr.LastReadId).To(Equal(1)) + GinkgoWriter.Println(ptr) + }) + It("update ptr", func() { + ptr.LastReadTime = 2 + ptr.LastReadId = 2 + err := updateLastPointer(ptr, filePath) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + /* + Context("recover from the pointer file lost", func() { + filePath := path.Join(testPointerDir, serialNormal) + + It("Remove the primary pointer file", func() { + err := os.Remove(filePath) + Expect(err).NotTo(HaveOccurred()) + }) + + It("check and create pointer file", func() { + filePath = path.Join(testPointerDir, serialNormal) + err := checkAndCreatePointerFile(filePath) + Expect(err).NotTo(HaveOccurred()) + }) + + It("read ptr file", func() { + ptr, err = readLastPointer(filePath) + Expect(err).NotTo(HaveOccurred()) + Expect(ptr.LastReadTime).To(Equal(int64(2))) + Expect(ptr.LastReadId).To(Equal(2)) + GinkgoWriter.Println(ptr) + }) + It("update ptr", func() { + ptr.LastReadTime = 3 + ptr.LastReadId = 3 + err := updateLastPointer(ptr, filePath) + Expect(err).NotTo(HaveOccurred()) + }) + }) + */ + + Context("delete retired server ptr file", func() { + It("do delete", func() { + fmt.Println("ML=", ml) + err := deletePtrFileDisappearedSerial(testPointerDir, ml) + Expect(err).NotTo(HaveOccurred()) + }) + It("check that file has been deleted", func() { + filePath := path.Join(testPointerDir, serialForDelete) + _, err := os.Open(filePath) + Expect(err).To(HaveOccurred()) + }) + }) +}) diff --git a/bmc-log-collector/machines-list.go b/bmc-log-collector/machines-list.go new file mode 100644 index 000000000..0515b52c8 --- /dev/null +++ b/bmc-log-collector/machines-list.go @@ -0,0 +1,36 @@ +package main + +import ( + "encoding/json" + "io" + "os" +) + +type Machine struct { + Serial string `json:"serial"` + BmcIP string `json:"bmc_ipv4"` + NodeIP string `json:"node_ipv4"` +} + +// Get BMC list from JSON file +func readMachineList(filename string) ([]Machine, error) { + var ml []Machine + + fd, err := os.Open(filename) + if err != nil { + return ml, err + } + defer fd.Close() + + byteData, err := io.ReadAll(fd) + if err != nil { + return ml, err + } + + err = json.Unmarshal(byteData, &ml) + if err != nil { + return ml, err + } + + return ml, nil +} diff --git a/bmc-log-collector/machines-list_test.go b/bmc-log-collector/machines-list_test.go new file mode 100644 index 000000000..23a95174e --- /dev/null +++ b/bmc-log-collector/machines-list_test.go @@ -0,0 +1,54 @@ +package main + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +/* +Tests machineListReader(), which reads a JSON file with a specified path and sets it into a structure. +*/ +var _ = Describe("Get Machines List", Ordered, func() { + Context("Normal", func() { + It("Read JSON file", func() { + ml, err := readMachineList("testdata/configmap/machines-list-test.json") + Expect(err).NotTo(HaveOccurred()) + Expect(ml[0].Serial).To(Equal("server1")) + Expect(ml[0].BmcIP).To(Equal("192.168.0.1")) + Expect(ml[0].NodeIP).To(Equal("172.16.0.1")) + + Expect(ml[1].Serial).To(Equal("server2")) + Expect(ml[1].BmcIP).To(Equal("192.168.0.2")) + Expect(ml[1].NodeIP).To(Equal("172.16.0.2")) + + Expect(ml[2].Serial).To(Equal("server3")) + Expect(ml[2].BmcIP).To(Equal("192.168.0.3")) + Expect(ml[2].NodeIP).To(Equal("172.16.0.3")) + + Expect(ml[3].Serial).To(Equal("server4")) + Expect(ml[3].BmcIP).To(Equal("192.168.0.4")) + Expect(ml[3].NodeIP).To(Equal("172.16.0.4")) + + Expect(ml[4].Serial).To(Equal("server5")) + Expect(ml[4].BmcIP).To(Equal("192.168.0.5")) + Expect(ml[4].NodeIP).To(Equal("172.16.0.5")) + }) + }) + + Context("Abnormal", func() { + It("Abnormal, no existing file", func() { + _, err := readMachineList("testdata/configmap/noexist.json") + Expect(err).To(HaveOccurred()) + }) + + It("Abnormal, lack of element", func() { + _, err := readMachineList("testdata/configmap/damaged.json") + Expect(err).To(HaveOccurred()) + }) + + It("Abnormal, read empty JSON file", func() { + _, err := readMachineList("testdata/configmap/empty.json") + Expect(err).To(HaveOccurred()) + }) + }) +}) diff --git a/bmc-log-collector/main.go b/bmc-log-collector/main.go new file mode 100644 index 000000000..a7508fb84 --- /dev/null +++ b/bmc-log-collector/main.go @@ -0,0 +1,137 @@ +package main + +import ( + "context" + "crypto/tls" + "fmt" + "log" + "log/slog" + "net" + "net/http" + "os" + "os/signal" + "sync" + "syscall" + "time" + + "github.com/spf13/pflag" +) + +type bmcLogWriter interface { + write(stringJson string, serial string) (err error) +} + +func doLogScrapingLoop(config selCollector, logWriter bmcLogWriter) { + config.httpClient = &http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DisableKeepAlives: true, + TLSHandshakeTimeout: 20 * time.Second, + DialContext: (&net.Dialer{ + Timeout: 15 * time.Second, + }).DialContext, + }, + } + + // Set up signal handling + ctx, cancelCause := context.WithCancelCause(context.Background()) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + sig := <-c + cancelCause(fmt.Errorf("%v", sig)) + }() + + // Set interval time + ticker := time.NewTicker(config.intervalTime) + defer ticker.Stop() + + // Expose metrics via HTTP + go metrics("/metrics", ":8080") + + // Scraping loop + var wg sync.WaitGroup + for { + select { + case <-ctx.Done(): + slog.Error("stopped by", "signal", context.Cause(ctx)) + // Graceful stop when catch SIGTERM + ticker.Stop() + wg.Wait() + return + case <-ticker.C: + machinesList, err := readMachineList(config.machinesListDir) + if err != nil { + slog.Error("can't read the machine list", "err", err, "path", config.machinesListDir) + return + } + // Start log collector workers by BMCs + for _, m := range machinesList { + wg.Add(1) + go func() { + config.collectSystemEventLog(ctx, m, logWriter) + wg.Done() + }() + } + wg.Wait() + // Remove ptr files that disappeared the serial in machineList + err = deletePtrFileDisappearedSerial(config.ptrDir, machinesList) + if err != nil { + slog.Error("failed remove the pointer file", "err", err, "path", config.ptrDir) + } + } + } +} + +// BMC log writer to forward Loki +type logProd struct{} + +func (l logProd) write(stringJson string, serial string) error { + // Use default logger to prevent to mix log messages cross go-routine + log.Print(stringJson) + return nil +} + +var ( + flgUserFile *string = pflag.String("bmc-user-json", "/users/neco/bmc-user.json", "User and password of BMC") + flgUserId *string = pflag.String("user-id", "support", "User ID of bmc-user-json JSON file") + flgMachineList *string = pflag.String("machine-list-json", "/config/machineslist.json", "Target machines list of log scraping") + flgPointerDir *string = pflag.String("pointer-dir-path", "/data/pointers", "Data directory of pointer management") + flgScrapingIntervalTime *int = pflag.Int("scraping-interval-time", 300, "Timer(sec) of scraping interval time") +) + +func main() { + pflag.Parse() + + // Setup slog + opts := &slog.HandlerOptions{ + AddSource: true, + } + logger := slog.New(slog.NewJSONHandler(os.Stderr, opts)) + slog.SetDefault(logger) + + // Read user & password for BMC + user, err := LoadBMCUserConfig(*flgUserFile) + if err != nil { + slog.Error("Can't read the user-list on BMC", "err", err) + os.Exit(1) + } + + // Setup log scraping loop + configLc := selCollector{ + machinesListDir: *flgMachineList, + rfSelPath: "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries", + ptrDir: *flgPointerDir, + username: *flgUserId, + password: user.Support.Password.Raw, + intervalTime: time.Duration(*flgScrapingIntervalTime) * time.Second, + } + + // Set BMC log writer + logWriter := logProd{} + log.SetOutput(os.Stdout) + log.SetFlags(0) + slog.Info("bmc-log-collector started", "interval time", *flgScrapingIntervalTime) + doLogScrapingLoop(configLc, logWriter) +} diff --git a/bmc-log-collector/main_test.go b/bmc-log-collector/main_test.go new file mode 100644 index 000000000..d74877beb --- /dev/null +++ b/bmc-log-collector/main_test.go @@ -0,0 +1,321 @@ +package main + +/* + Read the machines list and access iDRAC mock, and eliminate duplicated entry. +*/ +import ( + "bufio" + "encoding/json" + "net/http" + "os" + "path" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Collecting iDRAC Logs", Ordered, func() { + var testOutputDir = "testdata/output_main_test" + var testPointerDir = "testdata/pointers_main_test" + var serial1 = "683FPQ3" + var serial2 = "HN3CLP3" + var serial3 = "J7N6MW3" + + BeforeAll(func(ctx SpecContext) { + GinkgoWriter.Println("start BMC stub servers") + os.Remove(path.Join(testOutputDir, serial1)) + os.Remove(path.Join(testPointerDir, serial1)) + os.Remove(path.Join(testOutputDir, serial2)) + os.Remove(path.Join(testPointerDir, serial2)) + os.Remove(path.Join(testOutputDir, serial3)) + os.Remove(path.Join(testPointerDir, serial3)) + err := os.MkdirAll(testPointerDir, 0750) + Expect(err).ToNot(HaveOccurred()) + err = os.MkdirAll(testOutputDir, 0750) + Expect(err).ToNot(HaveOccurred()) + + bm1 := bmcMock{ + host: "127.0.0.1:7180", + resDir: "testdata/redfish_response", + files: []string{"683FPQ3-1.json", "683FPQ3-2.json", "683FPQ3-3.json"}, + accessCounter: make(map[string]int), + responseFiles: make(map[string][]string), + responseDir: make(map[string]string), + isInitmap: true, + } + bm1.startMock() + + bm2 := bmcMock{ + host: "127.0.0.1:7280", + resDir: "testdata/redfish_response", + files: []string{"HN3CLP3-1.json", "HN3CLP3-2.json", "HN3CLP3-3.json"}, + accessCounter: make(map[string]int), + responseFiles: make(map[string][]string), + responseDir: make(map[string]string), + isInitmap: true, + } + bm2.startMock() + + bm3 := bmcMock{ + host: "127.0.0.1:7380", + resDir: "testdata/redfish_response", + files: []string{"J7N6MW3-1.json", "J7N6MW3-2.json", "J7N6MW3-3.json"}, + accessCounter: make(map[string]int), + responseFiles: make(map[string][]string), + responseDir: make(map[string]string), + isInitmap: true, + } + bm3.startMock() + + // Wait starting stub servers + By("Test stub web access" + bm1.host) + Eventually(func(ctx SpecContext) error { + req, _ := http.NewRequest("GET", "http://"+bm1.host+"/", nil) + client := &http.Client{Timeout: time.Duration(3) * time.Second} + _, err := client.Do(req) + return err + }).WithContext(ctx).Should(Succeed()) + + By("Test stub web access" + bm2.host) + Eventually(func(ctx SpecContext) error { + req, _ := http.NewRequest("GET", "http://"+bm2.host+"/", nil) + client := &http.Client{Timeout: time.Duration(3) * time.Second} + _, err := client.Do(req) + return err + }).WithContext(ctx).Should(Succeed()) + + By("Test stub web access" + bm3.host) + Eventually(func(ctx SpecContext) error { + req, _ := http.NewRequest("GET", "http://"+bm3.host+"/", nil) + client := &http.Client{Timeout: time.Duration(3) * time.Second} + _, err := client.Do(req) + return err + }).WithContext(ctx).Should(Succeed()) + }, NodeTimeout(10*time.Second)) + + Context("stub of main equivalent", func() { + It("main loop test", func() { + intervalTimeString := "10s" + intervalTime, _ := time.ParseDuration(intervalTimeString) + lcConfig := selCollector{ + machinesListDir: "testdata/configmap/serverlist2.json", + rfSelPath: "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries", + ptrDir: testPointerDir, + username: "support", + intervalTime: intervalTime, + } + user, err := LoadBMCUserConfig("testdata/etc/bmc-user.json") + Expect(err).ToNot(HaveOccurred()) + lcConfig.password = user.Support.Password.Raw + + // Setup logWriter for test + logWriter := logTest{ + outputDir: testOutputDir, + } + func() { + go doLogScrapingLoop(lcConfig, logWriter) + }() + // Stop scraper after 30 sec + time.Sleep(30 * time.Second) + }) + }) + + Context("verify 683FPQ3", func() { + var serial string = "683FPQ3" + var file *os.File + var reader *bufio.Reader + var err error + + It("1st reply", func(ctx SpecContext) { + file, err = OpenTestResultLog(path.Join(testOutputDir, serial)) + Expect(err).ToNot(HaveOccurred()) + + reader = bufio.NewReaderSize(file, 4096) + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("---- serial = ", string(rslt.Serial)) + GinkgoWriter.Println("-------- id = ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("1")) + }, SpecTimeout(3*time.Second)) + + It("2nd reply", func(ctx SpecContext) { + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("---- serial = ", string(rslt.Serial)) + GinkgoWriter.Println("-------- id = ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("2")) + }, SpecTimeout(30*time.Second)) + }) + + Context("verify HN3CLP3", func() { + var serial string = "HN3CLP3" + var file *os.File + var reader *bufio.Reader + var err error + + It("check 1st log record", func(ctx SpecContext) { + file, err = OpenTestResultLog(path.Join(testOutputDir, serial)) + Expect(err).ToNot(HaveOccurred()) + reader = bufio.NewReaderSize(file, 4096) + + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("------ ", string(rslt.Serial)) + GinkgoWriter.Println("------ ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("1")) + }, SpecTimeout(39*time.Second)) + + It("check 2nd log record", func(ctx SpecContext) { + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("------ ", string(rslt.Serial)) + GinkgoWriter.Println("------ ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("2")) + }, SpecTimeout(30*time.Second)) + + It("check 3rd log record", func(ctx SpecContext) { + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("------ ", string(rslt.Serial)) + GinkgoWriter.Println("------ ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("3")) + }, SpecTimeout(30*time.Second)) + + It("check 4th log record", func(ctx SpecContext) { + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("------ ", string(rslt.Serial)) + GinkgoWriter.Println("------ ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("4")) + }, SpecTimeout(30*time.Second)) + }) + + Context("verify J7N6MW3", func() { + var serial string = "J7N6MW3" + var file *os.File + var reader *bufio.Reader + var err error + + It("check 1st log record", func(ctx SpecContext) { + file, err = OpenTestResultLog(path.Join(testOutputDir, serial)) + Expect(err).ToNot(HaveOccurred()) + reader = bufio.NewReaderSize(file, 4096) + + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("------ ", string(rslt.Serial)) + GinkgoWriter.Println("------ ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("1")) + }, SpecTimeout(30*time.Second)) + + It("check 2nd log record", func(ctx SpecContext) { + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("------ ", string(rslt.Serial)) + GinkgoWriter.Println("------ ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("2")) + }, SpecTimeout(30*time.Second)) + + It("check 3rd log record which after SEL cleanup", func(ctx SpecContext) { + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("------ ", string(rslt.Serial)) + GinkgoWriter.Println("------ ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("1")) + }, SpecTimeout(30*time.Second)) + + It("check 4th log record which after SEL cleanup", func(ctx SpecContext) { + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("------ ", string(rslt.Serial)) + GinkgoWriter.Println("------ ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("2")) + }, SpecTimeout(30*time.Second)) + + It("check 5th log record which after SEL cleanup", func(ctx SpecContext) { + stringJSON, err := ReadingTestResultLogNext(reader) + Expect(err).ToNot(HaveOccurred()) + GinkgoWriter.Println("**** Received stringJSON=", stringJSON) + + var rslt SystemEventLog + err = json.Unmarshal([]byte(stringJSON), &rslt) + Expect(err).ToNot(HaveOccurred()) + + GinkgoWriter.Println("------ ", string(rslt.Serial)) + GinkgoWriter.Println("------ ", string(rslt.Id)) + Expect(rslt.Serial).To(Equal(serial)) + Expect(rslt.Id).To(Equal("3")) + }, SpecTimeout(30*time.Second)) + }) +}) diff --git a/bmc-log-collector/metrics.go b/bmc-log-collector/metrics.go new file mode 100644 index 000000000..6e4373c06 --- /dev/null +++ b/bmc-log-collector/metrics.go @@ -0,0 +1,47 @@ +package main + +import ( + "log/slog" + "net/http" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +var counterRequestFailed = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "failed_counter", + Help: "The failed count for Redfish of BMC accessing", + }, + []string{"serial"}, +) + +var counterRequestSuccess = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "success_counter", + Help: "The success count for Redfish of BMC accessing", + }, + []string{"serial"}, +) + +func metrics(path string, port string) { + reg := prometheus.NewRegistry() + reg.MustRegister(counterRequestFailed) + reg.MustRegister(counterRequestSuccess) + + // Expose the registered metrics via HTTP. + http.Handle(path, promhttp.HandlerFor( + reg, + promhttp.HandlerOpts{ + // Opt into OpenMetrics to support exemplars. + EnableOpenMetrics: true, + }, + )) + slog.Error("error at ListenAndServe", "err", http.ListenAndServe(port, nil)) +} + +func deleteMetrics(serial string) { + counterRequestSuccess.DeleteLabelValues(serial) + counterRequestFailed.DeleteLabelValues(serial) +} diff --git a/bmc-log-collector/metrics_test.go b/bmc-log-collector/metrics_test.go new file mode 100644 index 000000000..29c8e9669 --- /dev/null +++ b/bmc-log-collector/metrics_test.go @@ -0,0 +1,169 @@ +package main + +import ( + "io" + "net/http" + "strconv" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/prometheus/common/expfmt" +) + +var _ = Describe("Get Metrics export", Ordered, func() { + var metricsPath = "/testmetrics1" + var metricsPort = ":28000" + BeforeAll(func() { + go func() { + metrics(metricsPath, metricsPort) + }() + }) + + Context("Normal case", func() { + var metricsLines []string + It("put metrics at failed case", func() { + counterRequestFailed.WithLabelValues("ABC123X").Inc() + }) + It("get metrics at success case", func() { + counterRequestSuccess.WithLabelValues("ABC123X").Inc() + }) + It("get metrics", func() { + url := "http://localhost" + metricsPort + metricsPath + req, err := http.NewRequest("GET", url, nil) + Expect(err).NotTo(HaveOccurred()) + + client := &http.Client{Timeout: time.Duration(10) * time.Second} + resp, err := client.Do(req) + Expect(err).NotTo(HaveOccurred()) + defer resp.Body.Close() + + buf, err := io.ReadAll(resp.Body) + Expect(err).NotTo(HaveOccurred()) + metricsLines = strings.Split(string(buf), "\n") + for i, v := range metricsLines { + GinkgoWriter.Println(i, v) + } + GinkgoWriter.Println(metricsLines) + }) + + It("verify HELP line in metrics", func() { + Expect(searchMetricsComment(metricsLines, "# HELP failed_counter The failed count for Redfish of BMC accessing")).To(Equal(true)) + }) + It("verify TYPE line in metrics", func() { + Expect(searchMetricsComment(metricsLines, "# TYPE failed_counter counter")).To(Equal(true)) + }) + + It("iDRAC ABC123X 172.16.0.1 failed", func() { + metricsLine, err := findMetrics(metricsLines, "failed_counter") + Expect(err).NotTo(HaveOccurred()) + + p := expfmt.TextParser{} + metricsFamily, err := p.TextToMetricFamilies(strings.NewReader(metricsLine)) + if err != nil { + GinkgoWriter.Println("err ", err) + } + + for _, v := range metricsFamily { + GinkgoWriter.Printf("name=%s, type=%s \n", v.GetName(), v.GetType()) + Expect(v.GetName()).To(Equal("failed_counter")) + } + for _, v := range metricsFamily { + for idx, l := range v.GetMetric()[0].Label { + GinkgoWriter.Printf("idx=%d label name=%s, value=%s \n", idx, l.GetName(), l.GetValue()) + if l.GetValue() == "172.16.0.1" { + switch idx { + case 0: + Expect(l.GetName()).To(Equal("ip_addr")) + Expect(l.GetValue()).To(Equal("172.16.0.1")) + case 1: + Expect(l.GetName()).To(Equal("serial")) + Expect(l.GetValue()).To(Equal("ABC123X")) + } + GinkgoWriter.Printf("untyped value=%f \n", v.GetMetric()[0].Untyped.GetValue()) + f, err := strconv.ParseFloat("1", 64) + if err != nil { + GinkgoWriter.Printf("error %w", err) + } + Expect(v.GetMetric()[0].Untyped.GetValue()).To(Equal(f)) + } + } + } + }) + + It("verify HELP line in metrics", func() { + Expect(searchMetricsComment(metricsLines, "# HELP success_counter The success count for Redfish of BMC accessing")).To(Equal(true)) + }) + It("verify TYPE line in metrics", func() { + Expect(searchMetricsComment(metricsLines, "# TYPE success_counter counter")).To(Equal(true)) + }) + + It("iDRAC ABC123X 172.16.0.1 success", func() { + metricsLine, err := findMetrics(metricsLines, "success_counter") + Expect(err).NotTo(HaveOccurred()) + p := expfmt.TextParser{} + metricsFamily, err := p.TextToMetricFamilies(strings.NewReader(metricsLine)) + if err != nil { + GinkgoWriter.Println("err ", err) + } + + for _, v := range metricsFamily { + GinkgoWriter.Printf("name=%s, type=%s \n", v.GetName(), v.GetType()) + Expect(v.GetName()).To(Equal("success_counter")) + } + + for _, v := range metricsFamily { + for idx, l := range v.GetMetric()[0].Label { + GinkgoWriter.Printf("idx=%d label name=%s, value=%s \n", idx, l.GetName(), l.GetValue()) + if l.GetValue() == "172.16.0.1" { + switch idx { + case 0: + Expect(l.GetName()).To(Equal("ip_addr")) + Expect(l.GetValue()).To(Equal("172.16.0.1")) + case 1: + Expect(l.GetName()).To(Equal("serial")) + Expect(l.GetValue()).To(Equal("ABC123X")) + } + GinkgoWriter.Printf("untyped value=%f \n", v.GetMetric()[0].Untyped.GetValue()) + f, err := strconv.ParseFloat("1", 64) + if err != nil { + GinkgoWriter.Printf("error %w", err) + } + Expect(v.GetMetric()[0].Untyped.GetValue()).To(Equal(f)) + } + } + } + }) + }) + + Context("Delete case", func() { + var metricsLines []string + It("delete iDRAC ABC123X 172.16.0.1 success", func() { + deleteMetrics("ABC123X") + }) + + It("after delete, get metrics", func() { + url := "http://localhost" + metricsPort + metricsPath + req, err := http.NewRequest("GET", url, nil) + Expect(err).NotTo(HaveOccurred()) + + client := &http.Client{Timeout: time.Duration(10) * time.Second} + resp, err := client.Do(req) + Expect(err).NotTo(HaveOccurred()) + defer resp.Body.Close() + + buf, err := io.ReadAll(resp.Body) + Expect(err).NotTo(HaveOccurred()) + metricsLines = strings.Split(string(buf), "\n") + for i, v := range metricsLines { + GinkgoWriter.Println(i, v) + } + GinkgoWriter.Println(metricsLines) + }) + + It("after delete, verify deleted line in metrics", func() { + Expect(searchMetricsComment(metricsLines, "ABC123X")).NotTo(Equal(true)) + }) + }) +}) diff --git a/bmc-log-collector/request-to-bmc.go b/bmc-log-collector/request-to-bmc.go new file mode 100644 index 000000000..0dcd5b267 --- /dev/null +++ b/bmc-log-collector/request-to-bmc.go @@ -0,0 +1,29 @@ +package main + +import ( + "context" + "io" + "net/http" +) + +// Get from Redfish API on BMC REST service +func requestToBmc(ctx context.Context, username string, password string, client *http.Client, url string) ([]byte, int, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, 0, err + } + req.SetBasicAuth(username, password) + req = req.WithContext(ctx) + resp, err := client.Do(req) + if err != nil { + return nil, 0, err + } + defer resp.Body.Close() + + buf, err := io.ReadAll(resp.Body) + if err != nil { + return nil, resp.StatusCode, err + } + + return buf, resp.StatusCode, nil +} diff --git a/bmc-log-collector/request-to-bmc_test.go b/bmc-log-collector/request-to-bmc_test.go new file mode 100644 index 000000000..1ed0c8b26 --- /dev/null +++ b/bmc-log-collector/request-to-bmc_test.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "crypto/tls" + "net" + "net/http" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +/* +Test the behavior of accessing iDRAC internal web services +*/ +var _ = Describe("Access BMC", Ordered, func() { + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + username := "support" + password := basicAuthPassword + client := &http.Client{ + Timeout: time.Duration(30) * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DisableKeepAlives: true, + TLSHandshakeTimeout: 30 * time.Second, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + }).DialContext, + }, + } + + BeforeAll(func(ctx SpecContext) { + GinkgoWriter.Println("*** Start iDRAC Stub") + bm1 := bmcMock{ + host: "127.0.0.1:19082", + resDir: "testdata/redfish_response", + files: []string{"683FPQ3-1.json", "683FPQ3-2.json", "683FPQ3-3.json"}, + accessCounter: make(map[string]int), + responseFiles: make(map[string][]string), + responseDir: make(map[string]string), + isInitmap: true, + } + bm1.startMock() + + // Wait for starting mock web server + By("Test stub web access" + bm1.host) + Eventually(func(ctx SpecContext) error { + req, _ := http.NewRequest("GET", "http://"+bm1.host+"/", nil) + client := &http.Client{Timeout: time.Duration(3) * time.Second} + _, err := client.Do(req) + return err + }).WithContext(ctx).Should(Succeed()) + }, NodeTimeout(10*time.Second)) + + AfterAll(func() { + time.Sleep(3 * time.Second) + cancel() + }) + + Context("Access iDRAC server to get SEL", func() { + It("Normal access", func() { + url := "https://127.0.0.1:19082/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries" + byteJSON, httpStatusCode, err := requestToBmc(ctx, username, password, client, url) + Expect(err).NotTo(HaveOccurred()) + Expect(httpStatusCode).To(Equal(200)) + Expect(len(byteJSON)).To(Equal(776)) + }) + + It("Abnormal access, not existing web server", func() { + bad_url := "https://127.0.0.9:19082/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries" + _, _, err := requestToBmc(ctx, username, password, client, bad_url) + Expect(err).To(HaveOccurred()) + }) + + It("Abnormal access, wrong path", func() { + wrong_path := "https://127.0.0.1:19082/redfish/v1/Managers/iDRAC.Embedded.1/LogServ1ces/Sel/EntriesWrong" + _, httpStatusCode, err := requestToBmc(ctx, username, password, client, wrong_path) + Expect(httpStatusCode).To(Equal(404)) + Expect(err).NotTo(HaveOccurred()) + }) + + It("Abnormal access, wrong username", func() { + url := "https://127.0.0.1:19082/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries" + bad_username := "badname" + _, httpStatusCode, err := requestToBmc(ctx, bad_username, password, client, url) + Expect(err).ToNot(HaveOccurred()) + Expect(httpStatusCode).To(Equal(401)) + }) + + It("Abnormal access, wrong password", func() { + url := "https://127.0.0.1:19082/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries" + bad_password := "badpassword" + _, httpStatusCode, err := requestToBmc(ctx, username, bad_password, client, url) + Expect(err).NotTo(HaveOccurred()) + Expect(httpStatusCode).To(Equal(401)) + }) + }) +}) diff --git a/bmc-log-collector/suite_test.go b/bmc-log-collector/suite_test.go new file mode 100644 index 000000000..6bcc7de3d --- /dev/null +++ b/bmc-log-collector/suite_test.go @@ -0,0 +1,13 @@ +package main_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestBmcLogCollector(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "BmcLogCollector Suite") +} diff --git a/bmc-log-collector/test_helper.go b/bmc-log-collector/test_helper.go new file mode 100644 index 000000000..3f8b6deb9 --- /dev/null +++ b/bmc-log-collector/test_helper.go @@ -0,0 +1,173 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "os" + "path" + "regexp" + "sync" + "time" +) + +var redfishPath string = "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries" + +// ID & password for basic authentication +const ( + basicAuthUser = "support" + basicAuthPassword = "raw password for support user" +) + +type bmcMock struct { + host string + resDir string + files []string + accessCounter map[string]int + responseFiles map[string][]string + responseDir map[string]string + isInitmap bool + mutex sync.Mutex +} + +// Mock server of iDRAC +func (b *bmcMock) startMock() { + b.mutex.Lock() + b.accessCounter[b.host] = 0 + b.responseFiles[b.host] = b.files + b.responseDir[b.host] = b.resDir + b.mutex.Unlock() + + server := http.NewServeMux() + server.HandleFunc(redfishPath, b.redfishSel) + go func() { + slog.Error("error at ListenAndServeTLS", "err", http.ListenAndServeTLS(b.host, "testdata/ssl/localhost.crt", "testdata/ssl/localhost.key", server)) + }() +} + +// DELL System Event Log Service at Redfish REST +func (b *bmcMock) redfishSel(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json;odata.metadata=minimal;charset=utf-8") + // Basic authentication + if user, pass, ok := r.BasicAuth(); !ok || user != basicAuthUser || pass != basicAuthPassword { + w.Header().Add("WWW-Authenticate", `Basic realm="my private area"`) + w.WriteHeader(http.StatusUnauthorized) + return + } + + // Exclusive lock against other mock server which parallel running + b.mutex.Lock() + defer b.mutex.Unlock() + + // Check a response file is available + key := string(r.Host) + if b.accessCounter[key] > (len(b.responseFiles[key]) - 1) { + time.Sleep(3 * time.Second) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusNotFound) + fmt.Println("error accessCounter[key]", b.accessCounter[key], key, r) + return + } + + fn := b.responseFiles[key][b.accessCounter[key]] + responseFile := path.Join(b.responseDir[key], fn) + b.accessCounter[key] = b.accessCounter[key] + 1 + fmt.Println("accessCounter[key]", b.accessCounter[key], key, r) + + // Create HTTP response from the response file + fd, err := os.Open(responseFile) + if err != nil { + // Create not found response + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusNotFound) + return + } + defer fd.Close() + // BMC working time + time.Sleep(1 * time.Second) + + // Reply + stringJSON, _ := io.ReadAll(fd) + fmt.Fprint(w, string(stringJSON)) +} + +// Method for Test +func OpenTestResultLog(fn string) (*os.File, error) { + var file *os.File + var err error + for { + file, err = os.Open(fn) + if errors.Is(err, os.ErrNotExist) { + time.Sleep(3 * time.Second) + continue + } + break + } + return file, err +} + +// Method for Test +func ReadingTestResultLogNext(b *bufio.Reader) (string, error) { + var stringJSON string + var err error + for { + stringJSON, err = b.ReadString('\n') + if err == io.EOF { + time.Sleep(1 * time.Second) + continue + } + break + } + return stringJSON, err +} + +type logTest struct { + outputDir string +} + +func (l logTest) write(byteJson string, serial string) error { + fn := path.Join(l.outputDir, serial) + fd, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + return err + } + defer fd.Close() + _, err = fd.WriteString(fmt.Sprintln(string(byteJson))) + if err != nil { + return err + } + fmt.Println(byteJson) + return nil +} + +func searchMetricsComment(lines []string, keyword string) bool { + pattern := "^" + keyword + re, err := regexp.Compile(pattern) + if err != nil { + return false + } + for _, line := range lines { + matches := re.FindAllString(line, -1) + if len(matches) > 0 { + return true + } + } + return false +} + +func findMetrics(lines []string, keyword string) (string, error) { + re, err := regexp.Compile(keyword) + if err != nil { + return "", err + } + for _, line := range lines { + matches := re.FindAllString(line, -1) + if len(matches) > 0 { + return line + "\n", nil + } + } + return "", fmt.Errorf("not Found %v", keyword) +} diff --git a/bmc-log-collector/testdata/configmap/damaged.json b/bmc-log-collector/testdata/configmap/damaged.json new file mode 100644 index 000000000..db23676a5 --- /dev/null +++ b/bmc-log-collector/testdata/configmap/damaged.json @@ -0,0 +1 @@ +[{"serial":"683FPQ3","bmc_ipv4":"127.0.0.1:7180","node_ipv4":"10.69.0.6"} diff --git a/bmc-log-collector/testdata/configmap/empty.json b/bmc-log-collector/testdata/configmap/empty.json new file mode 100644 index 000000000..e69de29bb diff --git a/bmc-log-collector/testdata/configmap/log-collector-test.json b/bmc-log-collector/testdata/configmap/log-collector-test.json new file mode 100644 index 000000000..35affbe38 --- /dev/null +++ b/bmc-log-collector/testdata/configmap/log-collector-test.json @@ -0,0 +1 @@ +[{"serial":"683FPQ3","bmc_ipv4":"127.0.0.1:8180","node_ipv4":"10.69.0.6"}] diff --git a/bmc-log-collector/testdata/configmap/machines-list-test.json b/bmc-log-collector/testdata/configmap/machines-list-test.json new file mode 100644 index 000000000..dadd1c04c --- /dev/null +++ b/bmc-log-collector/testdata/configmap/machines-list-test.json @@ -0,0 +1 @@ +[{"serial":"server1","bmc_ipv4":"192.168.0.1","node_ipv4":"172.16.0.1"},{"serial":"server2","bmc_ipv4":"192.168.0.2","node_ipv4":"172.16.0.2"},{"serial":"server3","bmc_ipv4":"192.168.0.3","node_ipv4":"172.16.0.3"},{"serial":"server4","bmc_ipv4":"192.168.0.4","node_ipv4":"172.16.0.4"},{"serial":"server5","bmc_ipv4":"192.168.0.5","node_ipv4":"172.16.0.5"}] diff --git a/bmc-log-collector/testdata/configmap/serverlist-1.json b/bmc-log-collector/testdata/configmap/serverlist-1.json new file mode 100644 index 000000000..33b286e83 --- /dev/null +++ b/bmc-log-collector/testdata/configmap/serverlist-1.json @@ -0,0 +1 @@ +[{"serial":"683FPQ3","bmc_ipv4":"127.0.0.1:7180","node_ipv4":"10.69.0.6"},{"serial":"J7N6MW3","bmc_ipv4":"127.0.0.1:7380","node_ipv4":"10.69.0.8"}] diff --git a/bmc-log-collector/testdata/configmap/serverlist-2.json b/bmc-log-collector/testdata/configmap/serverlist-2.json new file mode 100644 index 000000000..33b286e83 --- /dev/null +++ b/bmc-log-collector/testdata/configmap/serverlist-2.json @@ -0,0 +1 @@ +[{"serial":"683FPQ3","bmc_ipv4":"127.0.0.1:7180","node_ipv4":"10.69.0.6"},{"serial":"J7N6MW3","bmc_ipv4":"127.0.0.1:7380","node_ipv4":"10.69.0.8"}] diff --git a/bmc-log-collector/testdata/configmap/serverlist-3.json b/bmc-log-collector/testdata/configmap/serverlist-3.json new file mode 100644 index 000000000..33b286e83 --- /dev/null +++ b/bmc-log-collector/testdata/configmap/serverlist-3.json @@ -0,0 +1 @@ +[{"serial":"683FPQ3","bmc_ipv4":"127.0.0.1:7180","node_ipv4":"10.69.0.6"},{"serial":"J7N6MW3","bmc_ipv4":"127.0.0.1:7380","node_ipv4":"10.69.0.8"}] diff --git a/bmc-log-collector/testdata/configmap/serverlist.json b/bmc-log-collector/testdata/configmap/serverlist.json new file mode 100644 index 000000000..5fd92ab2b --- /dev/null +++ b/bmc-log-collector/testdata/configmap/serverlist.json @@ -0,0 +1 @@ +[{"serial":"683FPQ3","bmc_ipv4":"127.0.0.1:7180","node_ipv4":"10.69.0.6"},{"serial":"HN3CLP3","bmc_ipv4":"127.0.0.1:7280","node_ipv4":"10.69.0.7"}] diff --git a/bmc-log-collector/testdata/configmap/serverlist2.json b/bmc-log-collector/testdata/configmap/serverlist2.json new file mode 100644 index 000000000..373f6f6fa --- /dev/null +++ b/bmc-log-collector/testdata/configmap/serverlist2.json @@ -0,0 +1 @@ +[{"serial":"683FPQ3","bmc_ipv4":"127.0.0.1:7180","node_ipv4":"10.69.0.6"},{"serial":"HN3CLP3","bmc_ipv4":"127.0.0.1:7280","node_ipv4":"10.69.0.7"},{"serial":"J7N6MW3","bmc_ipv4":"127.0.0.1:7380","node_ipv4":"10.69.0.8"}] diff --git a/bmc-log-collector/testdata/etc/bmc-user-error.json b/bmc-log-collector/testdata/etc/bmc-user-error.json new file mode 100644 index 000000000..05f6f8e33 --- /dev/null +++ b/bmc-log-collector/testdata/etc/bmc-user-error.json @@ -0,0 +1,19 @@ +{ + "root": { + "password": { + "hash": "ABC", + "salt": "123" + } + }, + "repair": { + "password": { + "hash": "XYZ", + "salt": "456" + } + }, + "power": { + "password": { + "raw": "789" + } + } +} diff --git a/bmc-log-collector/testdata/etc/bmc-user.json b/bmc-log-collector/testdata/etc/bmc-user.json new file mode 100644 index 000000000..456053f58 --- /dev/null +++ b/bmc-log-collector/testdata/etc/bmc-user.json @@ -0,0 +1,24 @@ +{ + "root": { + "password": { + "hash": "ABC", + "salt": "123" + } + }, + "repair": { + "password": { + "hash": "XYZ", + "salt": "456" + } + }, + "power": { + "password": { + "raw": "789" + } + }, + "support": { + "password": { + "raw": "raw password for support user" + } + } +} diff --git a/bmc-log-collector/testdata/output_log_collector/683FPQ3 b/bmc-log-collector/testdata/output_log_collector/683FPQ3 new file mode 100644 index 000000000..0ba52ef0b --- /dev/null +++ b/bmc-log-collector/testdata/output_log_collector/683FPQ3 @@ -0,0 +1,3 @@ +{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_15_0.LogEntry","Created":"2022-08-31T09:53:17-05:00","Description":"Log Entry 1","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK","Serial":"683FPQ3","NodeIP":"10.69.0.6","BmcIP":"127.0.0.1:8180"} +{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2","@odata.type":"#LogEntry.v1_15_0.LogEntry","Created":"2023-12-19T05:58:45-06:00","Description":"Log Entry 2","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0041","Id":"2","Message":"The system stopped responding because of an exception while running the operating system.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6fa17973","Name":"Log Entry 2","SensorNumber":115,"SensorType":"OS Stop/Shutdown","Severity":"OK","Serial":"683FPQ3","NodeIP":"10.69.0.6","BmcIP":"127.0.0.1:8180"} +{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/3","@odata.type":"#LogEntry.v1_15_0.LogEntry","Created":"2034-07-27T02:23:28-05:00","Description":"Log Entry 3","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0073","Id":"3","Message":"OEM software event.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"72696767","Name":"Log Entry 3","SensorNumber":116,"SensorType":"OS Stop/Shutdown","Severity":"OK","Serial":"683FPQ3","NodeIP":"10.69.0.6","BmcIP":"127.0.0.1:8180"} diff --git a/bmc-log-collector/testdata/redfish_response/683FPQ3-1.json b/bmc-log-collector/testdata/redfish_response/683FPQ3-1.json new file mode 100644 index 000000000..83de83c21 --- /dev/null +++ b/bmc-log-collector/testdata/redfish_response/683FPQ3-1.json @@ -0,0 +1 @@ +{"@odata.context":"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection","@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries","@odata.type":"#LogEntryCollection.LogEntryCollection","Description":"System Event Logs for this manager","Members":[{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_15_0.LogEntry","Created":"2022-08-31T09:53:17-05:00","Description":"Log Entry 1","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK"}],"Members@odata.count":1,"Name":"Log Entry Collection"} diff --git a/bmc-log-collector/testdata/redfish_response/683FPQ3-2.json b/bmc-log-collector/testdata/redfish_response/683FPQ3-2.json new file mode 100644 index 000000000..8bd7c606d --- /dev/null +++ b/bmc-log-collector/testdata/redfish_response/683FPQ3-2.json @@ -0,0 +1 @@ +{"@odata.context":"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection","@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries","@odata.type":"#LogEntryCollection.LogEntryCollection","Description":"System Event Logs for this manager","Members":[{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2","@odata.type":"#LogEntry.v1_15_0.LogEntry","Created":"2023-12-19T05:58:45-06:00","Description":"Log Entry 2","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0041","Id":"2","Message":"The system stopped responding because of an exception while running the operating system.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6fa17973","Name":"Log Entry 2","SensorNumber":115,"SensorType":"OS Stop/Shutdown","Severity":"OK"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_15_0.LogEntry","Created":"2022-08-31T09:53:17-05:00","Description":"Log Entry 1","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK"}],"Members@odata.count":2,"Name":"Log Entry Collection"} diff --git a/bmc-log-collector/testdata/redfish_response/683FPQ3-3.json b/bmc-log-collector/testdata/redfish_response/683FPQ3-3.json new file mode 100644 index 000000000..09005c974 --- /dev/null +++ b/bmc-log-collector/testdata/redfish_response/683FPQ3-3.json @@ -0,0 +1 @@ +{"@odata.context":"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection","@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries","@odata.type":"#LogEntryCollection.LogEntryCollection","Description":"System Event Logs for this manager","Members":[{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/3","@odata.type":"#LogEntry.v1_15_0.LogEntry","Created":"2034-07-27T02:23:28-05:00","Description":"Log Entry 3","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0073","Id":"3","Message":"OEM software event.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"72696767","Name":"Log Entry 3","SensorNumber":116,"SensorType":"OS Stop/Shutdown","Severity":"OK"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2","@odata.type":"#LogEntry.v1_15_0.LogEntry","Created":"2023-12-19T05:58:45-06:00","Description":"Log Entry 2","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0041","Id":"2","Message":"The system stopped responding because of an exception while running the operating system.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6fa17973","Name":"Log Entry 2","SensorNumber":115,"SensorType":"OS Stop/Shutdown","Severity":"OK"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_15_0.LogEntry","Created":"2022-08-31T09:53:17-05:00","Description":"Log Entry 1","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK"}],"Members@odata.count":3,"Name":"Log Entry Collection"} diff --git a/bmc-log-collector/testdata/redfish_response/HN3CLP3-1.json b/bmc-log-collector/testdata/redfish_response/HN3CLP3-1.json new file mode 100644 index 000000000..8e0ac7f5d --- /dev/null +++ b/bmc-log-collector/testdata/redfish_response/HN3CLP3-1.json @@ -0,0 +1 @@ +{"@odata.context":"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection","@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries","@odata.type":"#LogEntryCollection.LogEntryCollection","Description":"System Event Logs for this manager","Members":[{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_6_1.LogEntry","Created":"2022-06-07T17:04:48-05:00","Description":"Log Entry 1","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Links":{},"Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK"}],"Members@odata.count":1,"Name":"Log Entry Collection"} diff --git a/bmc-log-collector/testdata/redfish_response/HN3CLP3-2.json b/bmc-log-collector/testdata/redfish_response/HN3CLP3-2.json new file mode 100644 index 000000000..b500b066a --- /dev/null +++ b/bmc-log-collector/testdata/redfish_response/HN3CLP3-2.json @@ -0,0 +1 @@ +{"@odata.context":"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection","@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries","@odata.type":"#LogEntryCollection.LogEntryCollection","Description":"System Event Logs for this manager","Members":[{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/4","@odata.type":"#LogEntry.v1_6_1.LogEntry","Created":"2023-03-08T05:00:43-06:00","Description":"Log Entry 4","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"4","Links":{},"Message":"The system inlet temperature is greater than the upper warning threshold.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"0157a6a6","Name":"Log Entry 4","SensorNumber":5,"SensorType":"Temperature","Severity":"Warning"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/3","@odata.type":"#LogEntry.v1_6_1.LogEntry","Created":"2023-03-08T02:44:28-06:00","Description":"Log Entry 3","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"3","Links":{},"Message":"The system inlet temperature is within range.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"8157a3a6","Name":"Log Entry 3","SensorNumber":5,"SensorType":"Temperature","Severity":"OK"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2","@odata.type":"#LogEntry.v1_6_1.LogEntry","Created":"2023-02-12T15:50:30-06:00","Description":"Log Entry 2","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"2","Links":{},"Message":"The system inlet temperature is greater than the upper warning threshold.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"0157a6a6","Name":"Log Entry 2","SensorNumber":5,"SensorType":"Temperature","Severity":"Warning"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_6_1.LogEntry","Created":"2022-06-07T17:04:48-05:00","Description":"Log Entry 1","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Links":{},"Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK"}],"Members@odata.count":4,"Name":"Log Entry Collection"} diff --git a/bmc-log-collector/testdata/redfish_response/HN3CLP3-3.json b/bmc-log-collector/testdata/redfish_response/HN3CLP3-3.json new file mode 100644 index 000000000..b500b066a --- /dev/null +++ b/bmc-log-collector/testdata/redfish_response/HN3CLP3-3.json @@ -0,0 +1 @@ +{"@odata.context":"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection","@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries","@odata.type":"#LogEntryCollection.LogEntryCollection","Description":"System Event Logs for this manager","Members":[{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/4","@odata.type":"#LogEntry.v1_6_1.LogEntry","Created":"2023-03-08T05:00:43-06:00","Description":"Log Entry 4","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"4","Links":{},"Message":"The system inlet temperature is greater than the upper warning threshold.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"0157a6a6","Name":"Log Entry 4","SensorNumber":5,"SensorType":"Temperature","Severity":"Warning"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/3","@odata.type":"#LogEntry.v1_6_1.LogEntry","Created":"2023-03-08T02:44:28-06:00","Description":"Log Entry 3","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"3","Links":{},"Message":"The system inlet temperature is within range.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"8157a3a6","Name":"Log Entry 3","SensorNumber":5,"SensorType":"Temperature","Severity":"OK"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2","@odata.type":"#LogEntry.v1_6_1.LogEntry","Created":"2023-02-12T15:50:30-06:00","Description":"Log Entry 2","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"2","Links":{},"Message":"The system inlet temperature is greater than the upper warning threshold.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"0157a6a6","Name":"Log Entry 2","SensorNumber":5,"SensorType":"Temperature","Severity":"Warning"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_6_1.LogEntry","Created":"2022-06-07T17:04:48-05:00","Description":"Log Entry 1","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Links":{},"Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK"}],"Members@odata.count":4,"Name":"Log Entry Collection"} diff --git a/bmc-log-collector/testdata/redfish_response/J7N6MW3-1.json b/bmc-log-collector/testdata/redfish_response/J7N6MW3-1.json new file mode 100644 index 000000000..8b0586043 --- /dev/null +++ b/bmc-log-collector/testdata/redfish_response/J7N6MW3-1.json @@ -0,0 +1 @@ +{"@odata.context":"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection","@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries","@odata.type":"#LogEntryCollection.LogEntryCollection","Description":"System Event Logs for this manager","Members":[{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2","@odata.type":"#LogEntry.v1_14_0.LogEntry","Created":"2024-03-06T02:41:34-06:00","Description":"Log Entry 2","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"2","Message":"The system inlet temperature is greater than the upper warning threshold.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"0157a1a1","Name":"Log Entry 2","SensorNumber":5,"SensorType":"Temperature","Severity":"Warning"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_14_0.LogEntry","Created":"2023-05-25T16:10:58-05:00","Description":"Log Entry 1","EntryCode":"Assert","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK"}],"Members@odata.count":5,"Name":"Log Entry Collection"} diff --git a/bmc-log-collector/testdata/redfish_response/J7N6MW3-2.json b/bmc-log-collector/testdata/redfish_response/J7N6MW3-2.json new file mode 100644 index 000000000..3f0d5624f --- /dev/null +++ b/bmc-log-collector/testdata/redfish_response/J7N6MW3-2.json @@ -0,0 +1 @@ +{"@odata.context":"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection","@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries","@odata.type":"#LogEntryCollection.LogEntryCollection","Description":"System Event Logs for this manager","Members":[{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2","@odata.type":"#LogEntry.v1_14_0.LogEntry","Created":"2024-06-05T05:58:15-05:00","Description":"Log Entry 2","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"2","Message":"The system inlet temperature is greater than the upper warning threshold.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"0157a1a1","Name":"Log Entry 2","SensorNumber":5,"SensorType":"Temperature","Severity":"Warning"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_14_0.LogEntry","Created":"2024-03-13T03:38:54-05:00","Description":"Log Entry 1","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK"}],"Members@odata.count":2,"Name":"Log Entry Collection"} diff --git a/bmc-log-collector/testdata/redfish_response/J7N6MW3-3.json b/bmc-log-collector/testdata/redfish_response/J7N6MW3-3.json new file mode 100644 index 000000000..20b167fb1 --- /dev/null +++ b/bmc-log-collector/testdata/redfish_response/J7N6MW3-3.json @@ -0,0 +1 @@ +{"@odata.context":"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection","@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries","@odata.type":"#LogEntryCollection.LogEntryCollection","Description":"System Event Logs for this manager","Members":[{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/3","@odata.type":"#LogEntry.v1_14_0.LogEntry","Created":"2024-06-19T02:06:39-05:00","Description":"Log Entry 3","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"3","Message":"The system inlet temperature is within range.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"81579ea1","Name":"Log Entry 3","SensorNumber":3,"SensorType":"Temperature","Severity":"OK"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2","@odata.type":"#LogEntry.v1_14_0.LogEntry","Created":"2024-06-05T05:58:15-05:00","Description":"Log Entry 2","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"2","Message":"The system inlet temperature is greater than the upper warning threshold.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"0157a1a1","Name":"Log Entry 2","SensorNumber":5,"SensorType":"Temperature","Severity":"Warning"},{"@odata.id":"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1","@odata.type":"#LogEntry.v1_14_0.LogEntry","Created":"2024-03-13T03:38:54-05:00","Description":"Log Entry 1","EntryCode":"Upper Non-critical - going high","EntryType":"SEL","GeneratorId":"0x0020","Id":"1","Message":"Log cleared.","MessageArgs":[],"MessageArgs@odata.count":0,"MessageId":"6f02ffff","Name":"Log Entry 1","SensorNumber":114,"SensorType":"Event Logging Disabled","Severity":"OK"}],"Members@odata.count":3,"Name":"Log Entry Collection"} diff --git a/bmc-log-collector/testdata/ssl/localCA.crt b/bmc-log-collector/testdata/ssl/localCA.crt new file mode 100644 index 000000000..ee08f0b53 --- /dev/null +++ b/bmc-log-collector/testdata/ssl/localCA.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRzCCAi8CFFfURrfWzcEJy8rWA+cl4LbTPecNMA0GCSqGSIb3DQEBCwUAMGAx +CzAJBgNVBAYTAmpwMQ4wDAYDVQQIDAVDaGliYTESMBAGA1UEBwwJSW56YWktc2hp +MRIwEAYDVQQKDAkiVEtSIEluYyIxCzAJBgNVBAsMAmhxMQwwCgYDVQQDDANsYWIw +HhcNMjQwNzA5MDEwNTQyWhcNMzQwNzA3MDEwNTQyWjBgMQswCQYDVQQGEwJqcDEO +MAwGA1UECAwFQ2hpYmExEjAQBgNVBAcMCUluemFpLXNoaTESMBAGA1UECgwJIlRL +UiBJbmMiMQswCQYDVQQLDAJocTEMMAoGA1UEAwwDbGFiMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAyZCdaedt3PbIHUHgG6gHJQNEV4bfI4w4aw4nYMHs +WPYOwe21K7MdKWOL/bHoDKvzkfwfmcGxebMpnkf6mua3VCrwOkoMnpE+bX6XP+CK +Nfzpe7SK++8zuuwVciiDnSfxLdvVq5J1u6TE/5m7mvOI2CHXE3O/MFtN8nxKEZqT +VBPXkFRO4FiNykvVAStsNZQ+6cYYqNeJZ9IUIS+B2LYS88vQqhvYJxCt9AXibReX +HRgpS0ejs6MU1WxHkkUC0okNOOKqvEwsOMHKFVSDp9ctxFU4Nyv0gpMxCGAkBVP+ +PXgXx1hDmLSSIMxCGRFW1aF+jR646JEGF2Kt8h/nPm+YLQIDAQABMA0GCSqGSIb3 +DQEBCwUAA4IBAQApKulOBQdygPClJRpimJp2oRRy01z+rpEWpHwu77jkmvZyLcwT +uie6lokGM4AuVxFSd63ulwbDUiT8RxdJprP7YJcXpPfhgO8JgDhahNp1Q/C7k173 +Qmh5KVWi+HpoSyMgW3cO6g9ipROkrSAc/NSHCSoylYKnC5k0mAaOHenmwzE4uYYZ +ZbHA1JVMvSuIBjjt+BrzW8kqik9Nb6k1yP9Z4NzJnwWE+b6iDjhjhUZY9EIm6kXJ +ws/AS/ANOjZ8tXkpQyFub/Dj+f5Ug007/aV0Pt+urUTdxFEqWNN2UySbU1VsOXwj +W4WA+yK0fyiMi5jn5XVvT4s8B/bPFNjpOasJ +-----END CERTIFICATE----- diff --git a/bmc-log-collector/testdata/ssl/localCA.csr b/bmc-log-collector/testdata/ssl/localCA.csr new file mode 100644 index 000000000..022cdbffe --- /dev/null +++ b/bmc-log-collector/testdata/ssl/localCA.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpTCCAY0CAQAwYDELMAkGA1UEBhMCanAxDjAMBgNVBAgMBUNoaWJhMRIwEAYD +VQQHDAlJbnphaS1zaGkxEjAQBgNVBAoMCSJUS1IgSW5jIjELMAkGA1UECwwCaHEx +DDAKBgNVBAMMA2xhYjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMmQ +nWnnbdz2yB1B4BuoByUDRFeG3yOMOGsOJ2DB7Fj2DsHttSuzHSlji/2x6Ayr85H8 +H5nBsXmzKZ5H+prmt1Qq8DpKDJ6RPm1+lz/gijX86Xu0ivvvM7rsFXIog50n8S3b +1auSdbukxP+Zu5rziNgh1xNzvzBbTfJ8ShGak1QT15BUTuBYjcpL1QErbDWUPunG +GKjXiWfSFCEvgdi2EvPL0Kob2CcQrfQF4m0Xlx0YKUtHo7OjFNVsR5JFAtKJDTji +qrxMLDjByhVUg6fXLcRVODcr9IKTMQhgJAVT/j14F8dYQ5i0kiDMQhkRVtWhfo0e +uOiRBhdirfIf5z5vmC0CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQAls6OxsrBh +ybl/TbS7SdGvpTmJnBNgcHS438BZCdtOar8vjBV5u5w67XGUC/Qhh91Oy3TaoUk+ +zUhcQwrJEMxmqclLDoiT70qJm2F3SwAzXW4db+6IZZ6rb1Fsf45VI/Ghqmxd6HzV +jqWTdQy1A86O6nyo2ejcaRZipgDfDLEkdvXIHTYJhtxlAa4hRd3HSpUw0icIanFO +/EXomsarE/xINT8DIN0Z7KBosxRh11FbQec+8X4Xx4Gf8WL7vs+9ioLJAvB8G+qs +Ht9J/qUD11DN/es/qZio6L/91/RyHQf3v8KdCqYGXALYe8V3BiXDfsEm6CmFr+ev +Kd3AWQaLAsYx +-----END CERTIFICATE REQUEST----- diff --git a/bmc-log-collector/testdata/ssl/localCA.key b/bmc-log-collector/testdata/ssl/localCA.key new file mode 100644 index 000000000..536584b41 --- /dev/null +++ b/bmc-log-collector/testdata/ssl/localCA.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAyZCdaedt3PbIHUHgG6gHJQNEV4bfI4w4aw4nYMHsWPYOwe21 +K7MdKWOL/bHoDKvzkfwfmcGxebMpnkf6mua3VCrwOkoMnpE+bX6XP+CKNfzpe7SK +++8zuuwVciiDnSfxLdvVq5J1u6TE/5m7mvOI2CHXE3O/MFtN8nxKEZqTVBPXkFRO +4FiNykvVAStsNZQ+6cYYqNeJZ9IUIS+B2LYS88vQqhvYJxCt9AXibReXHRgpS0ej +s6MU1WxHkkUC0okNOOKqvEwsOMHKFVSDp9ctxFU4Nyv0gpMxCGAkBVP+PXgXx1hD +mLSSIMxCGRFW1aF+jR646JEGF2Kt8h/nPm+YLQIDAQABAoIBABKjWKruR25jM2Xh +LYPhKaf2gV9GjWOQF7due52q0Nv5BKxR+qqryUqGde9jf/2L+N3K3nEtQ+fo9TQ5 +m6avJsyVm9kH99rBu3AMgJfbf2w2krBppWBeYf4qzmDb9i+V/esrpLpUtKZBOiYY +ZQyS8Fhr6KNX1Jn4F2EQtRijnnPrp77DFr8nf8ckCgOaKZQUsv+V5lcBfTx1eyN+ +LcOpJhctYOuWU4TK+TFH7w2tQCGo3/aubVmU21LTrNj3D7GW9u8fRwpZn5Xi0Wpm +1IiR7g5J1ox96q5WURkNSskt1pq83q1s4xQ3+5dmrd31HkdHl9pYHToH5ORe4TXm +rWLxUwECgYEA6sw+aMEGiDbIgSwN9hfp0Rdx3DC4nMcCzSoqUNv/0RU+2C0jT7np +6oC6Qe1+eBdoWk2e3F5her2jViOh5ABB7FECBd0szwFiyFL9/uc/bkeFGR7Fm5WB +0haXFcfAaw0vmS26b98ZIrjXBarS6BTdpj2lLsWvEsihK/LHFhe0jnECgYEA28Qi +gimIejpNCX9vtAkfkaH2tezkHDKFnsqxU2F31LJNF+CHsbGmmFm7ebwUOpyKZWYn +rdExLEVzdFy6v9QxejTWaf+0/vKqOyjlhJvSGmNNTEhem//IOk2bm5HenB67Vssl +J/9vK9djBHjuqmKQ2b/YhdI7uP/acxgw9/KSO30CgYEAmnanD+78RxwTJ7c9sAnn +TiCoTPzYVnstwp7cm7k/P+NEJp77PfXGhvkMj1aX/pUXl2C9A6grZL3g+EgUtpPc +yoQ4Wnc4hS4aOEMwyUKEHh561x3BTis67mTiG/f/A+32xESrA/G27XrAKgBJI1im +wDiJtI1kMW0/alQa2NPbemECgYBIGzdzW6tC52QlbKG7AY9RGkI3eM1zjwQWkMzT +3dQSfpoa7nm3TOGrdS21CIfen0QtdvIn7s5ihzsVf+NqU3YUcce0Kck6KWa/WHDO +LM4oCFLxZ4FbT/ZjYtleluLieJbhKHA9imrwiJAdhIxSbVq3h2HErrEXxyLmxpYy +lG10hQKBgEhtld/QPyrSznArlf/W0Hn13AW6xn4F8hROZwgTIEbcbAc4T3BH0rGS +agrVfhuj8GOwBjbR25PJd9WuLk0vnIudus9WGvlI2z9koSflUQ1ZxFpeqaBzi800 +J5BwSXWAd+bFzUG1asbGrysI9ojlH2OEMAHs2+pmc6sDOg5dD/7O +-----END RSA PRIVATE KEY----- diff --git a/bmc-log-collector/testdata/ssl/localCA.srl b/bmc-log-collector/testdata/ssl/localCA.srl new file mode 100644 index 000000000..88218b83e --- /dev/null +++ b/bmc-log-collector/testdata/ssl/localCA.srl @@ -0,0 +1 @@ +421468BA684397E62C558B527BC5D95FA69C7D15 diff --git a/bmc-log-collector/testdata/ssl/localhost.crt b/bmc-log-collector/testdata/ssl/localhost.crt new file mode 100644 index 000000000..85fb89c4e --- /dev/null +++ b/bmc-log-collector/testdata/ssl/localhost.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIUQhRoumhDl+YsVYtSe8XZX6acfRUwDQYJKoZIhvcNAQEL +BQAwYDELMAkGA1UEBhMCanAxDjAMBgNVBAgMBUNoaWJhMRIwEAYDVQQHDAlJbnph +aS1zaGkxEjAQBgNVBAoMCSJUS1IgSW5jIjELMAkGA1UECwwCaHExDDAKBgNVBAMM +A2xhYjAeFw0yNDA3MDkwMTA5MjNaFw0yOTA3MDgwMTA5MjNaMGYxCzAJBgNVBAYT +AmpwMQ4wDAYDVQQIDAVDaGliYTESMBAGA1UEBwwJSW56YWktc2hpMRIwEAYDVQQK +DAkiVEtSIEluYyIxCzAJBgNVBAsMAmhxMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDopu4sYOJXmbwKdleEawLBOQJZ +yj25H+bDBHQWN0HlleF37I7Z6ZQ6kKJAn7b2dQWC1PTix7r58Gfke25ZPTiszLm5 +BckTeSAE44teVwbSIZcuKIvP56qWToTmI8/DZYtEtfuIjdPf2K9KDw8BTTLjsuR9 +UCwkMbqhuYC0uCiGpzjX64aDPZr/U3GtQJf585kWz/gkcTT0dbimvDOHhQ3yCdfa +78CTEYOgLBH4p0skPmlUFN1qrWTjt17RVx9jzoHHFqrrbOrGWJaMpxFonMquTzy8 +PpMKbwLKSfUZBLtplEM3BSU0prr3yQpYPuXfIJE4mnzrY/7Mm8D7vuxgssErAgMB +AAGjSzBJMEcGA1UdEQRAMD6CCWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9t +YWluhwR/AAABggNhcHCCD2FwcC5sb2NhbGRvbWFpbjANBgkqhkiG9w0BAQsFAAOC +AQEAwAfUCHzlKLblzmXAxZZZp8EinXsjrHHPkzsbAgdUfq/MrDfsjaCcTQBuIAID +tJC8NlFjzfEe86O3IIS24cp54tx5rj2liKE90UNHgXahRYtJoqRuLx+ofhl4STbG +PJUw7sFW3mHgriMU+nt70xeXzN9KyWWCt9TtLCPCu99s5GR0VVFa3Z1khthrhCyk +rUJhX/law9xk7fOy4/FzWcdnAiTamdezRjgLgNprrkEsBydLtNgEkt5WrDmg8/AO +KSXidq/GArN0vju+c7P04we2Jk77fw3OcgNCgn2xCaADEI9WNv68YHjWXswBQoAX +MmnYO2svaa3ZYMcDOGmO+h8gZg== +-----END CERTIFICATE----- diff --git a/bmc-log-collector/testdata/ssl/localhost.csr b/bmc-log-collector/testdata/ssl/localhost.csr new file mode 100644 index 000000000..dc960c1d7 --- /dev/null +++ b/bmc-log-collector/testdata/ssl/localhost.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICqzCCAZMCAQAwZjELMAkGA1UEBhMCanAxDjAMBgNVBAgMBUNoaWJhMRIwEAYD +VQQHDAlJbnphaS1zaGkxEjAQBgNVBAoMCSJUS1IgSW5jIjELMAkGA1UECwwCaHEx +EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAOim7ixg4leZvAp2V4RrAsE5AlnKPbkf5sMEdBY3QeWV4XfsjtnplDqQokCf +tvZ1BYLU9OLHuvnwZ+R7blk9OKzMubkFyRN5IATji15XBtIhly4oi8/nqpZOhOYj +z8Nli0S1+4iN09/Yr0oPDwFNMuOy5H1QLCQxuqG5gLS4KIanONfrhoM9mv9Tca1A +l/nzmRbP+CRxNPR1uKa8M4eFDfIJ19rvwJMRg6AsEfinSyQ+aVQU3WqtZOO3XtFX +H2POgccWquts6sZYloynEWicyq5PPLw+kwpvAspJ9RkEu2mUQzcFJTSmuvfJClg+ +5d8gkTiafOtj/sybwPu+7GCywSsCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBZ +ssbEfCWe48dJCAsbvxuC/M1IglHgW99B4D4AArdQOyRltUV4qe9qRjVSH6eesImY +NRXG/5OuRiQaEetmviyGxenJC9MZG667h/9Y7Ot5P78d8hRe+Gvq6/M7OdNuOTFj +M2QU1V14GKeNixrMV0fl5BtFoQ9Rrayz9H79cAEDOnFb1D62B7TMXijbFuTOrAQe +Ve05kURipqaWnfvDRdYRBfeEEIh0lrHlqhHknnzF5mGCCCO3mUWlshosrCiMlG7A +GUcTR2qsjT7qSitD8pU9t3M85/v85Ug64hsTn/bm8tHJYezu79Vs8WEzrOiVUAGG +PppwbXBfk1wyfd3F2PX1 +-----END CERTIFICATE REQUEST----- diff --git a/bmc-log-collector/testdata/ssl/localhost.csx b/bmc-log-collector/testdata/ssl/localhost.csx new file mode 100644 index 000000000..989c4df35 --- /dev/null +++ b/bmc-log-collector/testdata/ssl/localhost.csx @@ -0,0 +1 @@ +subjectAltName = DNS:localhost, DNS:localhost.localdomain, IP:127.0.0.1, DNS:app, DNS:app.localdomain diff --git a/bmc-log-collector/testdata/ssl/localhost.key b/bmc-log-collector/testdata/ssl/localhost.key new file mode 100644 index 000000000..2ae69be6d --- /dev/null +++ b/bmc-log-collector/testdata/ssl/localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA6KbuLGDiV5m8CnZXhGsCwTkCWco9uR/mwwR0FjdB5ZXhd+yO +2emUOpCiQJ+29nUFgtT04se6+fBn5HtuWT04rMy5uQXJE3kgBOOLXlcG0iGXLiiL +z+eqlk6E5iPPw2WLRLX7iI3T39ivSg8PAU0y47LkfVAsJDG6obmAtLgohqc41+uG +gz2a/1NxrUCX+fOZFs/4JHE09HW4prwzh4UN8gnX2u/AkxGDoCwR+KdLJD5pVBTd +aq1k47de0VcfY86Bxxaq62zqxliWjKcRaJzKrk88vD6TCm8Cykn1GQS7aZRDNwUl +NKa698kKWD7l3yCROJp862P+zJvA+77sYLLBKwIDAQABAoIBAGIsZt7VN03t8pqb +m4ymN3Bf5S0MmYUwAAmVLINfZ/BAkfcwHIu59DHObJlGjEdt1BiEUp4L2Eu19bFF +oKgJnIvWjByWY2ZTO360ok/5+0faotf4hh8CTErG8W/H9tkmVWF2RaE28AxKfJWM +yzEqaWoKSD7VAPt4/J9YDwazm25N/kNfI70NmvTSnM4qDK0aeA1za48eyI0DMGoL +6At1PSv4RX2GlLtweNFKVySws/anvfK8RqZWVyAuzBB/G9OdVTA9zTA0RF/wcT5D +WI7lNoLBcj34FVlGgFXArzOIpEjHGOVA+EAkV2WW6vDXHms0j0ttp4NfpG0rhxZ+ +kVZtIPECgYEA91t7Q1XkjUlCP8SQdmNpH3psR2vzO2CF9jNjU0f7TVp7gx/LwdRX +7z1HrChwcXlCawX20uMVzrm+eiCmOsUlcTtxHJ2MTx4HXbk+9oyEHkGpSgKMOBVE +SJdRrlw/XbyRVL5Ls8GLu0Qpp22q9lpaQppi7KeowkwtCkTpx+zcoYkCgYEA8Mfq +amf6XU61XExlRaIQfsbpS5fW+7MZBeAyPvrnmjOfnAB7xqqblbizy32zFM6ZMq+E +O+HBLHH0aSKrdysmsSqKsDZVM+nPx+Tn8c/2cmu9z/+0KE9NiXWG4SjDeqyCD013 +01JsTnzm7Z1rwhnpuotl8PPqtSh25PrkgqtfpBMCgYAWWfvfb8mBQtdrr7VmHUGI +iKV0JrsJ6ITyZF5bTivVJ5H1nU/qKefwRFdPpS/T8SMhTJ6rG74CuiIOIxkiQmJJ +wxdLgKkHFS2ROIYJ/VSK71/+whAtmS5caOAGo2tOaxWu1Ks+iQS5XUVuTaifLjbt +wcminNvRAtUnEmcVm7+sEQKBgQCkXbR2Qti0TYCcq0IO0N01zg+AHP0TAWW5jxx9 +xcXIjCkO032rVtey001ZQku3jnkiEpLQVgIKlxfjlVk7lmdpVcMrG0v6jkfbCzYS +6oYwAcCndsTDJ6+kgGg82Bjpa021qIp95awtwDHcTzQ6Ynodb/99BuVYGXFgSq0n +TlmRpQKBgQCnvFDAccNq9tVJ9ppHqC52YUpXiy20RdX42K0kfIyrXaU+W6aaaHG7 +lf2Lbwr1veL7WhlVT4rNBgmUNrpGhe5YQLvQdtiNBYQbCbUw1JUSv6JWR03xm5iu +qppqFa7gEVMbx7Zql7A7zyoia/do4K1wETZtpe5w1ucWtt3BLS1Yyw== +-----END RSA PRIVATE KEY-----