From 1487736d4747fd737b40d0fd2feb7e1a15723602 Mon Sep 17 00:00:00 2001 From: Yiming Bao Date: Thu, 12 Sep 2024 16:08:41 +0800 Subject: [PATCH] Support SDT deployment via CSV - Add SDT related structs - Add RenewInstallationCookie to get valid cookie that can be used during cluster deployment - Replace cookie dir `/home` to user's home directory to avoid the case that user doesn't have write permission to `/home` or OS is Windows --- deploy.go | 68 +++++++++++++++++++++++++++++++++++++++-- deploy_test.go | 46 ++++++++++++++++++++++++++++ go.mod | 4 +-- go.sum | 4 +-- inttests/deploy_test.go | 6 ++++ types/v1/types.go | 17 +++++++++++ 6 files changed, 138 insertions(+), 7 deletions(-) diff --git a/deploy.go b/deploy.go index 6349d2f..52c583f 100644 --- a/deploy.go +++ b/deploy.go @@ -26,6 +26,7 @@ import ( "net/http" "net/url" "os" + "path/filepath" path "path/filepath" "regexp" "strconv" @@ -286,6 +287,14 @@ func (gc *GatewayClient) UploadPackages(filePaths []string) (*types.GatewayRespo gatewayResponse.StatusCode = 200 + // store cookie for successive deployment requests + if gc.version == "4.0" { + err := storeCookie(response.Header, gc.host) + if err != nil { + return &gatewayResponse, fmt.Errorf("Error While Storing cookie: %s", err) + } + } + return &gatewayResponse, nil } @@ -978,6 +987,47 @@ func (gc *GatewayClient) MoveToIdlePhase() (*types.GatewayResponse, error) { return &gatewayResponse, nil } +// RenewInstallationCookie is used to renew the installation cookie, i.e. LEGACYGWCOOKIE. +// Using the same LEGACYGWCOOKIE ensures that the REST requests are sent to the same GW pod. +// That would help to get the correct response from the GW pod that stores installation packages. +func (gc *GatewayClient) RenewInstallationCookie(retryCount int) error { + var packageParam []*types.PackageDetails + + req, httpError := http.NewRequest(http.MethodGet, gc.host+"/im/types/installationPackages/instances?onlyLatest=false&_search=false", nil) + if httpError != nil { + return httpError + } + + if gc.version == "4.0" { + req.Header.Set("Authorization", "Bearer "+gc.token) + } else { + req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(gc.username+":"+gc.password))) + } + req.Header.Set("Content-Type", "application/json") + + for i := 0; i < retryCount; i++ { + httpResp, httpRespError := gc.http.Do(req) + if httpRespError != nil { + continue + } + + responseString, err := extractString(httpResp) + if err != nil { + continue + } + + if httpResp.StatusCode == 200 { + err := json.Unmarshal([]byte(responseString), &packageParam) + // No packages found. Retry to find the cookie that can return packages info + if err != nil || len(packageParam) == 0 || storeCookie(httpResp.Header, gc.host) != nil { + continue + } + return nil + } + } + return fmt.Errorf("Failed to renew installation cookie %d times", retryCount) +} + // GetInQueueCommand used for get in queue commands func (gc *GatewayClient) GetInQueueCommand() ([]types.MDMQueueCommandDetails, error) { var mdmQueueCommandDetails []types.MDMQueueCommandDetails @@ -1156,7 +1206,17 @@ func jsonToMap(jsonStr string) (map[string]interface{}, error) { return result, nil } -const configFile = "/home/.cookie_config.yaml" +// getConfigPath returns the path to the cookie configuration file in the user's home directory. +func getConfigPath() (string, error) { + homeDir, err := os.UserHomeDir() + if err != nil { + return "/home/.cookie_config.yaml", err + } + + configPath := filepath.Join(homeDir, ".cookie_config.yaml") + + return configPath, nil +} var globalCookie string @@ -1233,7 +1293,8 @@ func setCookie(header http.Header, host string) error { } func loadConfig() (*CookieConfig, error) { - if _, err := os.Stat(configFile); err == nil { + configFile, _ := getConfigPath() + if _, err := os.Stat(filepath.Clean(configFile)); err == nil { data, err := ioutil.ReadFile(configFile) if err != nil { return nil, err @@ -1257,7 +1318,8 @@ func writeConfig(config *CookieConfig) error { return err } // #nosec G306 - err = ioutil.WriteFile(configFile, data, 0o644) + configFile, _ := getConfigPath() + err = ioutil.WriteFile(configFile, data, 0o600) if err != nil { return err } diff --git a/deploy_test.go b/deploy_test.go index 20d3478..827b093 100644 --- a/deploy_test.go +++ b/deploy_test.go @@ -421,3 +421,49 @@ func TestUninstallCluster(t *testing.T) { assert.NoError(t, err) assert.Equal(t, http.StatusOK, gatewayResponse.StatusCode) } + +func TestRenewInstallationCookie(t *testing.T) { + responseJSON := `[ + { + "version": "4.5-0.287", + "sioPatchNumber": 0, + "type": "mdm", + "size": 72378708, + "label": "0.287.sles15.3.x86_64", + "operatingSystem": "linux", + "linuxFlavour": "sles15_3", + "activemqPackage": false, + "filename": "EMC-ScaleIO-mdm-4.5-0.287.sles15.3.x86_64.rpm", + "activemqRpmPackage": false, + "activemqUbuntuPackage": false, + "latest": true + }]` + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && strings.Contains(r.URL.Path, "/im/types/installationPackages/instances") { + cookie := &http.Cookie{ + Name: "LEGACYGWCOOKIE", + Value: "123456789", + Path: "/", + } + http.SetCookie(w, cookie) + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(responseJSON)) + if err != nil { + t.Fatalf("Error writing response: %v", err) + } + return + } + http.NotFound(w, r) + })) + defer server.Close() + + gc := &GatewayClient{ + http: &http.Client{}, + host: server.URL, + username: "test_username", + password: "test_password", + } + + err := gc.RenewInstallationCookie(5) + assert.NoError(t, err) +} diff --git a/go.mod b/go.mod index f8bad7f..bf6fddd 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.2.0 // indirect + golang.org/x/sys v0.25.0 // indirect ) -go 1.22 +go 1.22.0 diff --git a/go.sum b/go.sum index c05e5f8..beac687 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/inttests/deploy_test.go b/inttests/deploy_test.go index 10f9658..e463145 100644 --- a/inttests/deploy_test.go +++ b/inttests/deploy_test.go @@ -164,3 +164,9 @@ func TestDeployCheckForCompletionQueueCommands(t *testing.T) { assert.NotNil(t, res) assert.Nil(t, err) } + +// TestRenewInstallationCookie function to test Renew Installation Cookie +func TestRenewInstallationCookie(t *testing.T) { + err := GC.RenewInstallationCookie(5) + assert.Nil(t, err) +} diff --git a/types/v1/types.go b/types/v1/types.go index f715325..be59dbe 100644 --- a/types/v1/types.go +++ b/types/v1/types.go @@ -1658,6 +1658,7 @@ type MDMTopologyDetails struct { SystemVersionName string `json:"systemVersionName,omitempty"` SdsList []SdsList `json:"sdsList,omitempty"` SdcList []SdcList `json:"sdcList,omitempty"` + SdtList []SdtList `json:"sdtList,omitempty"` ProtectionDomains []ProtectionDomains `json:"protectionDomains,omitempty"` SdrList []SdrList `json:"sdrList,omitempty"` VasaProviderList []any `json:"vasaProviderList,omitempty"` @@ -1759,6 +1760,22 @@ type SdsList struct { ID string `json:"id,omitempty"` } +// SdtList defines struct for SDT Details +type SdtList struct { + Node Node `json:"node,omitempty"` + SdtName string `json:"sdtName,omitempty"` + ProtectionDomain string `json:"protectionDomain,omitempty"` + ProtectionDomainID string `json:"protectionDomainId,omitempty"` + AllIPs []string `json:"allIPs,omitempty"` + StorageOnlyIPs []string `json:"storageOnlyIPs,omitempty"` + HostOnlyIPs []string `json:"hostOnlyIPs,omitempty"` + StoragePort int `json:"storagePort,omitempty"` + NvmePort int `json:"nvmePort,omitempty"` + DiscoveryPort int `json:"discoveryPort,omitempty"` + Optimized bool `json:"optimized,omitempty"` + ID string `json:"id,omitempty"` +} + // SdcList defines struct for SDC Details type SdcList struct { Node Node `json:"node,omitempty"`