Skip to content

Commit

Permalink
Node prep execution
Browse files Browse the repository at this point in the history
Co-authored-by: Vivin Thomas Wilson <vivintwilson@gmail.com>
Co-authored-by: Vivin <vivin@netapp.com>
  • Loading branch information
3 people authored Oct 1, 2024
1 parent 1779f49 commit e0e1fac
Show file tree
Hide file tree
Showing 43 changed files with 2,771 additions and 163 deletions.
6 changes: 3 additions & 3 deletions cli/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/validation"
"github.com/netapp/trident/internal/nodeprep/protocol"
. "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 = validation.FormatProtocols(nodePrep)
nodePrep = protocol.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 := validation.ValidateProtocols(nodePrep); err != nil {
if err := protocol.ValidateProtocols(nodePrep); err != nil {
return err
}

Expand Down
11 changes: 10 additions & 1 deletion cmd/node_prep/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package main

import (
"context"
"flag"
"fmt"
"os"
Expand All @@ -24,7 +25,15 @@ func main() {
"binary": os.Args[0],
}).Info("Running Trident node preparation.")

nodeprep.NewNodePrep().PrepareNode(strings.Split(strings.ToLower(*flags.nodePrep), ","))
ctx := context.Background()

// This is a work-around for osutils because getting system information checks for this environment variable
// to decide if this is running inside a container. osutils should be refactored to not rely on this.
if err := os.Setenv("CSI_ENDPOINT", "unix://run/csi/socket"); err != nil {
Log().Fatal("Failed to set environment variable.")
}

os.Exit(nodeprep.New().Prepare(ctx, strings.Split(strings.ToLower(*flags.nodePrep), ",")))
}

func initLogging(flags appFlags) {
Expand Down
24 changes: 24 additions & 0 deletions internal/nodeprep/execution/executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package execution

import (
"context"

"github.com/netapp/trident/internal/nodeprep/instruction"
. "github.com/netapp/trident/logging"
)

func Execute(ctx context.Context, instructions []instruction.Instructions) (err error) {
for _, i := range instructions {
Log().WithField("instructions", i.GetName()).Info("Preparing node")
if err = i.PreCheck(ctx); err != nil {
return
}
if err = i.Apply(ctx); err != nil {
return
}
if err = i.PostCheck(ctx); err != nil {
return
}
}
return
}
126 changes: 126 additions & 0 deletions internal/nodeprep/execution/executor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2024 NetApp, Inc. All Rights Reserved.

package execution_test

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"

"github.com/netapp/trident/internal/nodeprep/execution"
"github.com/netapp/trident/internal/nodeprep/instruction"
"github.com/netapp/trident/mocks/mock_internal/mock_nodeprep/mock_instruction"
"github.com/netapp/trident/utils/errors"
)

