Skip to content

Commit

Permalink
Add Wrapper for Multipath Configuration Update
Browse files Browse the repository at this point in the history
* Adds wrapper to handle CRUD operations on the multipath configuration.
  • Loading branch information
vivintw authored Sep 17, 2024
1 parent 6b71527 commit 6d55b38
Show file tree
Hide file tree
Showing 20 changed files with 2,276 additions and 46 deletions.
8 changes: 4 additions & 4 deletions cli/cmd/install.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 NetApp, Inc. All Rights Reserved.
// Copyright 2024 NetApp, Inc. All Rights Reserved.

package cmd

Expand All @@ -24,7 +24,7 @@ import (
"github.com/netapp/trident/cli/api"
k8sclient "github.com/netapp/trident/cli/k8s_client"
tridentconfig "github.com/netapp/trident/config"
"github.com/netapp/trident/internal/nodeprep"
"github.com/netapp/trident/internal/nodeprep/validation"
. "github.com/netapp/trident/logging"
"github.com/netapp/trident/utils"
"github.com/netapp/trident/utils/crypto"
Expand Down Expand Up @@ -425,7 +425,7 @@ func processInstallationArguments(_ *cobra.Command) {

persistentObjectLabelKey = TridentPersistentObjectLabelKey
persistentObjectLabelValue = TridentPersistentObjectLabelValue
nodePrep = nodeprep.FormatProtocols(nodePrep)
nodePrep = validation.FormatProtocols(nodePrep)
}

func validateInstallationArguments() error {
Expand All @@ -436,7 +436,7 @@ func validateInstallationArguments() error {
return fmt.Errorf("'%s' is not a valid namespace name; %s", TridentPodNamespace, labelFormat)
}

if err := nodeprep.ValidateProtocols(nodePrep); err != nil {
if err := validation.ValidateProtocols(nodePrep); err != nil {
return err
}

Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1
github.com/hpe-storage/common-host-libs v4.7.1+incompatible
github.com/jarcoal/httpmock v1.3.1
github.com/kr/secureheader v0.2.0
github.com/kubernetes-csi/csi-lib-utils v0.16.0
Expand Down Expand Up @@ -108,6 +109,7 @@ require (
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
Expand Down Expand Up @@ -165,6 +167,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
Expand Down Expand Up @@ -213,6 +215,8 @@ github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoF
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpe-storage/common-host-libs v4.7.1+incompatible h1:ndkULLQW8eul2xQZTKDAP3iGEBRhXkxj9Sc8/alXzVI=
github.com/hpe-storage/common-host-libs v4.7.1+incompatible/go.mod h1:qQxvwt4l9C79p2V8bY1P13As1+ylyznKJVp3K2P5bz8=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down Expand Up @@ -409,6 +413,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
Expand Down Expand Up @@ -476,6 +481,8 @@ gopkg.in/dnaeon/go-vcr.v3 v3.2.0 h1:Rltp0Vf+Aq0u4rQXgmXgtgoRDStTnFN83cWgSGSoRzM=
gopkg.in/dnaeon/go-vcr.v3 v3.2.0/go.mod h1:2IMOnnlx9I6u9x+YBsM3tAMx6AlOxnJ0pWxQAzZ79Ag=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
93 changes: 93 additions & 0 deletions internal/nodeprep/mpathconfig/configuration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2024 NetApp, Inc. All Rights Reserved.

package mpathconfig

//go:generate mockgen -destination=../../../mocks/mock_nodeprep/mock_mpathconfig/mock_config.go github.com/netapp/trident/internal/nodeprep/mpathconfig MpathConfiguration

import (
"bufio"
"fmt"
"os"

"github.com/hpe-storage/common-host-libs/mpathconfig"

. "github.com/netapp/trident/logging"
"github.com/netapp/trident/utils/errors"
)

const (
MultiPathConfigurationLocation = mpathconfig.MPATHCONF
)

type MpathConfiguration interface {
GetRootSection() MpathConfigurationSection
GetSection(sectionName string) (MpathConfigurationSection, error)
PrintConf() []string
SaveConfig(filePath string) error
}

var _ MpathConfiguration = &Configuration{}

type Configuration struct {
configuration *mpathconfig.Configuration
}

func (c *Configuration) GetRootSection() MpathConfigurationSection {
return &Section{configuration: c.configuration, section: c.configuration.GetRoot()}
}

func (c *Configuration) GetSection(sectionName string) (MpathConfigurationSection, error) {
if c.configuration == nil {
return nil, errors.UnsupportedError(fmt.Sprintf("error getting section %s: empty configuration", sectionName))
}

section, err := c.configuration.GetSection(sectionName, "")
if err != nil {
return nil, errors.NotFoundError("error getting section %s: %v", sectionName, err)
}
return &Section{configuration: c.configuration, section: section}, nil
}

func (c *Configuration) PrintConf() []string {
if c.configuration == nil {
return nil
}
return c.configuration.PrintConf()
}

func (c *Configuration) SaveConfig(filePath string) error {
f, err := os.Create(filePath)
if err != nil {
return err
}

defer func() {
_ = f.Close()
}()

s := c.PrintConf()
w := bufio.NewWriter(f)
for _, line := range s {
if _, err = w.WriteString(line); err != nil {
Log().WithField("fileName", filePath).WithError(err).Error("Error writing to multipath configuration file")
}
}
if err = w.Flush(); err != nil {
Log().WithField("fileName", filePath).WithError(err).Error("Error flushing multipath configuration file")
return errors.New(fmt.Sprintf("error flushing multipath configuration file: %v", err))
}

return err
}

func New() (MpathConfiguration, error) {
return NewFromFile(os.DevNull)
}

func NewFromFile(filename string) (MpathConfiguration, error) {
mpathCfg, err := mpathconfig.ParseConfig(filename)
if err != nil {
return nil, fmt.Errorf("error creating mpath config: %v", err)
}
return &Configuration{configuration: mpathCfg}, nil
}
188 changes: 188 additions & 0 deletions internal/nodeprep/mpathconfig/configuration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Copyright 2024 NetApp, Inc. All Rights Reserved.

package mpathconfig_test

import (
"os"
"testing"

"github.com/stretchr/testify/assert"

"github.com/netapp/trident/internal/nodeprep/mpathconfig"
)

func TestNew(t *testing.T) {
config, err := mpathconfig.New()
assert.Nil(t, err)
assert.NotNil(t, config)
assert.IsType(t, &mpathconfig.Configuration{}, config)
}

func TestNewFromFile(t *testing.T) {
type parameters struct {
fileName string
assertError assert.ErrorAssertionFunc
assertConfig assert.ValueAssertionFunc
}

tests := map[string]parameters{
"test with a valid file": {
fileName: os.DevNull,
assertError: assert.NoError,
assertConfig: assert.NotNil,
},
"test with an invalid file": {
fileName: "dd435124-8ef6-4c0c-b926-13f7c1f53ede.conf",
assertError: assert.Error,
assertConfig: assert.Nil,
},
}

for name, params := range tests {
t.Run(name, func(t *testing.T) {
config, err := mpathconfig.NewFromFile(params.fileName)
if params.assertError != nil {
params.assertError(t, err)
}
if params.assertConfig != nil {
params.assertConfig(t, config)
}
})
}
}

func TestConfiguration_GetRootSection(t *testing.T) {
config, err := mpathconfig.New()
assert.Nil(t, err)
assert.NotNil(t, config)

section := config.GetRootSection()
assert.NotNil(t, section)
assert.IsType(t, &mpathconfig.Section{}, section)
}

func TestConfiguration_GetSection(t *testing.T) {
type parameters struct {
getConfig func() mpathconfig.MpathConfiguration
sectionName string
assertError assert.ErrorAssertionFunc
assertSection assert.ValueAssertionFunc
}

tests := map[string]parameters{
"get default section from uninitialized configuration": {
getConfig: func() mpathconfig.MpathConfiguration {
return &mpathconfig.Configuration{}
},
sectionName: mpathconfig.DefaultsSectionName,
assertError: assert.Error,
assertSection: assert.Nil,
},
"get default section from empty configuration": {
getConfig: func() mpathconfig.MpathConfiguration {
config, err := mpathconfig.New()
assert.Nil(t, err)
return config
},
sectionName: mpathconfig.DefaultsSectionName,
assertError: assert.Error,
assertSection: assert.Nil,
},
"get default section from configuration that has a default section": {
getConfig: func() mpathconfig.MpathConfiguration {
config, err := mpathconfig.New()
assert.Nil(t, err)

_, err = config.GetRootSection().AddSection(mpathconfig.DefaultsSectionName)
assert.Nil(t, err)

return config
},
sectionName: mpathconfig.DefaultsSectionName,
assertError: assert.NoError,
assertSection: assert.NotNil,
},
"get invalid section from configuration that has a default section": {
getConfig: func() mpathconfig.MpathConfiguration {
config, err := mpathconfig.New()
assert.Nil(t, err)

_, err = config.GetRootSection().AddSection(mpathconfig.DefaultsSectionName)
assert.Nil(t, err)

return config
},
sectionName: "invalid",
assertError: assert.Error,
assertSection: assert.Nil,
},
}

for name, params := range tests {
t.Run(name, func(t *testing.T) {
config := params.getConfig()

section, err := config.GetSection(params.sectionName)
if params.assertError != nil {
params.assertError(t, err)
}
if params.assertSection != nil {
params.assertSection(t, section)
}
})
}
}

func TestConfiguration_PrintConf(t *testing.T) {
type parameters struct {
getConfig func() mpathconfig.MpathConfiguration
expectedOutput []string
}

tests := map[string]parameters{
"print uninitialized configuration": {
getConfig: func() mpathconfig.MpathConfiguration {
return &mpathconfig.Configuration{}
},
expectedOutput: nil,
},
"print empty configuration": {
getConfig: func() mpathconfig.MpathConfiguration {
config, err := mpathconfig.New()
assert.Nil(t, err)
return config
},
expectedOutput: nil,
},
"print configuration with a default section": {
getConfig: func() mpathconfig.MpathConfiguration {
config, err := mpathconfig.New()
assert.Nil(t, err)

_, err = config.GetRootSection().AddSection(mpathconfig.DefaultsSectionName)
assert.Nil(t, err)

return config
},
expectedOutput: []string{"defaults {\n", "}\n"},
},
}

for name, params := range tests {
t.Run(name, func(t *testing.T) {
config := params.getConfig()
output := config.PrintConf()
assert.Equal(t, params.expectedOutput, output)
})
}
}

func TestConfiguration_SaveConfig(t *testing.T) {
config, err := mpathconfig.New()
assert.NoError(t, err)
defaultSection, err := config.GetRootSection().AddSection(mpathconfig.DefaultsSectionName)
assert.NoError(t, err)
err = defaultSection.SetProperty("find_multipaths", "no")
assert.NoError(t, err)
assert.Nil(t, config.SaveConfig(os.DevNull))
}
Loading

0 comments on commit 6d55b38

Please sign in to comment.