From 22c562eb850442905869a8a50816752c3f979d1d Mon Sep 17 00:00:00 2001 From: Yiming Bao Date: Tue, 12 Nov 2024 12:19:29 +0800 Subject: [PATCH] Add functions to get and compare PFMP version --- .github/workflows/common-workflows.yaml | 2 +- lcm.go | 102 ++++++++++++++++ lcm_test.go | 149 ++++++++++++++++++++++++ types/v1/types.go | 7 ++ 4 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 lcm.go create mode 100644 lcm_test.go diff --git a/.github/workflows/common-workflows.yaml b/.github/workflows/common-workflows.yaml index 05e4e07..7f5d0ec 100644 --- a/.github/workflows/common-workflows.yaml +++ b/.github/workflows/common-workflows.yaml @@ -14,4 +14,4 @@ jobs: common: name: Quality Checks - uses: dell/common-github-actions/.github/workflows/go-common.yml@main + uses: dell/common-github-actions/.github/workflows/go-common.yml@main \ No newline at end of file diff --git a/lcm.go b/lcm.go new file mode 100644 index 0000000..359e19b --- /dev/null +++ b/lcm.go @@ -0,0 +1,102 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goscaleio + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "time" + + types "github.com/dell/goscaleio/types/v1" +) + +// CheckPfmpVersion checks if the PFMP version is greater than the given version +// Returns -1 if PFMP version < the given version, +// Returns 1 if PFMP version > the given version, +// Returns 0 if PFMP version == the given version. +func CheckPfmpVersion(client *Client, version string) (int, error) { + defer TimeSpent("CheckPfmpVersion", time.Now()) + + lcmStatus, err := GetPfmpStatus(*client) + if err != nil { + return -1, fmt.Errorf("failed to get PFMP version : %v", err) + } + + result, err := CompareVersion(lcmStatus.ClusterVersion, version) + if err != nil { + return -1, err + } + return result, nil +} + +// GetPfmpStatus gets the PFMP status +func GetPfmpStatus(client Client) (*types.LcmStatus, error) { + defer TimeSpent("GetPfmpStatus", time.Now()) + + path := "/Api/V1/corelcm/status" + + var status types.LcmStatus + err := client.getJSONWithRetry( + http.MethodGet, path, nil, &status) + if err != nil { + return nil, err + } + + return &status, nil +} + +// CompareVersion compares two version strings. +// Returns -1 if versionA < versionB, +// Returns 1 if versionA > versionB, +// Returns 0 if versionA == versionB. +func CompareVersion(versionA, versionB string) (int, error) { + partsA := strings.Split(versionA, ".") + partsB := strings.Split(versionB, ".") + + maxLength := len(partsA) + if len(partsB) > maxLength { + maxLength = len(partsB) + } + + // Compare each part of the versions + for i := 0; i < maxLength; i++ { + var partA, partB int + var err error + + if i < len(partsA) { + partA, err = strconv.Atoi(partsA[i]) + if err != nil { + err := fmt.Errorf("error parsing part PFMP version: %s", versionA) + return -1, err + } + } + + if i < len(partsB) { + partB, err = strconv.Atoi(partsB[i]) + if err != nil { + err := fmt.Errorf("error parsing part PFMP version: %s", versionB) + return -1, err + } + } + + if partA < partB { + return -1, nil + } else if partA > partB { + return 1, nil + } + } + + return 0, nil +} diff --git a/lcm_test.go b/lcm_test.go new file mode 100644 index 0000000..97acdfb --- /dev/null +++ b/lcm_test.go @@ -0,0 +1,149 @@ +// Copyright © 2024 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package goscaleio + +import ( + "math" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_CheckPfmpVersion(t *testing.T) { + type checkFn func(*testing.T, int, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, _ int, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + checkVersionEqual := func(t *testing.T, result int, _ error) { + assert.Equal(t, result, 0) + } + + checkVersionGreaterThan := func(t *testing.T, result int, _ error) { + assert.Equal(t, result, 1) + } + + checkVersionLessThan := func(t *testing.T, result int, _ error) { + assert.Equal(t, result, -1) + } + + hasError := func(t *testing.T, _ int, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, string, []checkFn){ + "PFMP version == 4.6": func(t *testing.T) (*httptest.Server, string, []checkFn) { + url := "/Api/V1/corelcm/status" + responseJSON := `{ + "lcmStatus": "READY", + "clusterVersion": "4.6.0.0", + "clusterBuild": "1258" + }` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(responseJSON)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + return + } + http.NotFound(w, r) + })) + return server, "4.6", check(hasNoError, checkVersionEqual) + }, + "PFMP version > 4.6": func(t *testing.T) (*httptest.Server, string, []checkFn) { + url := "/Api/V1/corelcm/status" + responseJSON := `{ + "lcmStatus": "READY", + "clusterVersion": "4.7.0.0", + "clusterBuild": "1258" + }` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(responseJSON)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + return + } + http.NotFound(w, r) + })) + return server, "4.6.0.0", check(hasNoError, checkVersionGreaterThan) + }, + "PFMP version < 4.6": func(t *testing.T) (*httptest.Server, string, []checkFn) { + url := "/Api/V1/corelcm/status" + responseJSON := `{ + "lcmStatus": "READY", + "clusterVersion": "4.5.0.0", + "clusterBuild": "1258" + }` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(responseJSON)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + return + } + http.NotFound(w, r) + })) + return server, "4.6", check(hasNoError, checkVersionLessThan) + }, + "wrong version": func(t *testing.T) (*httptest.Server, string, []checkFn) { + url := "/Api/V1/corelcm/status" + responseJSON := `{ + "lcmStatus": "READY", + "clusterVersion": "4.5.0.0", + "clusterBuild": "1258" + }` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && strings.EqualFold(r.URL.Path, url) { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(responseJSON)) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + return + } + http.NotFound(w, r) + })) + return server, "4.a.b.c", check(hasError) + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + server, version, checkFns := tc(t) + defer server.Close() + + client, _ := NewClientWithArgs(server.URL, "", math.MaxInt64, true, false) + result, err := CheckPfmpVersion(client, version) + + for _, checkFn := range checkFns { + checkFn(t, result, err) + } + }) + } +} diff --git a/types/v1/types.go b/types/v1/types.go index 5302976..e7c0934 100644 --- a/types/v1/types.go +++ b/types/v1/types.go @@ -2184,3 +2184,10 @@ type NvmeController struct { IsAssigned bool `json:"isAssigned"` ID string `json:"id"` } + +// LcmStatus defines struct for PFMP status +type LcmStatus struct { + LcmStatus string `json:"lcmStatus"` + ClusterVersion string `json:"clusterVersion"` + ClusterBuild string `json:"clusterBuild"` +}