func TestExecute(t *testing.T) {
type parameters struct {
getInstructions func(controller *gomock.Controller) []instruction.Instructions
assertError assert.ErrorAssertionFunc
}

tests := map[string]parameters{
"execute single instruction successfully": {
getInstructions: func(controller *gomock.Controller) []instruction.Instructions {
mockInstruction := mock_instruction.NewMockInstructions(controller)
mockInstruction.EXPECT().GetName().Return("mockInstruction")
mockInstruction.EXPECT().PreCheck(gomock.Any()).Return(nil)
mockInstruction.EXPECT().Apply(gomock.Any()).Return(nil)
mockInstruction.EXPECT().PostCheck(gomock.Any()).Return(nil)
return []instruction.Instructions{mockInstruction}
},
assertError: assert.NoError,
},
"execute single instruction pre check failure": {
getInstructions: func(controller *gomock.Controller) []instruction.Instructions {
mockInstruction := mock_instruction.NewMockInstructions(controller)
mockInstruction.EXPECT().GetName().Return("mockInstruction")
mockInstruction.EXPECT().PreCheck(gomock.Any()).Return(errors.New("mock error"))
return []instruction.Instructions{mockInstruction}
},
assertError: assert.Error,
},
"execute single instruction apply failure": {
getInstructions: func(controller *gomock.Controller) []instruction.Instructions {
mockInstruction := mock_instruction.NewMockInstructions(controller)
mockInstruction.EXPECT().GetName().Return("mockInstruction")
mockInstruction.EXPECT().PreCheck(gomock.Any()).Return(nil)
mockInstruction.EXPECT().Apply(gomock.Any()).Return(errors.New("mock error"))
return []instruction.Instructions{mockInstruction}
},
assertError: assert.Error,
},
"execute single instruction post check failure": {
getInstructions: func(controller *gomock.Controller) []instruction.Instructions {
mockInstruction := mock_instruction.NewMockInstructions(controller)
mockInstruction.EXPECT().GetName().Return("mockInstruction")
mockInstruction.EXPECT().PreCheck(gomock.Any()).Return(nil)
mockInstruction.EXPECT().Apply(gomock.Any()).Return(nil)
mockInstruction.EXPECT().PostCheck(gomock.Any()).Return(errors.New("mock error"))
return []instruction.Instructions{mockInstruction}
},
assertError: assert.Error,
},
"execute multi instruction successfully": {
getInstructions: func(controller *gomock.Controller) []instruction.Instructions {
mockInstruction1 := mock_instruction.NewMockInstructions(controller)
mockInstruction1.EXPECT().GetName().Return("mockInstruction")
mockInstruction1.EXPECT().PreCheck(gomock.Any()).Return(nil)
mockInstruction1.EXPECT().Apply(gomock.Any()).Return(nil)
mockInstruction1.EXPECT().PostCheck(gomock.Any()).Return(nil)

mockInstruction2 := mock_instruction.NewMockInstructions(controller)
mockInstruction2.EXPECT().GetName().Return("mockInstruction")
mockInstruction2.EXPECT().PreCheck(gomock.Any()).Return(nil)
mockInstruction2.EXPECT().Apply(gomock.Any()).Return(nil)
mockInstruction2.EXPECT().PostCheck(gomock.Any()).Return(nil)

return []instruction.Instructions{mockInstruction1, mockInstruction2}
},
assertError: assert.NoError,
},
"execute multi instruction first instruction failure": {
getInstructions: func(controller *gomock.Controller) []instruction.Instructions {
mockInstruction1 := mock_instruction.NewMockInstructions(controller)
mockInstruction1.EXPECT().GetName().Return("mockInstruction")
mockInstruction1.EXPECT().PreCheck(gomock.Any()).Return(nil)
mockInstruction1.EXPECT().Apply(gomock.Any()).Return(errors.New("mock error"))

mockInstruction2 := mock_instruction.NewMockInstructions(controller)

return []instruction.Instructions{mockInstruction1, mockInstruction2}
},
assertError: assert.Error,
},
"execute multi instruction second instruction failure": {
getInstructions: func(controller *gomock.Controller) []instruction.Instructions {
mockInstruction1 := mock_instruction.NewMockInstructions(controller)
mockInstruction1.EXPECT().GetName().Return("mockInstruction")
mockInstruction1.EXPECT().PreCheck(gomock.Any()).Return(nil)
mockInstruction1.EXPECT().Apply(gomock.Any()).Return(nil)
mockInstruction1.EXPECT().PostCheck(gomock.Any()).Return(nil)

mockInstruction2 := mock_instruction.NewMockInstructions(controller)
mockInstruction2.EXPECT().GetName().Return("mockInstruction")
mockInstruction2.EXPECT().PreCheck(gomock.Any()).Return(nil)
mockInstruction2.EXPECT().Apply(gomock.Any()).Return(errors.New("mock error"))

return []instruction.Instructions{mockInstruction1, mockInstruction2}
},
assertError: assert.Error,
},
}

for name, params := range tests {
t.Run(name, func(t *testing.T) {
ctrl := gomock.NewController(t)

err := execution.Execute(context.TODO(), params.getInstructions(ctrl))
if params.assertError != nil {
params.assertError(t, err)
}
})
}
}
62 changes: 62 additions & 0 deletions internal/nodeprep/instruction/default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2024 NetApp, Inc. All Rights Reserved.

package instruction

import (
"context"

"github.com/netapp/trident/internal/nodeprep/step"
. "github.com/netapp/trident/logging"
)

const defaultInstructionName = "default instructions"

type Default struct {
name string
steps []step.Step
}

func (r *Default) GetName() string {
if r.name == "" {
return defaultInstructionName
}
return r.name
}

func (r *Default) GetSteps() []step.Step {
return r.steps
}

func (r *Default) PreCheck(_ context.Context) error {
return nil
}

func (r *Default) Apply(ctx context.Context) error {
Log().Infof("Applying %s", r.GetName())
for _, s := range r.steps {
if err := executeStep(ctx, s); err != nil {
return err
}
}
return nil
}

func executeStep(context context.Context, step step.Step) error {
Log().WithField("step", step.GetName()).Info("Executing step")

if err := step.Apply(context); err != nil {
if step.IsRequired() {
return err
}
Log().WithError(err).WithField("step", step.GetName()).
Info("apply failed but step is not required, continuing")
}

Log().WithField("step", step.GetName()).
Info("successfully applied step")
return nil
}

func (r *Default) PostCheck(_ context.Context) error {
return nil
}
63 changes: 63 additions & 0 deletions internal/nodeprep/instruction/instructions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2024 NetApp, Inc. All Rights Reserved.

