Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add nodes validation in farmerbot #1135

Merged
merged 17 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions farmerbot/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ var runCmd = &cobra.Command{
return err
}

if err := parser.ValidateConfig(config, network); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}

config.ContinueOnPoweringOnErr = continueOnPoweringOnErr

farmerBot, err := internal.NewFarmerBot(cmd.Context(), config, network, mnemonicOrSeed, keyType)
Expand Down
86 changes: 86 additions & 0 deletions farmerbot/parser/validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package parser

import (
"fmt"
"slices"

substrate "github.com/threefoldtech/tfchain/clients/tfchain-client-go"
"github.com/threefoldtech/tfgrid-sdk-go/farmerbot/internal"
)

// wrapper for validateInput
func ValidateConfig(input internal.Config, network string) error {
manager := substrate.NewManager(internal.SubstrateURLs[network]...)
subConn, err := manager.Substrate()
if err != nil {
return err
}
defer subConn.Close()
return validateInput(input, subConn)
}

// validateInput validates that included, excluded and priority nodes are in the farm
func validateInput(input internal.Config, sub internal.Substrate) error {
nodes, err := sub.GetNodes(input.FarmID)
if err != nil {
return fmt.Errorf("couldn't retrieve node for %d : %v", input.FarmID, err)
}
farmNodes := make(map[uint32]bool)
for _, node := range nodes {
farmNodes[node] = true
}
includedNodes := make(map[uint32]bool)
for _, includedNode := range input.IncludedNodes {
includedNodes[includedNode] = true
}
if len(includedNodes) == 0 {
for key, value := range farmNodes {
if !slices.Contains(input.ExcludedNodes, key) {
includedNodes[key] = value
}
}
}
if err := validateIncludedNodes(input.IncludedNodes, input.ExcludedNodes, farmNodes); err != nil {
return err
}
if err := validateExcludedNodes(input.ExcludedNodes, farmNodes); err != nil {
return err
}
if err := validatePriorityOrNeverShutdown("priority", input.PriorityNodes, includedNodes); err != nil {
return err
}
if err := validatePriorityOrNeverShutdown("never shutdown", input.NeverShutDownNodes, includedNodes); err != nil {
return err
}
return nil
}

func validateIncludedNodes(included, excluded []uint32, farmNodes map[uint32]bool) error {
for _, node := range included {
if _, ok := farmNodes[node]; !ok {
return fmt.Errorf("included node with id %d doesn't exist in the farm", node)
}
if slices.Contains(excluded, node) {
return fmt.Errorf("cannot include and exclude the same node %d", node)
}
}
return nil
}

func validatePriorityOrNeverShutdown(typeOfValidation string, toBeValidated []uint32, includedNodes map[uint32]bool) error {
for _, node := range toBeValidated {
if _, ok := includedNodes[node]; !ok {
return fmt.Errorf("%s node with id %d doesn't exist in the included nodes ", typeOfValidation, node)
}
}
return nil
}

func validateExcludedNodes(excluded []uint32, farmNodes map[uint32]bool) error {
for _, node := range excluded {
if _, ok := farmNodes[node]; !ok {
return fmt.Errorf("excluded node with id %d doesn't exist in the farm", node)
}
}
return nil
}
112 changes: 112 additions & 0 deletions farmerbot/parser/validator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package parser

import (
"testing"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/threefoldtech/tfgrid-sdk-go/farmerbot/internal"
"github.com/threefoldtech/tfgrid-sdk-go/farmerbot/mocks"
)

var testCases = []struct {
name string
includedNodes []uint32
priorityNodes []uint32
excludedNodes []uint32
neverShutdownNodes []uint32
shouldFail bool
}{
{
name: "test include, exclude, priority nodes and never shutdown nodes found in farm nodes",
includedNodes: []uint32{20, 21, 22, 30, 31, 32, 40, 41},
priorityNodes: []uint32{20, 21},
excludedNodes: []uint32{23, 24, 34},
neverShutdownNodes: []uint32{22, 30},
shouldFail: false,
},
{
name: "test included node doesn't exist in farm nodes",
includedNodes: []uint32{26, 27},
shouldFail: true,
},
{
name: "test priority node doesn't exist in included nodes",
includedNodes: []uint32{21},
priorityNodes: []uint32{20, 21},
shouldFail: true,
},
{
name: "test never shutdown node doesn't exist in included nodes",
includedNodes: []uint32{21},
neverShutdownNodes: []uint32{20, 21},
shouldFail: true,
}, {
name: "test overlapping nodes in included and excluded nodes",
includedNodes: []uint32{21},
excludedNodes: []uint32{20, 21},
shouldFail: true,
}, {
name: "test overlapping nodes in priority and excluded nodes",
includedNodes: []uint32{21},
priorityNodes: []uint32{21},
excludedNodes: []uint32{20, 21},
shouldFail: true,
}, {
name: "test overlapping nodes in never shutdown and excluded nodes",
includedNodes: []uint32{21},
excludedNodes: []uint32{20, 21},
neverShutdownNodes: []uint32{21},
shouldFail: true,
}, {
name: "test excluded node doesn't exist in farm nodes",
excludedNodes: []uint32{26, 27},
shouldFail: true,
}, {
name: "test all nodes included and other nodes exist in included nodes",
priorityNodes: []uint32{21},
excludedNodes: []uint32{22},
neverShutdownNodes: []uint32{20},
shouldFail: false,
}, {
name: "test all nodes included and priority nodes doesn't exist in included nodes",
priorityNodes: []uint32{27, 26},
shouldFail: true,
}, {
name: "test all nodes included and shutdown nodes doesn't exist in included nodes",
neverShutdownNodes: []uint32{27, 26},
shouldFail: true,
}, {
name: "test all nodes included and overlapping node between shutdown and excluded nodes",
neverShutdownNodes: []uint32{21, 20},
excludedNodes: []uint32{21, 20},
shouldFail: true,
}, {
name: "test all nodes included and overlapping node between priority and excluded nodes",
excludedNodes: []uint32{21, 20},
priorityNodes: []uint32{21, 20},
shouldFail: true,
},
}

func TestValidateInput(t *testing.T) {
config := internal.Config{FarmID: uint32(25)}
ctrl := gomock.NewController(t)
mockGetNodes := mocks.NewMockSubstrate(ctrl)
mockGetNodes.EXPECT().GetNodes(config.FarmID).Times(13).Return([]uint32{20, 21, 22, 23, 24, 30, 31, 32, 34, 40, 41}, nil)
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
config.IncludedNodes = test.includedNodes
config.ExcludedNodes = test.excludedNodes
config.PriorityNodes = test.priorityNodes
config.NeverShutDownNodes = test.neverShutdownNodes
got := validateInput(config, mockGetNodes)
if test.shouldFail {
assert.Error(t, got)
} else {
assert.NoError(t, got)
}
})
}

}
Loading