package instruction

//go:generate mockgen -destination=../../../mocks/mock_internal/mock_nodeprep/mock_instruction/mock_instruction.go github.com/netapp/trident/internal/nodeprep/instruction Instructions

import (
"context"
"fmt"

"github.com/netapp/trident/internal/nodeprep/nodeinfo"
"github.com/netapp/trident/internal/nodeprep/protocol"
"github.com/netapp/trident/internal/nodeprep/step"
utilserrors "github.com/netapp/trident/utils/errors"
)

// Planning for future distros and protocols which may not align easily with each other and need different instructions
// For example windows might need different instructions for iSCSI than linux or ROSA might be very different from amazon linux
// And some may not need package managers and some will

type Key struct {
Protocol protocol.Protocol
Distro nodeinfo.Distro
PkgMgr nodeinfo.PkgMgr
}

var instructionMap = map[Key]Instructions{}

func init() {
instructionMap[Key{Protocol: protocol.ISCSI, Distro: nodeinfo.DistroAmzn, PkgMgr: nodeinfo.PkgMgrYum}] = newAmznYumISCSI()
instructionMap[Key{Protocol: protocol.ISCSI, Distro: nodeinfo.DistroUbuntu, PkgMgr: nodeinfo.PkgMgrApt}] = newDebianAptISCSI()
}

type Instructions interface {
GetName() string
PreCheck(ctx context.Context) error
Apply(ctx context.Context) error
PostCheck(ctx context.Context) error
GetSteps() []step.Step
}

// ScopedInstructions is used to mock out the default instructions for testing,
// this should not be called anywhere else other than tests
func ScopedInstructions(instructions map[Key]Instructions) func() {
existingInstructions := instructionMap
instructionMap = instructions
return func() {
instructionMap = existingInstructions
}
}

func GetInstructions(nodeInfo *nodeinfo.NodeInfo, protocols []protocol.Protocol) ([]Instructions, error) {
instructions := make([]Instructions, 0)
for _, p := range protocols {
if instruction, ok := instructionMap[Key{Protocol: p, Distro: nodeInfo.Distro, PkgMgr: nodeInfo.PkgMgr}]; ok {
instructions = append(instructions, instruction)
}
}
if len(instructions) == 0 {
return nil, utilserrors.UnsupportedError(fmt.Sprintf("distribution %s for protocols %s is not supported", nodeInfo.Distro, protocols))
}
return instructions, nil
}
37 changes: 37 additions & 0 deletions internal/nodeprep/instruction/iscsi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2024 NetApp, Inc. All Rights Reserved.

package instruction

import (
"github.com/netapp/trident/internal/nodeprep/packagemanager"
"github.com/netapp/trident/internal/nodeprep/packagemanager/apt"
"github.com/netapp/trident/internal/nodeprep/packagemanager/yum"
"github.com/netapp/trident/internal/nodeprep/step"
"github.com/netapp/trident/internal/nodeprep/systemmanager"
"github.com/netapp/trident/internal/nodeprep/systemmanager/amzn"
"github.com/netapp/trident/internal/nodeprep/systemmanager/debian"
)

type ISCSI struct {
Default
}

func newDebianAptISCSI() (instruction Instructions) {
return newISCSI(apt.New(), debian.New())
}

func newAmznYumISCSI() (instruction Instructions) {
return newISCSI(yum.New(), amzn.New())
}

func newISCSI(packageManager packagemanager.PackageManager, systemManager systemmanager.SystemManager) (instruction *ISCSI) {
instruction = &ISCSI{}
instruction.name = "iscsi instructions"
// ordering of steps matter here, multipath must be configured before installing iscsi tools to be idempotent
instruction.steps = []step.Step{
step.NewMultipathConfigureStep(packageManager),
step.NewInstallIscsiTools(packageManager),
step.NewEnableIscsiServices(systemManager),
}
return
}
2 changes: 1 addition & 1 deletion internal/nodeprep/mpathconfig/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

package mpathconfig

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

import (
"bufio"
Expand Down
2 changes: 1 addition & 1 deletion internal/nodeprep/mpathconfig/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

package mpathconfig

//go:generate mockgen -destination=../../../mocks/mock_nodeprep/mock_mpathconfig/mock_section.go github.com/netapp/trident/internal/nodeprep/mpathconfig MpathConfigurationSection
//go:generate mockgen -destination=../../../mocks/mock_internal/mock_nodeprep/mock_mpathconfig/mock_section.go github.com/netapp/trident/internal/nodeprep/mpathconfig MpathConfigurationSection

import (
"fmt"
Expand Down
Loading

0 comments on commit e0e1fac

Please sign in to comment.