From fea8a7cdccf7c5690ca8cec0bae48e6d78797704 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 11:03:34 +0200 Subject: [PATCH 01/20] device/interface: split DeviceQueryor into interfaces for inband and oob queries --- .mockery.yaml | 3 +- internal/device/interface.go | 12 +- internal/device/mock_inbandQueryor.go | 211 +++++++ internal/device/mock_outofbandQueryor.go | 673 +++++++++++++++++++++ internal/device/mock_queryor.go | 673 --------------------- internal/install/task_handler.go | 9 +- internal/outofband/action_handlers.go | 2 +- internal/outofband/action_handlers_test.go | 8 +- internal/outofband/actions.go | 6 +- internal/outofband/actions_test.go | 16 +- internal/outofband/bmc.go | 2 +- internal/outofband/graph.go | 2 +- internal/runner/runner.go | 4 +- internal/worker/task_handler.go | 4 +- 14 files changed, 924 insertions(+), 701 deletions(-) create mode 100644 internal/device/mock_inbandQueryor.go create mode 100644 internal/device/mock_outofbandQueryor.go delete mode 100644 internal/device/mock_queryor.go diff --git a/.mockery.yaml b/.mockery.yaml index 7ada9b04..aa336e77 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -15,4 +15,5 @@ packages: fileName: "mock_{{.InterfaceName | firstLower}}.go" inpackage: true interfaces: - Queryor: + OutofbandQueryor: + InbandQueryor: diff --git a/internal/device/interface.go b/internal/device/interface.go index d7c24cd1..f54f308a 100644 --- a/internal/device/interface.go +++ b/internal/device/interface.go @@ -7,14 +7,15 @@ import ( "github.com/bmc-toolbox/common" bconsts "github.com/bmc-toolbox/bmclib/v2/constants" + ironlibm "github.com/metal-toolbox/ironlib/model" ) //go:generate mockgen -source model.go -destination=../fixtures/mock.go -package=fixtures -// Queryor interface defines methods to query a device. +// QueryorOutofband interface defines the out-of-band methods to query a device. // // This is common interface to the ironlib and bmclib libraries. -type Queryor interface { +type OutofbandQueryor interface { // Open opens the connection to the device. Open(ctx context.Context) error @@ -43,3 +44,10 @@ type Queryor interface { FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) } + +type InbandQueryor interface { + // Inventory returns the device inventory + Inventory(ctx context.Context) (*common.Device, error) + FirmwareInstall(ctx context.Context, component, vendor string, model, version, updateFile string, force bool) error + FirmwareInstallRequirements(ctx context.Context, component, vendor, model string) (*ironlibm.UpdateRequirements, error) +} diff --git a/internal/device/mock_inbandQueryor.go b/internal/device/mock_inbandQueryor.go new file mode 100644 index 00000000..002e9d9c --- /dev/null +++ b/internal/device/mock_inbandQueryor.go @@ -0,0 +1,211 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + +package device + +import ( + context "context" + + common "github.com/bmc-toolbox/common" + + mock "github.com/stretchr/testify/mock" + + model "github.com/metal-toolbox/ironlib/model" +) + +// MockInbandQueryor is an autogenerated mock type for the InbandQueryor type +type MockInbandQueryor struct { + mock.Mock +} + +type MockInbandQueryor_Expecter struct { + mock *mock.Mock +} + +func (_m *MockInbandQueryor) EXPECT() *MockInbandQueryor_Expecter { + return &MockInbandQueryor_Expecter{mock: &_m.Mock} +} + +// FirmwareInstall provides a mock function with given fields: ctx, component, vendor, _a3, version, updateFile, force +func (_m *MockInbandQueryor) FirmwareInstall(ctx context.Context, component string, vendor string, _a3 string, version string, updateFile string, force bool) error { + ret := _m.Called(ctx, component, vendor, _a3, version, updateFile, force) + + if len(ret) == 0 { + panic("no return value specified for FirmwareInstall") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, string, string, bool) error); ok { + r0 = rf(ctx, component, vendor, _a3, version, updateFile, force) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockInbandQueryor_FirmwareInstall_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareInstall' +type MockInbandQueryor_FirmwareInstall_Call struct { + *mock.Call +} + +// FirmwareInstall is a helper method to define mock.On call +// - ctx context.Context +// - component string +// - vendor string +// - _a3 string +// - version string +// - updateFile string +// - force bool +func (_e *MockInbandQueryor_Expecter) FirmwareInstall(ctx interface{}, component interface{}, vendor interface{}, _a3 interface{}, version interface{}, updateFile interface{}, force interface{}) *MockInbandQueryor_FirmwareInstall_Call { + return &MockInbandQueryor_FirmwareInstall_Call{Call: _e.mock.On("FirmwareInstall", ctx, component, vendor, _a3, version, updateFile, force)} +} + +func (_c *MockInbandQueryor_FirmwareInstall_Call) Run(run func(ctx context.Context, component string, vendor string, _a3 string, version string, updateFile string, force bool)) *MockInbandQueryor_FirmwareInstall_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string), args[4].(string), args[5].(string), args[6].(bool)) + }) + return _c +} + +func (_c *MockInbandQueryor_FirmwareInstall_Call) Return(_a0 error) *MockInbandQueryor_FirmwareInstall_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockInbandQueryor_FirmwareInstall_Call) RunAndReturn(run func(context.Context, string, string, string, string, string, bool) error) *MockInbandQueryor_FirmwareInstall_Call { + _c.Call.Return(run) + return _c +} + +// FirmwareInstallRequirements provides a mock function with given fields: ctx, component, vendor, _a3 +func (_m *MockInbandQueryor) FirmwareInstallRequirements(ctx context.Context, component string, vendor string, _a3 string) (*model.UpdateRequirements, error) { + ret := _m.Called(ctx, component, vendor, _a3) + + if len(ret) == 0 { + panic("no return value specified for FirmwareInstallRequirements") + } + + var r0 *model.UpdateRequirements + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) (*model.UpdateRequirements, error)); ok { + return rf(ctx, component, vendor, _a3) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) *model.UpdateRequirements); ok { + r0 = rf(ctx, component, vendor, _a3) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.UpdateRequirements) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok { + r1 = rf(ctx, component, vendor, _a3) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockInbandQueryor_FirmwareInstallRequirements_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareInstallRequirements' +type MockInbandQueryor_FirmwareInstallRequirements_Call struct { + *mock.Call +} + +// FirmwareInstallRequirements is a helper method to define mock.On call +// - ctx context.Context +// - component string +// - vendor string +// - _a3 string +func (_e *MockInbandQueryor_Expecter) FirmwareInstallRequirements(ctx interface{}, component interface{}, vendor interface{}, _a3 interface{}) *MockInbandQueryor_FirmwareInstallRequirements_Call { + return &MockInbandQueryor_FirmwareInstallRequirements_Call{Call: _e.mock.On("FirmwareInstallRequirements", ctx, component, vendor, _a3)} +} + +func (_c *MockInbandQueryor_FirmwareInstallRequirements_Call) Run(run func(ctx context.Context, component string, vendor string, _a3 string)) *MockInbandQueryor_FirmwareInstallRequirements_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(string)) + }) + return _c +} + +func (_c *MockInbandQueryor_FirmwareInstallRequirements_Call) Return(_a0 *model.UpdateRequirements, _a1 error) *MockInbandQueryor_FirmwareInstallRequirements_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockInbandQueryor_FirmwareInstallRequirements_Call) RunAndReturn(run func(context.Context, string, string, string) (*model.UpdateRequirements, error)) *MockInbandQueryor_FirmwareInstallRequirements_Call { + _c.Call.Return(run) + return _c +} + +// Inventory provides a mock function with given fields: ctx +func (_m *MockInbandQueryor) Inventory(ctx context.Context) (*common.Device, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Inventory") + } + + var r0 *common.Device + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*common.Device, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *common.Device); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*common.Device) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockInbandQueryor_Inventory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Inventory' +type MockInbandQueryor_Inventory_Call struct { + *mock.Call +} + +// Inventory is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockInbandQueryor_Expecter) Inventory(ctx interface{}) *MockInbandQueryor_Inventory_Call { + return &MockInbandQueryor_Inventory_Call{Call: _e.mock.On("Inventory", ctx)} +} + +func (_c *MockInbandQueryor_Inventory_Call) Run(run func(ctx context.Context)) *MockInbandQueryor_Inventory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockInbandQueryor_Inventory_Call) Return(_a0 *common.Device, _a1 error) *MockInbandQueryor_Inventory_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockInbandQueryor_Inventory_Call) RunAndReturn(run func(context.Context) (*common.Device, error)) *MockInbandQueryor_Inventory_Call { + _c.Call.Return(run) + return _c +} + +// NewMockInbandQueryor creates a new instance of MockInbandQueryor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockInbandQueryor(t interface { + mock.TestingT + Cleanup(func()) +}) *MockInbandQueryor { + mock := &MockInbandQueryor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/device/mock_outofbandQueryor.go b/internal/device/mock_outofbandQueryor.go new file mode 100644 index 00000000..00c1f713 --- /dev/null +++ b/internal/device/mock_outofbandQueryor.go @@ -0,0 +1,673 @@ +// Code generated by mockery v2.42.1. DO NOT EDIT. + +package device + +import ( + constants "github.com/bmc-toolbox/bmclib/v2/constants" + common "github.com/bmc-toolbox/common" + + context "context" + + mock "github.com/stretchr/testify/mock" + + os "os" +) + +// MockOutofbandQueryor is an autogenerated mock type for the OutofbandQueryor type +type MockOutofbandQueryor struct { + mock.Mock +} + +type MockOutofbandQueryor_Expecter struct { + mock *mock.Mock +} + +func (_m *MockOutofbandQueryor) EXPECT() *MockOutofbandQueryor_Expecter { + return &MockOutofbandQueryor_Expecter{mock: &_m.Mock} +} + +// Close provides a mock function with given fields: ctx +func (_m *MockOutofbandQueryor) Close(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Close") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockOutofbandQueryor_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type MockOutofbandQueryor_Close_Call struct { + *mock.Call +} + +// Close is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockOutofbandQueryor_Expecter) Close(ctx interface{}) *MockOutofbandQueryor_Close_Call { + return &MockOutofbandQueryor_Close_Call{Call: _e.mock.On("Close", ctx)} +} + +func (_c *MockOutofbandQueryor_Close_Call) Run(run func(ctx context.Context)) *MockOutofbandQueryor_Close_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_Close_Call) Return(_a0 error) *MockOutofbandQueryor_Close_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockOutofbandQueryor_Close_Call) RunAndReturn(run func(context.Context) error) *MockOutofbandQueryor_Close_Call { + _c.Call.Return(run) + return _c +} + +// FirmwareInstallSteps provides a mock function with given fields: ctx, component +func (_m *MockOutofbandQueryor) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) { + ret := _m.Called(ctx, component) + + if len(ret) == 0 { + panic("no return value specified for FirmwareInstallSteps") + } + + var r0 []constants.FirmwareInstallStep + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]constants.FirmwareInstallStep, error)); ok { + return rf(ctx, component) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []constants.FirmwareInstallStep); ok { + r0 = rf(ctx, component) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]constants.FirmwareInstallStep) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, component) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockOutofbandQueryor_FirmwareInstallSteps_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareInstallSteps' +type MockOutofbandQueryor_FirmwareInstallSteps_Call struct { + *mock.Call +} + +// FirmwareInstallSteps is a helper method to define mock.On call +// - ctx context.Context +// - component string +func (_e *MockOutofbandQueryor_Expecter) FirmwareInstallSteps(ctx interface{}, component interface{}) *MockOutofbandQueryor_FirmwareInstallSteps_Call { + return &MockOutofbandQueryor_FirmwareInstallSteps_Call{Call: _e.mock.On("FirmwareInstallSteps", ctx, component)} +} + +func (_c *MockOutofbandQueryor_FirmwareInstallSteps_Call) Run(run func(ctx context.Context, component string)) *MockOutofbandQueryor_FirmwareInstallSteps_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareInstallSteps_Call) Return(_a0 []constants.FirmwareInstallStep, _a1 error) *MockOutofbandQueryor_FirmwareInstallSteps_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareInstallSteps_Call) RunAndReturn(run func(context.Context, string) ([]constants.FirmwareInstallStep, error)) *MockOutofbandQueryor_FirmwareInstallSteps_Call { + _c.Call.Return(run) + return _c +} + +// FirmwareInstallUploadAndInitiate provides a mock function with given fields: ctx, component, file +func (_m *MockOutofbandQueryor) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (string, error) { + ret := _m.Called(ctx, component, file) + + if len(ret) == 0 { + panic("no return value specified for FirmwareInstallUploadAndInitiate") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *os.File) (string, error)); ok { + return rf(ctx, component, file) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *os.File) string); ok { + r0 = rf(ctx, component, file) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *os.File) error); ok { + r1 = rf(ctx, component, file) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareInstallUploadAndInitiate' +type MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call struct { + *mock.Call +} + +// FirmwareInstallUploadAndInitiate is a helper method to define mock.On call +// - ctx context.Context +// - component string +// - file *os.File +func (_e *MockOutofbandQueryor_Expecter) FirmwareInstallUploadAndInitiate(ctx interface{}, component interface{}, file interface{}) *MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call { + return &MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call{Call: _e.mock.On("FirmwareInstallUploadAndInitiate", ctx, component, file)} +} + +func (_c *MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call) Run(run func(ctx context.Context, component string, file *os.File)) *MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*os.File)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call) Return(taskID string, err error) *MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call { + _c.Call.Return(taskID, err) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call) RunAndReturn(run func(context.Context, string, *os.File) (string, error)) *MockOutofbandQueryor_FirmwareInstallUploadAndInitiate_Call { + _c.Call.Return(run) + return _c +} + +// FirmwareInstallUploaded provides a mock function with given fields: ctx, component, uploadVerifyTaskID +func (_m *MockOutofbandQueryor) FirmwareInstallUploaded(ctx context.Context, component string, uploadVerifyTaskID string) (string, error) { + ret := _m.Called(ctx, component, uploadVerifyTaskID) + + if len(ret) == 0 { + panic("no return value specified for FirmwareInstallUploaded") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) (string, error)); ok { + return rf(ctx, component, uploadVerifyTaskID) + } + if rf, ok := ret.Get(0).(func(context.Context, string, string) string); ok { + r0 = rf(ctx, component, uploadVerifyTaskID) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { + r1 = rf(ctx, component, uploadVerifyTaskID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockOutofbandQueryor_FirmwareInstallUploaded_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareInstallUploaded' +type MockOutofbandQueryor_FirmwareInstallUploaded_Call struct { + *mock.Call +} + +// FirmwareInstallUploaded is a helper method to define mock.On call +// - ctx context.Context +// - component string +// - uploadVerifyTaskID string +func (_e *MockOutofbandQueryor_Expecter) FirmwareInstallUploaded(ctx interface{}, component interface{}, uploadVerifyTaskID interface{}) *MockOutofbandQueryor_FirmwareInstallUploaded_Call { + return &MockOutofbandQueryor_FirmwareInstallUploaded_Call{Call: _e.mock.On("FirmwareInstallUploaded", ctx, component, uploadVerifyTaskID)} +} + +func (_c *MockOutofbandQueryor_FirmwareInstallUploaded_Call) Run(run func(ctx context.Context, component string, uploadVerifyTaskID string)) *MockOutofbandQueryor_FirmwareInstallUploaded_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareInstallUploaded_Call) Return(installTaskID string, err error) *MockOutofbandQueryor_FirmwareInstallUploaded_Call { + _c.Call.Return(installTaskID, err) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareInstallUploaded_Call) RunAndReturn(run func(context.Context, string, string) (string, error)) *MockOutofbandQueryor_FirmwareInstallUploaded_Call { + _c.Call.Return(run) + return _c +} + +// FirmwareTaskStatus provides a mock function with given fields: ctx, kind, component, taskID, installVersion +func (_m *MockOutofbandQueryor) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component string, taskID string, installVersion string) (constants.TaskState, string, error) { + ret := _m.Called(ctx, kind, component, taskID, installVersion) + + if len(ret) == 0 { + panic("no return value specified for FirmwareTaskStatus") + } + + var r0 constants.TaskState + var r1 string + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, constants.FirmwareInstallStep, string, string, string) (constants.TaskState, string, error)); ok { + return rf(ctx, kind, component, taskID, installVersion) + } + if rf, ok := ret.Get(0).(func(context.Context, constants.FirmwareInstallStep, string, string, string) constants.TaskState); ok { + r0 = rf(ctx, kind, component, taskID, installVersion) + } else { + r0 = ret.Get(0).(constants.TaskState) + } + + if rf, ok := ret.Get(1).(func(context.Context, constants.FirmwareInstallStep, string, string, string) string); ok { + r1 = rf(ctx, kind, component, taskID, installVersion) + } else { + r1 = ret.Get(1).(string) + } + + if rf, ok := ret.Get(2).(func(context.Context, constants.FirmwareInstallStep, string, string, string) error); ok { + r2 = rf(ctx, kind, component, taskID, installVersion) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// MockOutofbandQueryor_FirmwareTaskStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareTaskStatus' +type MockOutofbandQueryor_FirmwareTaskStatus_Call struct { + *mock.Call +} + +// FirmwareTaskStatus is a helper method to define mock.On call +// - ctx context.Context +// - kind constants.FirmwareInstallStep +// - component string +// - taskID string +// - installVersion string +func (_e *MockOutofbandQueryor_Expecter) FirmwareTaskStatus(ctx interface{}, kind interface{}, component interface{}, taskID interface{}, installVersion interface{}) *MockOutofbandQueryor_FirmwareTaskStatus_Call { + return &MockOutofbandQueryor_FirmwareTaskStatus_Call{Call: _e.mock.On("FirmwareTaskStatus", ctx, kind, component, taskID, installVersion)} +} + +func (_c *MockOutofbandQueryor_FirmwareTaskStatus_Call) Run(run func(ctx context.Context, kind constants.FirmwareInstallStep, component string, taskID string, installVersion string)) *MockOutofbandQueryor_FirmwareTaskStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(constants.FirmwareInstallStep), args[2].(string), args[3].(string), args[4].(string)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareTaskStatus_Call) Return(state constants.TaskState, status string, err error) *MockOutofbandQueryor_FirmwareTaskStatus_Call { + _c.Call.Return(state, status, err) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareTaskStatus_Call) RunAndReturn(run func(context.Context, constants.FirmwareInstallStep, string, string, string) (constants.TaskState, string, error)) *MockOutofbandQueryor_FirmwareTaskStatus_Call { + _c.Call.Return(run) + return _c +} + +// FirmwareUpload provides a mock function with given fields: ctx, component, reader +func (_m *MockOutofbandQueryor) FirmwareUpload(ctx context.Context, component string, reader *os.File) (string, error) { + ret := _m.Called(ctx, component, reader) + + if len(ret) == 0 { + panic("no return value specified for FirmwareUpload") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *os.File) (string, error)); ok { + return rf(ctx, component, reader) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *os.File) string); ok { + r0 = rf(ctx, component, reader) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *os.File) error); ok { + r1 = rf(ctx, component, reader) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockOutofbandQueryor_FirmwareUpload_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareUpload' +type MockOutofbandQueryor_FirmwareUpload_Call struct { + *mock.Call +} + +// FirmwareUpload is a helper method to define mock.On call +// - ctx context.Context +// - component string +// - reader *os.File +func (_e *MockOutofbandQueryor_Expecter) FirmwareUpload(ctx interface{}, component interface{}, reader interface{}) *MockOutofbandQueryor_FirmwareUpload_Call { + return &MockOutofbandQueryor_FirmwareUpload_Call{Call: _e.mock.On("FirmwareUpload", ctx, component, reader)} +} + +func (_c *MockOutofbandQueryor_FirmwareUpload_Call) Run(run func(ctx context.Context, component string, reader *os.File)) *MockOutofbandQueryor_FirmwareUpload_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*os.File)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareUpload_Call) Return(uploadVerifyTaskID string, err error) *MockOutofbandQueryor_FirmwareUpload_Call { + _c.Call.Return(uploadVerifyTaskID, err) + return _c +} + +func (_c *MockOutofbandQueryor_FirmwareUpload_Call) RunAndReturn(run func(context.Context, string, *os.File) (string, error)) *MockOutofbandQueryor_FirmwareUpload_Call { + _c.Call.Return(run) + return _c +} + +// Inventory provides a mock function with given fields: ctx +func (_m *MockOutofbandQueryor) Inventory(ctx context.Context) (*common.Device, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Inventory") + } + + var r0 *common.Device + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (*common.Device, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) *common.Device); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*common.Device) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockOutofbandQueryor_Inventory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Inventory' +type MockOutofbandQueryor_Inventory_Call struct { + *mock.Call +} + +// Inventory is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockOutofbandQueryor_Expecter) Inventory(ctx interface{}) *MockOutofbandQueryor_Inventory_Call { + return &MockOutofbandQueryor_Inventory_Call{Call: _e.mock.On("Inventory", ctx)} +} + +func (_c *MockOutofbandQueryor_Inventory_Call) Run(run func(ctx context.Context)) *MockOutofbandQueryor_Inventory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_Inventory_Call) Return(_a0 *common.Device, _a1 error) *MockOutofbandQueryor_Inventory_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockOutofbandQueryor_Inventory_Call) RunAndReturn(run func(context.Context) (*common.Device, error)) *MockOutofbandQueryor_Inventory_Call { + _c.Call.Return(run) + return _c +} + +// Open provides a mock function with given fields: ctx +func (_m *MockOutofbandQueryor) Open(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for Open") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockOutofbandQueryor_Open_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Open' +type MockOutofbandQueryor_Open_Call struct { + *mock.Call +} + +// Open is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockOutofbandQueryor_Expecter) Open(ctx interface{}) *MockOutofbandQueryor_Open_Call { + return &MockOutofbandQueryor_Open_Call{Call: _e.mock.On("Open", ctx)} +} + +func (_c *MockOutofbandQueryor_Open_Call) Run(run func(ctx context.Context)) *MockOutofbandQueryor_Open_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_Open_Call) Return(_a0 error) *MockOutofbandQueryor_Open_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockOutofbandQueryor_Open_Call) RunAndReturn(run func(context.Context) error) *MockOutofbandQueryor_Open_Call { + _c.Call.Return(run) + return _c +} + +// PowerStatus provides a mock function with given fields: ctx +func (_m *MockOutofbandQueryor) PowerStatus(ctx context.Context) (string, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for PowerStatus") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) string); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockOutofbandQueryor_PowerStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PowerStatus' +type MockOutofbandQueryor_PowerStatus_Call struct { + *mock.Call +} + +// PowerStatus is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockOutofbandQueryor_Expecter) PowerStatus(ctx interface{}) *MockOutofbandQueryor_PowerStatus_Call { + return &MockOutofbandQueryor_PowerStatus_Call{Call: _e.mock.On("PowerStatus", ctx)} +} + +func (_c *MockOutofbandQueryor_PowerStatus_Call) Run(run func(ctx context.Context)) *MockOutofbandQueryor_PowerStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_PowerStatus_Call) Return(status string, err error) *MockOutofbandQueryor_PowerStatus_Call { + _c.Call.Return(status, err) + return _c +} + +func (_c *MockOutofbandQueryor_PowerStatus_Call) RunAndReturn(run func(context.Context) (string, error)) *MockOutofbandQueryor_PowerStatus_Call { + _c.Call.Return(run) + return _c +} + +// ReinitializeClient provides a mock function with given fields: ctx +func (_m *MockOutofbandQueryor) ReinitializeClient(ctx context.Context) { + _m.Called(ctx) +} + +// MockOutofbandQueryor_ReinitializeClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReinitializeClient' +type MockOutofbandQueryor_ReinitializeClient_Call struct { + *mock.Call +} + +// ReinitializeClient is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockOutofbandQueryor_Expecter) ReinitializeClient(ctx interface{}) *MockOutofbandQueryor_ReinitializeClient_Call { + return &MockOutofbandQueryor_ReinitializeClient_Call{Call: _e.mock.On("ReinitializeClient", ctx)} +} + +func (_c *MockOutofbandQueryor_ReinitializeClient_Call) Run(run func(ctx context.Context)) *MockOutofbandQueryor_ReinitializeClient_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_ReinitializeClient_Call) Return() *MockOutofbandQueryor_ReinitializeClient_Call { + _c.Call.Return() + return _c +} + +func (_c *MockOutofbandQueryor_ReinitializeClient_Call) RunAndReturn(run func(context.Context)) *MockOutofbandQueryor_ReinitializeClient_Call { + _c.Call.Return(run) + return _c +} + +// ResetBMC provides a mock function with given fields: ctx +func (_m *MockOutofbandQueryor) ResetBMC(ctx context.Context) error { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for ResetBMC") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockOutofbandQueryor_ResetBMC_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResetBMC' +type MockOutofbandQueryor_ResetBMC_Call struct { + *mock.Call +} + +// ResetBMC is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockOutofbandQueryor_Expecter) ResetBMC(ctx interface{}) *MockOutofbandQueryor_ResetBMC_Call { + return &MockOutofbandQueryor_ResetBMC_Call{Call: _e.mock.On("ResetBMC", ctx)} +} + +func (_c *MockOutofbandQueryor_ResetBMC_Call) Run(run func(ctx context.Context)) *MockOutofbandQueryor_ResetBMC_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_ResetBMC_Call) Return(_a0 error) *MockOutofbandQueryor_ResetBMC_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockOutofbandQueryor_ResetBMC_Call) RunAndReturn(run func(context.Context) error) *MockOutofbandQueryor_ResetBMC_Call { + _c.Call.Return(run) + return _c +} + +// SetPowerState provides a mock function with given fields: ctx, state +func (_m *MockOutofbandQueryor) SetPowerState(ctx context.Context, state string) error { + ret := _m.Called(ctx, state) + + if len(ret) == 0 { + panic("no return value specified for SetPowerState") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, state) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockOutofbandQueryor_SetPowerState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPowerState' +type MockOutofbandQueryor_SetPowerState_Call struct { + *mock.Call +} + +// SetPowerState is a helper method to define mock.On call +// - ctx context.Context +// - state string +func (_e *MockOutofbandQueryor_Expecter) SetPowerState(ctx interface{}, state interface{}) *MockOutofbandQueryor_SetPowerState_Call { + return &MockOutofbandQueryor_SetPowerState_Call{Call: _e.mock.On("SetPowerState", ctx, state)} +} + +func (_c *MockOutofbandQueryor_SetPowerState_Call) Run(run func(ctx context.Context, state string)) *MockOutofbandQueryor_SetPowerState_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockOutofbandQueryor_SetPowerState_Call) Return(_a0 error) *MockOutofbandQueryor_SetPowerState_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockOutofbandQueryor_SetPowerState_Call) RunAndReturn(run func(context.Context, string) error) *MockOutofbandQueryor_SetPowerState_Call { + _c.Call.Return(run) + return _c +} + +// NewMockOutofbandQueryor creates a new instance of MockOutofbandQueryor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockOutofbandQueryor(t interface { + mock.TestingT + Cleanup(func()) +}) *MockOutofbandQueryor { + mock := &MockOutofbandQueryor{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/device/mock_queryor.go b/internal/device/mock_queryor.go deleted file mode 100644 index 24529ddd..00000000 --- a/internal/device/mock_queryor.go +++ /dev/null @@ -1,673 +0,0 @@ -// Code generated by mockery v2.42.1. DO NOT EDIT. - -package device - -import ( - constants "github.com/bmc-toolbox/bmclib/v2/constants" - common "github.com/bmc-toolbox/common" - - context "context" - - mock "github.com/stretchr/testify/mock" - - os "os" -) - -// MockQueryor is an autogenerated mock type for the Queryor type -type MockQueryor struct { - mock.Mock -} - -type MockQueryor_Expecter struct { - mock *mock.Mock -} - -func (_m *MockQueryor) EXPECT() *MockQueryor_Expecter { - return &MockQueryor_Expecter{mock: &_m.Mock} -} - -// Close provides a mock function with given fields: ctx -func (_m *MockQueryor) Close(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Close") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockQueryor_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' -type MockQueryor_Close_Call struct { - *mock.Call -} - -// Close is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockQueryor_Expecter) Close(ctx interface{}) *MockQueryor_Close_Call { - return &MockQueryor_Close_Call{Call: _e.mock.On("Close", ctx)} -} - -func (_c *MockQueryor_Close_Call) Run(run func(ctx context.Context)) *MockQueryor_Close_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockQueryor_Close_Call) Return(_a0 error) *MockQueryor_Close_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockQueryor_Close_Call) RunAndReturn(run func(context.Context) error) *MockQueryor_Close_Call { - _c.Call.Return(run) - return _c -} - -// FirmwareInstallSteps provides a mock function with given fields: ctx, component -func (_m *MockQueryor) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) { - ret := _m.Called(ctx, component) - - if len(ret) == 0 { - panic("no return value specified for FirmwareInstallSteps") - } - - var r0 []constants.FirmwareInstallStep - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string) ([]constants.FirmwareInstallStep, error)); ok { - return rf(ctx, component) - } - if rf, ok := ret.Get(0).(func(context.Context, string) []constants.FirmwareInstallStep); ok { - r0 = rf(ctx, component) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]constants.FirmwareInstallStep) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { - r1 = rf(ctx, component) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockQueryor_FirmwareInstallSteps_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareInstallSteps' -type MockQueryor_FirmwareInstallSteps_Call struct { - *mock.Call -} - -// FirmwareInstallSteps is a helper method to define mock.On call -// - ctx context.Context -// - component string -func (_e *MockQueryor_Expecter) FirmwareInstallSteps(ctx interface{}, component interface{}) *MockQueryor_FirmwareInstallSteps_Call { - return &MockQueryor_FirmwareInstallSteps_Call{Call: _e.mock.On("FirmwareInstallSteps", ctx, component)} -} - -func (_c *MockQueryor_FirmwareInstallSteps_Call) Run(run func(ctx context.Context, component string)) *MockQueryor_FirmwareInstallSteps_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) - }) - return _c -} - -func (_c *MockQueryor_FirmwareInstallSteps_Call) Return(_a0 []constants.FirmwareInstallStep, _a1 error) *MockQueryor_FirmwareInstallSteps_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockQueryor_FirmwareInstallSteps_Call) RunAndReturn(run func(context.Context, string) ([]constants.FirmwareInstallStep, error)) *MockQueryor_FirmwareInstallSteps_Call { - _c.Call.Return(run) - return _c -} - -// FirmwareInstallUploadAndInitiate provides a mock function with given fields: ctx, component, file -func (_m *MockQueryor) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (string, error) { - ret := _m.Called(ctx, component, file) - - if len(ret) == 0 { - panic("no return value specified for FirmwareInstallUploadAndInitiate") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, *os.File) (string, error)); ok { - return rf(ctx, component, file) - } - if rf, ok := ret.Get(0).(func(context.Context, string, *os.File) string); ok { - r0 = rf(ctx, component, file) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, *os.File) error); ok { - r1 = rf(ctx, component, file) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockQueryor_FirmwareInstallUploadAndInitiate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareInstallUploadAndInitiate' -type MockQueryor_FirmwareInstallUploadAndInitiate_Call struct { - *mock.Call -} - -// FirmwareInstallUploadAndInitiate is a helper method to define mock.On call -// - ctx context.Context -// - component string -// - file *os.File -func (_e *MockQueryor_Expecter) FirmwareInstallUploadAndInitiate(ctx interface{}, component interface{}, file interface{}) *MockQueryor_FirmwareInstallUploadAndInitiate_Call { - return &MockQueryor_FirmwareInstallUploadAndInitiate_Call{Call: _e.mock.On("FirmwareInstallUploadAndInitiate", ctx, component, file)} -} - -func (_c *MockQueryor_FirmwareInstallUploadAndInitiate_Call) Run(run func(ctx context.Context, component string, file *os.File)) *MockQueryor_FirmwareInstallUploadAndInitiate_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(*os.File)) - }) - return _c -} - -func (_c *MockQueryor_FirmwareInstallUploadAndInitiate_Call) Return(taskID string, err error) *MockQueryor_FirmwareInstallUploadAndInitiate_Call { - _c.Call.Return(taskID, err) - return _c -} - -func (_c *MockQueryor_FirmwareInstallUploadAndInitiate_Call) RunAndReturn(run func(context.Context, string, *os.File) (string, error)) *MockQueryor_FirmwareInstallUploadAndInitiate_Call { - _c.Call.Return(run) - return _c -} - -// FirmwareInstallUploaded provides a mock function with given fields: ctx, component, uploadVerifyTaskID -func (_m *MockQueryor) FirmwareInstallUploaded(ctx context.Context, component string, uploadVerifyTaskID string) (string, error) { - ret := _m.Called(ctx, component, uploadVerifyTaskID) - - if len(ret) == 0 { - panic("no return value specified for FirmwareInstallUploaded") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string) (string, error)); ok { - return rf(ctx, component, uploadVerifyTaskID) - } - if rf, ok := ret.Get(0).(func(context.Context, string, string) string); ok { - r0 = rf(ctx, component, uploadVerifyTaskID) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok { - r1 = rf(ctx, component, uploadVerifyTaskID) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockQueryor_FirmwareInstallUploaded_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareInstallUploaded' -type MockQueryor_FirmwareInstallUploaded_Call struct { - *mock.Call -} - -// FirmwareInstallUploaded is a helper method to define mock.On call -// - ctx context.Context -// - component string -// - uploadVerifyTaskID string -func (_e *MockQueryor_Expecter) FirmwareInstallUploaded(ctx interface{}, component interface{}, uploadVerifyTaskID interface{}) *MockQueryor_FirmwareInstallUploaded_Call { - return &MockQueryor_FirmwareInstallUploaded_Call{Call: _e.mock.On("FirmwareInstallUploaded", ctx, component, uploadVerifyTaskID)} -} - -func (_c *MockQueryor_FirmwareInstallUploaded_Call) Run(run func(ctx context.Context, component string, uploadVerifyTaskID string)) *MockQueryor_FirmwareInstallUploaded_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(string)) - }) - return _c -} - -func (_c *MockQueryor_FirmwareInstallUploaded_Call) Return(installTaskID string, err error) *MockQueryor_FirmwareInstallUploaded_Call { - _c.Call.Return(installTaskID, err) - return _c -} - -func (_c *MockQueryor_FirmwareInstallUploaded_Call) RunAndReturn(run func(context.Context, string, string) (string, error)) *MockQueryor_FirmwareInstallUploaded_Call { - _c.Call.Return(run) - return _c -} - -// FirmwareTaskStatus provides a mock function with given fields: ctx, kind, component, taskID, installVersion -func (_m *MockQueryor) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component string, taskID string, installVersion string) (constants.TaskState, string, error) { - ret := _m.Called(ctx, kind, component, taskID, installVersion) - - if len(ret) == 0 { - panic("no return value specified for FirmwareTaskStatus") - } - - var r0 constants.TaskState - var r1 string - var r2 error - if rf, ok := ret.Get(0).(func(context.Context, constants.FirmwareInstallStep, string, string, string) (constants.TaskState, string, error)); ok { - return rf(ctx, kind, component, taskID, installVersion) - } - if rf, ok := ret.Get(0).(func(context.Context, constants.FirmwareInstallStep, string, string, string) constants.TaskState); ok { - r0 = rf(ctx, kind, component, taskID, installVersion) - } else { - r0 = ret.Get(0).(constants.TaskState) - } - - if rf, ok := ret.Get(1).(func(context.Context, constants.FirmwareInstallStep, string, string, string) string); ok { - r1 = rf(ctx, kind, component, taskID, installVersion) - } else { - r1 = ret.Get(1).(string) - } - - if rf, ok := ret.Get(2).(func(context.Context, constants.FirmwareInstallStep, string, string, string) error); ok { - r2 = rf(ctx, kind, component, taskID, installVersion) - } else { - r2 = ret.Error(2) - } - - return r0, r1, r2 -} - -// MockQueryor_FirmwareTaskStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareTaskStatus' -type MockQueryor_FirmwareTaskStatus_Call struct { - *mock.Call -} - -// FirmwareTaskStatus is a helper method to define mock.On call -// - ctx context.Context -// - kind constants.FirmwareInstallStep -// - component string -// - taskID string -// - installVersion string -func (_e *MockQueryor_Expecter) FirmwareTaskStatus(ctx interface{}, kind interface{}, component interface{}, taskID interface{}, installVersion interface{}) *MockQueryor_FirmwareTaskStatus_Call { - return &MockQueryor_FirmwareTaskStatus_Call{Call: _e.mock.On("FirmwareTaskStatus", ctx, kind, component, taskID, installVersion)} -} - -func (_c *MockQueryor_FirmwareTaskStatus_Call) Run(run func(ctx context.Context, kind constants.FirmwareInstallStep, component string, taskID string, installVersion string)) *MockQueryor_FirmwareTaskStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(constants.FirmwareInstallStep), args[2].(string), args[3].(string), args[4].(string)) - }) - return _c -} - -func (_c *MockQueryor_FirmwareTaskStatus_Call) Return(state constants.TaskState, status string, err error) *MockQueryor_FirmwareTaskStatus_Call { - _c.Call.Return(state, status, err) - return _c -} - -func (_c *MockQueryor_FirmwareTaskStatus_Call) RunAndReturn(run func(context.Context, constants.FirmwareInstallStep, string, string, string) (constants.TaskState, string, error)) *MockQueryor_FirmwareTaskStatus_Call { - _c.Call.Return(run) - return _c -} - -// FirmwareUpload provides a mock function with given fields: ctx, component, reader -func (_m *MockQueryor) FirmwareUpload(ctx context.Context, component string, reader *os.File) (string, error) { - ret := _m.Called(ctx, component, reader) - - if len(ret) == 0 { - panic("no return value specified for FirmwareUpload") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, *os.File) (string, error)); ok { - return rf(ctx, component, reader) - } - if rf, ok := ret.Get(0).(func(context.Context, string, *os.File) string); ok { - r0 = rf(ctx, component, reader) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context, string, *os.File) error); ok { - r1 = rf(ctx, component, reader) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockQueryor_FirmwareUpload_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FirmwareUpload' -type MockQueryor_FirmwareUpload_Call struct { - *mock.Call -} - -// FirmwareUpload is a helper method to define mock.On call -// - ctx context.Context -// - component string -// - reader *os.File -func (_e *MockQueryor_Expecter) FirmwareUpload(ctx interface{}, component interface{}, reader interface{}) *MockQueryor_FirmwareUpload_Call { - return &MockQueryor_FirmwareUpload_Call{Call: _e.mock.On("FirmwareUpload", ctx, component, reader)} -} - -func (_c *MockQueryor_FirmwareUpload_Call) Run(run func(ctx context.Context, component string, reader *os.File)) *MockQueryor_FirmwareUpload_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(*os.File)) - }) - return _c -} - -func (_c *MockQueryor_FirmwareUpload_Call) Return(uploadVerifyTaskID string, err error) *MockQueryor_FirmwareUpload_Call { - _c.Call.Return(uploadVerifyTaskID, err) - return _c -} - -func (_c *MockQueryor_FirmwareUpload_Call) RunAndReturn(run func(context.Context, string, *os.File) (string, error)) *MockQueryor_FirmwareUpload_Call { - _c.Call.Return(run) - return _c -} - -// Inventory provides a mock function with given fields: ctx -func (_m *MockQueryor) Inventory(ctx context.Context) (*common.Device, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Inventory") - } - - var r0 *common.Device - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (*common.Device, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) *common.Device); ok { - r0 = rf(ctx) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*common.Device) - } - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockQueryor_Inventory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Inventory' -type MockQueryor_Inventory_Call struct { - *mock.Call -} - -// Inventory is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockQueryor_Expecter) Inventory(ctx interface{}) *MockQueryor_Inventory_Call { - return &MockQueryor_Inventory_Call{Call: _e.mock.On("Inventory", ctx)} -} - -func (_c *MockQueryor_Inventory_Call) Run(run func(ctx context.Context)) *MockQueryor_Inventory_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockQueryor_Inventory_Call) Return(_a0 *common.Device, _a1 error) *MockQueryor_Inventory_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockQueryor_Inventory_Call) RunAndReturn(run func(context.Context) (*common.Device, error)) *MockQueryor_Inventory_Call { - _c.Call.Return(run) - return _c -} - -// Open provides a mock function with given fields: ctx -func (_m *MockQueryor) Open(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for Open") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockQueryor_Open_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Open' -type MockQueryor_Open_Call struct { - *mock.Call -} - -// Open is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockQueryor_Expecter) Open(ctx interface{}) *MockQueryor_Open_Call { - return &MockQueryor_Open_Call{Call: _e.mock.On("Open", ctx)} -} - -func (_c *MockQueryor_Open_Call) Run(run func(ctx context.Context)) *MockQueryor_Open_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockQueryor_Open_Call) Return(_a0 error) *MockQueryor_Open_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockQueryor_Open_Call) RunAndReturn(run func(context.Context) error) *MockQueryor_Open_Call { - _c.Call.Return(run) - return _c -} - -// PowerStatus provides a mock function with given fields: ctx -func (_m *MockQueryor) PowerStatus(ctx context.Context) (string, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for PowerStatus") - } - - var r0 string - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) string); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(string) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockQueryor_PowerStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PowerStatus' -type MockQueryor_PowerStatus_Call struct { - *mock.Call -} - -// PowerStatus is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockQueryor_Expecter) PowerStatus(ctx interface{}) *MockQueryor_PowerStatus_Call { - return &MockQueryor_PowerStatus_Call{Call: _e.mock.On("PowerStatus", ctx)} -} - -func (_c *MockQueryor_PowerStatus_Call) Run(run func(ctx context.Context)) *MockQueryor_PowerStatus_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockQueryor_PowerStatus_Call) Return(status string, err error) *MockQueryor_PowerStatus_Call { - _c.Call.Return(status, err) - return _c -} - -func (_c *MockQueryor_PowerStatus_Call) RunAndReturn(run func(context.Context) (string, error)) *MockQueryor_PowerStatus_Call { - _c.Call.Return(run) - return _c -} - -// ReinitializeClient provides a mock function with given fields: ctx -func (_m *MockQueryor) ReinitializeClient(ctx context.Context) { - _m.Called(ctx) -} - -// MockQueryor_ReinitializeClient_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ReinitializeClient' -type MockQueryor_ReinitializeClient_Call struct { - *mock.Call -} - -// ReinitializeClient is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockQueryor_Expecter) ReinitializeClient(ctx interface{}) *MockQueryor_ReinitializeClient_Call { - return &MockQueryor_ReinitializeClient_Call{Call: _e.mock.On("ReinitializeClient", ctx)} -} - -func (_c *MockQueryor_ReinitializeClient_Call) Run(run func(ctx context.Context)) *MockQueryor_ReinitializeClient_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockQueryor_ReinitializeClient_Call) Return() *MockQueryor_ReinitializeClient_Call { - _c.Call.Return() - return _c -} - -func (_c *MockQueryor_ReinitializeClient_Call) RunAndReturn(run func(context.Context)) *MockQueryor_ReinitializeClient_Call { - _c.Call.Return(run) - return _c -} - -// ResetBMC provides a mock function with given fields: ctx -func (_m *MockQueryor) ResetBMC(ctx context.Context) error { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for ResetBMC") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockQueryor_ResetBMC_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResetBMC' -type MockQueryor_ResetBMC_Call struct { - *mock.Call -} - -// ResetBMC is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockQueryor_Expecter) ResetBMC(ctx interface{}) *MockQueryor_ResetBMC_Call { - return &MockQueryor_ResetBMC_Call{Call: _e.mock.On("ResetBMC", ctx)} -} - -func (_c *MockQueryor_ResetBMC_Call) Run(run func(ctx context.Context)) *MockQueryor_ResetBMC_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockQueryor_ResetBMC_Call) Return(_a0 error) *MockQueryor_ResetBMC_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockQueryor_ResetBMC_Call) RunAndReturn(run func(context.Context) error) *MockQueryor_ResetBMC_Call { - _c.Call.Return(run) - return _c -} - -// SetPowerState provides a mock function with given fields: ctx, state -func (_m *MockQueryor) SetPowerState(ctx context.Context, state string) error { - ret := _m.Called(ctx, state) - - if len(ret) == 0 { - panic("no return value specified for SetPowerState") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { - r0 = rf(ctx, state) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MockQueryor_SetPowerState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPowerState' -type MockQueryor_SetPowerState_Call struct { - *mock.Call -} - -// SetPowerState is a helper method to define mock.On call -// - ctx context.Context -// - state string -func (_e *MockQueryor_Expecter) SetPowerState(ctx interface{}, state interface{}) *MockQueryor_SetPowerState_Call { - return &MockQueryor_SetPowerState_Call{Call: _e.mock.On("SetPowerState", ctx, state)} -} - -func (_c *MockQueryor_SetPowerState_Call) Run(run func(ctx context.Context, state string)) *MockQueryor_SetPowerState_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string)) - }) - return _c -} - -func (_c *MockQueryor_SetPowerState_Call) Return(_a0 error) *MockQueryor_SetPowerState_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockQueryor_SetPowerState_Call) RunAndReturn(run func(context.Context, string) error) *MockQueryor_SetPowerState_Call { - _c.Call.Return(run) - return _c -} - -// NewMockQueryor creates a new instance of MockQueryor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockQueryor(t interface { - mock.TestingT - Cleanup(func()) -}) *MockQueryor { - mock := &MockQueryor{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/internal/install/task_handler.go b/internal/install/task_handler.go index 3c4c8cb5..dfa3a048 100644 --- a/internal/install/task_handler.go +++ b/internal/install/task_handler.go @@ -4,6 +4,7 @@ import ( "context" "github.com/bmc-toolbox/common" + "github.com/metal-toolbox/flasher/internal/device" "github.com/metal-toolbox/flasher/internal/model" "github.com/metal-toolbox/flasher/internal/outofband" "github.com/metal-toolbox/flasher/internal/runner" @@ -111,13 +112,13 @@ func (t *handler) queryFromDevice(ctx context.Context) ([]*rtypes.Component, err t.taskCtx.Task.Status.Append("connecting to device BMC") - if err := t.taskCtx.DeviceQueryor.Open(ctx); err != nil { + if err := t.taskCtx.DeviceQueryor.(device.OutofbandQueryor).Open(ctx); err != nil { return nil, err } t.taskCtx.Task.Status.Append("collecting inventory from device BMC") - deviceCommon, err := t.taskCtx.DeviceQueryor.Inventory(ctx) + deviceCommon, err := t.taskCtx.DeviceQueryor.(device.OutofbandQueryor).Inventory(ctx) if err != nil { return nil, err } @@ -138,7 +139,7 @@ func (t *handler) OnSuccess(ctx context.Context, _ *model.Task) { return } - if err := t.taskCtx.DeviceQueryor.Close(ctx); err != nil { + if err := t.taskCtx.DeviceQueryor.(device.OutofbandQueryor).Close(ctx); err != nil { t.taskCtx.Logger.WithFields(logrus.Fields{"err": err.Error()}).Warn("device logout error") } } @@ -148,7 +149,7 @@ func (t *handler) OnFailure(ctx context.Context, _ *model.Task) { return } - if err := t.taskCtx.DeviceQueryor.Close(ctx); err != nil { + if err := t.taskCtx.DeviceQueryor.(device.OutofbandQueryor).Close(ctx); err != nil { t.taskCtx.Logger.WithFields(logrus.Fields{"err": err.Error()}).Warn("device logout error") } } diff --git a/internal/outofband/action_handlers.go b/internal/outofband/action_handlers.go index e0faa5b7..3f495c86 100644 --- a/internal/outofband/action_handlers.go +++ b/internal/outofband/action_handlers.go @@ -86,7 +86,7 @@ type handler struct { firmware *model.Firmware task *model.Task action *model.Action - deviceQueryor device.Queryor + deviceQueryor device.OutofbandQueryor publisher model.Publisher logger *logrus.Entry } diff --git a/internal/outofband/action_handlers_test.go b/internal/outofband/action_handlers_test.go index f53d3393..cfd04753 100644 --- a/internal/outofband/action_handlers_test.go +++ b/internal/outofband/action_handlers_test.go @@ -45,11 +45,11 @@ func TestCheckCurrentFirmware(t *testing.T) { t.Parallel() // helper func to initialize handler, mock device queryor - init := func(t *testing.T) (*handler, *device.MockQueryor) { + init := func(t *testing.T) (*handler, *device.MockOutofbandQueryor) { t.Helper() actionCtx := newTestActionCtx() - m := new(device.MockQueryor) + m := new(device.MockOutofbandQueryor) m.On("FirmwareInstallSteps", mock.Anything, "drive").Once().Return( []bconsts.FirmwareInstallStep{ bconsts.FirmwareInstallStepUploadInitiateInstall, @@ -240,11 +240,11 @@ func TestPollFirmwareInstallStatus(t *testing.T) { }, } - init := func(t *testing.T) (*handler, *device.MockQueryor) { + init := func(t *testing.T) (*handler, *device.MockOutofbandQueryor) { t.Helper() actionCtx := newTestActionCtx() - m := new(device.MockQueryor) + m := new(device.MockOutofbandQueryor) m.On("FirmwareInstallSteps", mock.Anything, "drive").Once().Return( []bconsts.FirmwareInstallStep{ bconsts.FirmwareInstallStepUploadInitiateInstall, diff --git a/internal/outofband/actions.go b/internal/outofband/actions.go index d432e4df..f5172a24 100644 --- a/internal/outofband/actions.go +++ b/internal/outofband/actions.go @@ -42,7 +42,7 @@ type ActionHandler struct { handler *handler } -func initHandler(actionCtx *runner.ActionHandlerContext, queryor device.Queryor) *handler { +func initHandler(actionCtx *runner.ActionHandlerContext, queryor device.OutofbandQueryor) *handler { return &handler{ task: actionCtx.Task, firmware: actionCtx.Firmware, @@ -53,11 +53,11 @@ func initHandler(actionCtx *runner.ActionHandlerContext, queryor device.Queryor) } func (o *ActionHandler) ComposeAction(ctx context.Context, actionCtx *runner.ActionHandlerContext) (*model.Action, error) { - var deviceQueryor device.Queryor + var deviceQueryor device.OutofbandQueryor if actionCtx.DeviceQueryor == nil { deviceQueryor = NewDeviceQueryor(ctx, actionCtx.Task.Server, actionCtx.Logger) } else { - deviceQueryor = actionCtx.DeviceQueryor + deviceQueryor = actionCtx.DeviceQueryor.(device.OutofbandQueryor) } o.handler = initHandler(actionCtx, deviceQueryor) diff --git a/internal/outofband/actions_test.go b/internal/outofband/actions_test.go index 7af00c45..47547f80 100644 --- a/internal/outofband/actions_test.go +++ b/internal/outofband/actions_test.go @@ -38,7 +38,7 @@ func TestComposeAction(t *testing.T) { tests := []struct { name string - mockSetup func(actionCtx *runner.ActionHandlerContext, m *device.MockQueryor) + mockSetup func(actionCtx *runner.ActionHandlerContext, m *device.MockOutofbandQueryor) expectBMCResetPreInstall bool expectForceInstall bool expectBMCResetPostInstall bool @@ -49,7 +49,7 @@ func TestComposeAction(t *testing.T) { { name: "test bmc-reset pre-install is true on first action", expectBMCResetPreInstall: true, - mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockQueryor) { + mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockOutofbandQueryor) { actionCtx.Task.Parameters.ResetBMCBeforeInstall = true actionCtx.First = true @@ -66,7 +66,7 @@ func TestComposeAction(t *testing.T) { { name: "test bmc-reset pre-install is false on first action, force is true", expectForceInstall: true, - mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockQueryor) { + mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockOutofbandQueryor) { actionCtx.First = true actionCtx.Task.Parameters.ForceInstall = true actionCtx.Task.Parameters.ResetBMCBeforeInstall = false @@ -84,7 +84,7 @@ func TestComposeAction(t *testing.T) { { name: "test bmc reset post install", expectBMCResetPostInstall: true, - mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockQueryor) { + mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockOutofbandQueryor) { actionCtx.DeviceQueryor = m m.On("FirmwareInstallSteps", mock.Anything, "drive").Once().Return( []bconsts.FirmwareInstallStep{ @@ -99,7 +99,7 @@ func TestComposeAction(t *testing.T) { { name: "test host power off pre-install", expectHostPowerOffPreInstall: true, - mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockQueryor) { + mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockOutofbandQueryor) { actionCtx.First = true actionCtx.DeviceQueryor = m @@ -116,7 +116,7 @@ func TestComposeAction(t *testing.T) { { name: "test bmc reset on install failure", expectBMCResetOnInstallFailure: true, - mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockQueryor) { + mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockOutofbandQueryor) { actionCtx.DeviceQueryor = m m.On("FirmwareInstallSteps", mock.Anything, "drive").Once().Return( []bconsts.FirmwareInstallStep{ @@ -130,7 +130,7 @@ func TestComposeAction(t *testing.T) { }, { name: "test error - no install steps", - mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockQueryor) { + mockSetup: func(actionCtx *runner.ActionHandlerContext, m *device.MockOutofbandQueryor) { actionCtx.DeviceQueryor = m m.On("FirmwareInstallSteps", mock.Anything, "drive").Once().Return( []bconsts.FirmwareInstallStep{}, @@ -146,7 +146,7 @@ func TestComposeAction(t *testing.T) { actx := newTestActionCtx() // setup mocks - mockDeviceQueryor := new(device.MockQueryor) + mockDeviceQueryor := new(device.MockOutofbandQueryor) tc.mockSetup(actx, mockDeviceQueryor) // init handler diff --git a/internal/outofband/bmc.go b/internal/outofband/bmc.go index 8c36366e..98e2e0b8 100644 --- a/internal/outofband/bmc.go +++ b/internal/outofband/bmc.go @@ -59,7 +59,7 @@ type bmc struct { } // NewDeviceQueryor returns a bmc queryor that implements the DeviceQueryor interface -func NewDeviceQueryor(ctx context.Context, asset *rtypes.Server, logger *logrus.Entry) device.Queryor { +func NewDeviceQueryor(ctx context.Context, asset *rtypes.Server, logger *logrus.Entry) device.OutofbandQueryor { return &bmc{ client: newBmclibv2Client(ctx, asset, logger), logger: logger, diff --git a/internal/outofband/graph.go b/internal/outofband/graph.go index 82937dd8..88b37d84 100644 --- a/internal/outofband/graph.go +++ b/internal/outofband/graph.go @@ -17,7 +17,7 @@ import ( func GraphSteps(ctx context.Context, g *dot.Graph) error { // setup a mock device queryor - m := new(device.MockQueryor) + m := new(device.MockOutofbandQueryor) m.On("FirmwareInstallSteps", mock.Anything, "drive").Once().Return( []bconsts.FirmwareInstallStep{ bconsts.FirmwareInstallStepUploadInitiateInstall, diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 52ef068c..4834fe97 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -43,7 +43,9 @@ type TaskHandlerContext struct { Logger *logrus.Entry // Device queryor interface - DeviceQueryor device.Queryor + // + // type asserted to InbandQueryor or OutofbandQueryor at invocation + DeviceQueryor any // Data store repository Store store.Repository diff --git a/internal/worker/task_handler.go b/internal/worker/task_handler.go index f03654dc..d42d7686 100644 --- a/internal/worker/task_handler.go +++ b/internal/worker/task_handler.go @@ -276,7 +276,7 @@ func (t *handler) OnSuccess(ctx context.Context, _ *model.Task) { return } - if err := t.DeviceQueryor.Close(ctx); err != nil { + if err := t.DeviceQueryor.(device.OutofbandQueryor).Close(ctx); err != nil { t.Logger.WithFields(logrus.Fields{"err": err.Error()}).Warn("device logout error") } } @@ -286,7 +286,7 @@ func (t *handler) OnFailure(ctx context.Context, _ *model.Task) { return } - if err := t.DeviceQueryor.Close(ctx); err != nil { + if err := t.DeviceQueryor.(device.OutofbandQueryor).Close(ctx); err != nil { t.Logger.WithFields(logrus.Fields{"err": err.Error()}).Warn("device logout error") } } From 9f75e14b12b103ad60ea73f339e6fbf1ee2d5183 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 11:05:32 +0200 Subject: [PATCH 02/20] model/action: adds a few additional fields The Action object is persisted as part of the Task object and the added fields provide context to installer across action steps --- internal/model/action.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/model/action.go b/internal/model/action.go index 07fd8dc4..54bace25 100644 --- a/internal/model/action.go +++ b/internal/model/action.go @@ -20,6 +20,9 @@ type Action struct { // or an install was initiated on the BMC . BMCTaskID string `json:"bmc_task_id,omitempty"` + // Set to the component identified as the target of the firmware install + Component *rtypes.Component `json:"component"` + // Method of install InstallMethod InstallMethod `json:"install_method"` @@ -29,6 +32,9 @@ type Action struct { // Firmware to be installed, this is set in the Task Plan phase. Firmware Firmware `json:"firmware"` + // In the remote inband case a list of firmwares will be delegated for install + Firmwares []Firmware `json:"firmwares"` + FirmwareInstallStep string `json:"firmware_install_step"` // FirmwareTempFile is the temporary file downloaded to be installed. @@ -51,6 +57,12 @@ type Action struct { // HostPowerCycled is set when the host has been power cycled for the action. HostPowerCycled bool `json:"host_power_cycled"` + // HostPowerCycleInitiated indicates when a power cycle has been initated for the host. + HostPowerCycleInitiated bool `json:"host_power_cycle_initiated"` + + //HostPowerOffInitiated indicates a power off was initated on the host. + HostPowerOffInitiated bool `json:"host_power_off_initiated"` + // HostPowerOffPreInstall is set when the firmware install provider indicates // the host must be powered off before proceeding with the install step. HostPowerOffPreInstall bool `json:"host_power_off_pre_install"` @@ -61,6 +73,9 @@ type Action struct { // Last is set to true when its the last action being executed Last bool `json:"last"` + // Attempts indicates how many times this action has been tried + Attempts int `json:"attempts"` + // Steps identify the smallest unit of work executed by an action Steps Steps `json:"steps"` } @@ -86,3 +101,8 @@ func (a Actions) ByID(id string) *Action { return nil } + +func (a Actions) Prepend(action *Action) Actions { + a = append([]*Action{action}, a...) + return a +} From 4e7014086ed2de0776ef9eb3b3a0c8d4f3da1343 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 11:09:11 +0200 Subject: [PATCH 03/20] model/firmware: turn InbandInstall, Oem attributes to be boolean --- internal/model/firmware.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/internal/model/firmware.go b/internal/model/firmware.go index 6c7fea73..56930649 100644 --- a/internal/model/firmware.go +++ b/internal/model/firmware.go @@ -10,15 +10,16 @@ import ( // // nolint:govet // fieldalignment struct is easier to read in the current format type Firmware struct { - ID string `yaml:"id"` - Vendor string `yaml:"vendor"` - Models []string `yaml:"models"` - FileName string `yaml:"filename"` - Version string `yaml:"version"` - URL string `yaml:"URL"` - Component string `yaml:"component"` - Checksum string `yaml:"checksum"` - InstallMethod InstallMethod `yaml:"install_method"` + ID string `yaml:"id"` + Vendor string `yaml:"vendor"` + Models []string `yaml:"models"` + FileName string `yaml:"filename"` + Version string `yaml:"version"` + URL string `yaml:"URL"` + Component string `yaml:"component"` + Checksum string `yaml:"checksum"` + InstallInband bool `yaml:"install_inband"` + Oem bool `yaml:"oem"` } var ( From 2c1c8cc0ea8935f2c448a255c8188f62fb7e8099 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 11:10:47 +0200 Subject: [PATCH 04/20] app: load inband run mode configuration --- internal/app/app.go | 5 +- internal/app/config.go | 115 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/internal/app/app.go b/internal/app/app.go index 3d981629..7573baaa 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -37,10 +37,12 @@ type App struct { Logger *logrus.Logger // Kind is the type of application - worker Kind model.AppKind + // Mode indicates the means of installing firmware on the target + Mode model.RunMode } // New returns returns a new instance of the flasher app -func New(appKind model.AppKind, storeKind model.StoreKind, cfgFile, loglevel string, profiling bool) (*App, <-chan os.Signal, error) { +func New(appKind model.AppKind, storeKind model.StoreKind, cfgFile, loglevel string, profiling bool, mode model.RunMode) (*App, <-chan os.Signal, error) { if appKind != model.AppKindWorker && appKind != model.AppKindCLI { return nil, nil, errors.Wrap(ErrAppInit, "invalid app kind: "+string(appKind)) } @@ -50,6 +52,7 @@ func New(appKind model.AppKind, storeKind model.StoreKind, cfgFile, loglevel str Kind: appKind, Config: &Configuration{}, Logger: logrus.New(), + Mode: mode, } switch model.LogLevel(loglevel) { diff --git a/internal/app/config.go b/internal/app/config.go index e96f4188..c4d705f7 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/jeremywohl/flatten" "github.com/metal-toolbox/flasher/internal/model" "github.com/mitchellh/mapstructure" @@ -47,6 +48,12 @@ type Configuration struct { // // This parameter is required when StoreKind is set to serverservice. FleetDBAPIOptions *FleetDBAPIOptions `mapstructure:"serverservice"` + + // ServerID parameter required for inband run mode + ServerID string `mapstructure:"serverid"` + + // OrchestratorAPIParams required for inband run mode + OrchestratorAPIParams *OrchestratorAPIParams `mapstructure:"orchestrator_api"` } // FleetDBAPIOptions defines configuration for the FleetDBAPI client. @@ -67,6 +74,20 @@ type FleetDBAPIOptions struct { DisableOAuth bool `mapstructure:"disable_oauth"` } +type InbandRunParams struct { +} + +type OrchestratorAPIParams struct { + OidcIssuerEndpoint string `mapstructure:"oidc_issuer_endpoint"` + OidcAudienceEndpoint string `mapstructure:"oidc_audience_endpoint"` + OidcClientSecret string `mapstructure:"oidc_client_secret"` + OidcClientID string `mapstructure:"oidc_client_id"` + OidcClientScopes []string `mapstructure:"oidc_client_scopes"` + Endpoint string `mapstructure:"endpoint"` + AuthDisabled bool `mapstructure:"disable_oauth"` + AuthToken string +} + // LoadConfiguration loads application configuration // // Reads in the cfgFile when available and overrides from environment variables. @@ -113,6 +134,12 @@ func (a *App) LoadConfiguration(cfgFile string, storeKind model.StoreKind) error a.Config.Concurrency = WorkerConcurrency } + if a.Mode == model.RunInband { + if err := a.inbandInstallParams(); err != nil { + return errors.Wrap(ErrConfig, err.Error()) + } + } + return nil } @@ -184,6 +211,94 @@ func (a *App) NatsParams() (NatsConfig, error) { return cfg, nil } +func (a *App) inbandInstallParams() error { + errInbandParam := errors.New("inband parameter error") + + // load serverID param from env if not defined in configuration + if a.Config.ServerID == "" { + id := a.v.GetString("serverid") + serverID, err := uuid.Parse(id) + if err != nil { + return errors.Wrap(errInbandParam, "serverid parameter invalid: "+err.Error()) + } + + a.Config.ServerID = serverID.String() + } + + // orchestrator client env override params + if err := a.envVarOrchestratorAPIOverrides(); err != nil { + return errors.Wrap(errInbandParam, err.Error()) + } + + return nil +} + +func (a *App) envVarOrchestratorAPIOverrides() error { + if a.Config.OrchestratorAPIParams == nil { + a.Config.OrchestratorAPIParams = &OrchestratorAPIParams{} + } + + cfg := a.Config.OrchestratorAPIParams + if a.v.GetString("orchestrator.api.endpoint") != "" { + cfg.Endpoint = a.v.GetString("orchestrator.api.endpoint") + } else { + return errors.New("missing parameter: orchestrator.api.endpoint") + } + + cfg.AuthDisabled = a.v.GetBool("orchestrator.api.disable.oauth") + if cfg.AuthDisabled { + return nil + } + + if a.v.GetString("orchestrator.api.authtoken") != "" { + cfg.AuthToken = a.v.GetString("orchestrator.api.authtoken") + } else { + return errors.New("missing parameter: orchestrator.api.authtoken") + } + + if a.v.GetString("orchestrator.api.oidc.issuer.endpoint") != "" { + cfg.OidcIssuerEndpoint = a.v.GetString("orchestrator.api.oidc.issuer.endpoint") + } + + if cfg.OidcIssuerEndpoint == "" { + return errors.New("orchestrator api oidc.issuer.endpoint not defined") + } + + if a.v.GetString("orchestrator.api.oidc.audience.endpoint") != "" { + cfg.OidcAudienceEndpoint = a.v.GetString("orchestrator.api.oidc.audience.endpoint") + } + + if cfg.OidcAudienceEndpoint == "" { + return errors.New("orchestrator api oidc.audience.endpoint not defined") + } + + if a.v.GetString("orchestrator.api.oidc.client.secret") != "" { + cfg.OidcClientSecret = a.v.GetString("orchestrator.api.oidc.client.secret") + } + + if cfg.OidcClientSecret == "" { + return errors.New("orchestrator.api.oidc.client.secret not defined") + } + + if a.v.GetString("orchestrator.api.oidc.client.id") != "" { + cfg.OidcClientID = a.v.GetString("orchestrator.api.oidc.client.id") + } + + if cfg.OidcClientID == "" { + return errors.New("orchestrator.api.oidc.client.id not defined") + } + + if a.v.GetString("orchestrator.api.oidc.client.scopes") != "" { + cfg.OidcClientScopes = a.v.GetStringSlice("orchestrator.api.oidc.client.scopes") + } + + if len(cfg.OidcClientScopes) == 0 { + return errors.New("orchestrator api oidc.client.scopes not defined") + } + + return nil +} + // Server service configuration options // nolint:gocyclo // parameter validation is cyclomatic From 1b1100b716da3d4d48cedacb54bfc72d664aeb6b Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 11:13:30 +0200 Subject: [PATCH 05/20] download: move download into its own package so its available for reuse --- internal/{outofband => download}/download.go | 8 ++++---- internal/{outofband => download}/download_test.go | 4 ++-- internal/outofband/action_handlers.go | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) rename internal/{outofband => download}/download.go (93%) rename internal/{outofband => download}/download_test.go (94%) diff --git a/internal/outofband/download.go b/internal/download/download.go similarity index 93% rename from internal/outofband/download.go rename to internal/download/download.go index 18dbbd0c..dcac9fe9 100644 --- a/internal/outofband/download.go +++ b/internal/download/download.go @@ -1,4 +1,4 @@ -package outofband +package download import ( "bytes" @@ -24,8 +24,8 @@ var ( ErrFormat = errors.New("bad checksum format") ) -// download fetches the file into dst -func download(ctx context.Context, fileURL, dst string) error { +// FromURLToFile fetches the file into dst +func FromURLToFile(ctx context.Context, fileURL, dst string) error { // create file fileHandle, err := os.Create(dst) if err != nil { @@ -65,7 +65,7 @@ func download(ctx context.Context, fileURL, dst string) error { return err } -func checksumValidate(filename, checksum string) error { +func ChecksumValidate(filename, checksum string) error { // no checksum prefix, default to md5sum if !strings.Contains(checksum, ":") { return checksumValidateMD5(filename, checksum) diff --git a/internal/outofband/download_test.go b/internal/download/download_test.go similarity index 94% rename from internal/outofband/download_test.go rename to internal/download/download_test.go index ca0dff05..ad8f401c 100644 --- a/internal/outofband/download_test.go +++ b/internal/download/download_test.go @@ -1,4 +1,4 @@ -package outofband +package download import ( "os" @@ -57,7 +57,7 @@ func TestChecksumValidate(t *testing.T) { defer os.Remove(binPath) - err = checksumValidate(binPath, tt.checksum) + err = ChecksumValidate(binPath, tt.checksum) if tt.expectedError != nil { assert.ErrorIs(t, err, tt.expectedError) return diff --git a/internal/outofband/action_handlers.go b/internal/outofband/action_handlers.go index 3f495c86..6ee15f7e 100644 --- a/internal/outofband/action_handlers.go +++ b/internal/outofband/action_handlers.go @@ -11,6 +11,7 @@ import ( "github.com/bmc-toolbox/common" "github.com/hashicorp/go-multierror" "github.com/metal-toolbox/flasher/internal/device" + "github.com/metal-toolbox/flasher/internal/download" "github.com/metal-toolbox/flasher/internal/metrics" "github.com/metal-toolbox/flasher/internal/model" "github.com/pkg/errors" @@ -76,7 +77,6 @@ var ( ErrContextCancelled = errors.New("context canceled") ErrUnexpected = errors.New("unexpected error occurred") ErrInstalledFirmwareNotEqual = errors.New("installed and expected firmware not equal") - ErrInstalledFirmwareEqual = errors.New("installed and expected firmware are equal, no action necessary") ErrInstalledVersionUnknown = errors.New("installed version unknown") ErrComponentNotFound = errors.New("component not identified for firmware install") ErrRequireHostPoweredOff = errors.New("expected host to be powered off") @@ -284,7 +284,7 @@ func (h *handler) downloadFirmware(ctx context.Context) error { file := filepath.Join(dir, h.firmware.FileName) // download firmware file - err = download(ctx, h.firmware.URL, file) + err = download.FromURLToFile(ctx, h.firmware.URL, file) if err != nil { return err } @@ -301,7 +301,7 @@ func (h *handler) downloadFirmware(ctx context.Context) error { } // validate checksum - if err := checksumValidate(file, h.firmware.Checksum); err != nil { + if err := download.ChecksumValidate(file, h.firmware.Checksum); err != nil { os.RemoveAll(filepath.Dir(file)) return err } From ffdb421713be2f69f1618662ed0ab5522fdec33b Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 11:36:32 +0200 Subject: [PATCH 06/20] inband: Add actions and firmware install action handlers include a component helper method --- internal/inband/actions.go | 217 ++++++++++++++++++++++ internal/inband/actions_handlers.go | 271 ++++++++++++++++++++++++++++ internal/inband/server.go | 74 ++++++++ internal/model/component.go | 17 ++ 4 files changed, 579 insertions(+) create mode 100644 internal/inband/actions.go create mode 100644 internal/inband/actions_handlers.go create mode 100644 internal/inband/server.go diff --git a/internal/inband/actions.go b/internal/inband/actions.go new file mode 100644 index 00000000..68ddd423 --- /dev/null +++ b/internal/inband/actions.go @@ -0,0 +1,217 @@ +package inband + +import ( + "context" + "fmt" + "strings" + + "github.com/metal-toolbox/flasher/internal/device" + "github.com/metal-toolbox/flasher/internal/model" + "github.com/metal-toolbox/flasher/internal/runner" + rtypes "github.com/metal-toolbox/rivets/types" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + imodel "github.com/metal-toolbox/ironlib/model" +) + +var ( + errCompose = errors.New("error in composing steps for firmware install") +) + +const ( + // transition types implemented and defined further below + powerOffServer model.StepName = "powerOffServer" + powerCycleServer model.StepName = "powerCycleServer" + checkInstalledFirmware model.StepName = "checkInstalledFirmware" + downloadFirmware model.StepName = "downloadFirmware" + installFirmware model.StepName = "installFirmware" + pollInstallStatus model.StepName = "pollInstallStatus" +) + +const ( + PreInstall model.StepGroup = "PreInstall" + PostInstall model.StepGroup = "PostInstall" + Install model.StepGroup = "Install" + PowerState model.StepGroup = "PowerState" +) + +type ActionHandler struct { + handler *handler +} + +func (i *ActionHandler) identifyComponent(ctx context.Context, component string, models []string, deviceQueryor device.InbandQueryor) (*rtypes.Component, error) { + var components rtypes.Components + + if len(i.handler.task.Server.Components) > 0 { + components = rtypes.Components(i.handler.task.Server.Components) + } else { + deviceCommon, err := deviceQueryor.Inventory(ctx) + if err != nil { + return nil, err + } + + components, err = model.NewComponentConverter().CommonDeviceToComponents(deviceCommon) + if err != nil { + return nil, err + } + } + + found := components.ByNameModel(component, models) + if found == nil { + // nolint:goerr113 // its clearer to define this error here + errComponentMatch := fmt.Errorf( + "unable to identify component '%s' from inventory for given models: %s", + component, + strings.Join(models, ","), + ) + + return nil, errComponentMatch + } + + return found, nil +} + +func (i *ActionHandler) ComposeAction(ctx context.Context, actionCtx *runner.ActionHandlerContext) (*model.Action, error) { + var deviceQueryor device.InbandQueryor + if actionCtx.DeviceQueryor == nil { + deviceQueryor = NewDeviceQueryor(actionCtx.Logger) + } else { + deviceQueryor = actionCtx.DeviceQueryor.(device.InbandQueryor) + } + + i.handler = initHandler(actionCtx, deviceQueryor) + + component, err := i.identifyComponent(ctx, actionCtx.Firmware.Component, actionCtx.Firmware.Models, deviceQueryor) + if err != nil { + return nil, errors.Wrap(ErrComponentNotFound, err.Error()) + } + + i.handler.logger.WithFields(logrus.Fields{ + "component": actionCtx.Firmware.Component, + "model": component.Model, + "current": component.Firmware.Installed, + }).Info("target component identified for firmware install") + + required, err := deviceQueryor.FirmwareInstallRequirements( + ctx, + actionCtx.Firmware.Component, + actionCtx.Firmware.Vendor, + component.Model, + ) + if err != nil { + // not a fatal error + i.handler.logger.WithFields(logrus.Fields{ + "component": actionCtx.Firmware.Component, + "model": actionCtx.Firmware.Models, + }).WithError(err). + Info("No firmware install requirements were identified for component") + } + + steps, err := i.composeSteps(required) + if err != nil { + return nil, errors.Wrap(errCompose, err.Error()) + } + + action := &model.Action{ + InstallMethod: model.InstallMethodInband, + Firmware: *actionCtx.Firmware, + ForceInstall: actionCtx.Task.Parameters.ForceInstall, + Steps: steps, + First: actionCtx.First, + Last: actionCtx.Last, + Component: component, + } + + i.handler.action = action + + return action, nil +} + +func initHandler(actionCtx *runner.ActionHandlerContext, queryor device.InbandQueryor) *handler { + return &handler{ + task: actionCtx.Task, + firmware: actionCtx.Firmware, + publisher: actionCtx.Publisher, + logger: actionCtx.Logger, + deviceQueryor: queryor, + } +} + +func (i *ActionHandler) composeSteps(required *imodel.UpdateRequirements) (model.Steps, error) { + var final model.Steps + + // pre-install steps + preinstall, err := i.definitions().ByGroup(PreInstall) + if err != nil { + return nil, err + } + + final = append(final, preinstall...) + + // install steps + install, err := i.definitions().ByGroup(Install) + if err != nil { + return nil, err + } + + final = append(final, install...) + + if required != nil && required.PostInstallHostPowercycle { + powerCycle, errDef := i.definitions().ByName(powerCycleServer) + if errDef != nil { + return nil, err + } + + final = append(final, &powerCycle) + } + + postinstall, err := i.definitions().ByGroup(PostInstall) + if err != nil { + return nil, err + } + + final = append(final, postinstall...) + + return final, nil +} + +func (i *ActionHandler) definitions() model.Steps { + return model.Steps{ + { + Name: checkInstalledFirmware, + Group: PreInstall, + Handler: i.handler.checkCurrentFirmware, + Description: "Check firmware currently installed on component", + State: model.StatePending, + }, + { + Name: downloadFirmware, + Group: PreInstall, + Handler: i.handler.downloadFirmware, + Description: "Download and verify firmware file checksum.", + State: model.StatePending, + }, + { + Name: installFirmware, + Group: Install, + Handler: i.handler.installFirmware, + Description: "Install firmware.", + State: model.StatePending, + }, + { + Name: powerCycleServer, + Group: PowerState, + Handler: i.handler.powerCycleServer, + Description: "Turn the computer off and on again.", + State: model.StatePending, + }, + { + Name: checkInstalledFirmware, + Group: PostInstall, + Handler: i.handler.checkCurrentFirmware, + Description: "Check firmware currently installed on components", + State: model.StatePending, + }, + } +} diff --git a/internal/inband/actions_handlers.go b/internal/inband/actions_handlers.go new file mode 100644 index 00000000..54dd30f2 --- /dev/null +++ b/internal/inband/actions_handlers.go @@ -0,0 +1,271 @@ +package inband + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/metal-toolbox/flasher/internal/device" + "github.com/metal-toolbox/flasher/internal/download" + "github.com/metal-toolbox/flasher/internal/metrics" + "github.com/metal-toolbox/flasher/internal/model" + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + "github.com/sirupsen/logrus" + + iutils "github.com/metal-toolbox/ironlib/utils" +) + +const ( + // firmware files are downloaded into this directory + downloadDir = "/tmp" + rebootFlag = "/var/run/reboot" +) + +var ( + ErrInstalledFirmwareNotEqual = errors.New("installed and expected firmware not equal") + ErrInstalledFirmwareEqual = errors.New("installed and expected firmware are equal, no action necessary") + ErrInstalledVersionUnknown = errors.New("installed version unknown") + ErrComponentNotFound = errors.New("component not identified for firmware install") + ErrRequireHostPoweredOff = errors.New("expected host to be powered off") +) + +type handler struct { + firmware *model.Firmware + task *model.Task + action *model.Action + deviceQueryor device.InbandQueryor + publisher model.Publisher + logger *logrus.Entry +} + +func (h *handler) installedEqualsExpected(ctx context.Context, component, expectedFirmware, vendor string, models []string) error { + inv, err := h.deviceQueryor.Inventory(ctx) + if err != nil { + return err + } + + h.logger.WithFields( + logrus.Fields{ + "component": component, + }).Debug("Querying device inventory from BMC for current component firmware") + + components, err := model.NewComponentConverter().CommonDeviceToComponents(inv) + if err != nil { + return err + } + + found := components.ByNameModel(component, models) + if found == nil { + h.logger.WithFields( + logrus.Fields{ + "component": component, + "vendor": vendor, + "models": models, + "err": ErrComponentNotFound, + }).Error("no component found for given component/vendor/model") + + return errors.Wrap(ErrComponentNotFound, + fmt.Sprintf("component: %s, vendor: %s, model: %s", component, + vendor, + models, + ), + ) + } + + h.logger.WithFields( + logrus.Fields{ + "component": component, + "model": found.Model, + "vendor": found.Vendor, + }).Debug("component for update identified") + + if strings.TrimSpace(found.Firmware.Installed) == "" { + return ErrInstalledVersionUnknown + } + + if !strings.EqualFold(expectedFirmware, found.Firmware.Installed) { + return errors.Wrap( + ErrInstalledFirmwareNotEqual, + fmt.Sprintf("expected: %s, current: %s", expectedFirmware, found.Firmware.Installed), + ) + } + + return nil +} + +func (h *handler) checkCurrentFirmware(ctx context.Context) error { + if h.task.Parameters.ForceInstall { + h.logger.WithFields( + logrus.Fields{ + "component": h.firmware.Component, + }).Debug("Skipped installed version lookup - task.Parameters.ForceInstall=true") + + return nil + } + + if err := h.installedEqualsExpected( + ctx, + h.firmware.Component, + h.firmware.Version, + h.firmware.Vendor, + h.firmware.Models, + ); err != nil { + if errors.Is(err, ErrInstalledVersionUnknown) { + return errors.Wrap(err, "use task.Parameters.ForceInstall=true to disable this check") + } + + if errors.Is(err, ErrInstalledFirmwareNotEqual) { + return nil + } + + return err + } + + return ErrInstalledFirmwareEqual +} + +func (h *handler) downloadFirmware(ctx context.Context) error { + if h.action.FirmwareTempFile != "" { + h.logger.WithFields( + logrus.Fields{ + "component": h.firmware.Component, + "file": h.action.FirmwareTempFile, + }).Info("firmware file path provided, skipped download") + + return nil + } + + // create a temp download directory + dir, err := os.MkdirTemp(downloadDir, "") + if err != nil { + return errors.Wrap(err, "error creating tmp directory to download firmware") + } + + file := filepath.Join(dir, h.firmware.FileName) + + // download firmware file + err = download.FromURLToFile(ctx, h.firmware.URL, file) + if err != nil { + return err + } + + // collect download metrics + fileInfo, err := os.Stat(file) + if err == nil { + metrics.DownloadBytes.With( + prometheus.Labels{ + "component": h.firmware.Component, + "vendor": h.firmware.Vendor, + }, + ).Add(float64(fileInfo.Size())) + } + + // validate checksum + if err := download.ChecksumValidate(file, h.firmware.Checksum); err != nil { + os.RemoveAll(filepath.Dir(file)) + return err + } + + // store the firmware temp file location + h.action.FirmwareTempFile = file + + h.logger.WithFields( + logrus.Fields{ + "component": h.firmware.Component, + "version": h.firmware.Version, + "url": h.firmware.URL, + "file": file, + "checksum": h.firmware.Checksum, + }).Info("downloaded and verified firmware file checksum") + + return nil +} + +func (h *handler) installFirmware(ctx context.Context) error { + if !h.task.Parameters.DryRun { + // initiate firmware install + if err := h.deviceQueryor.FirmwareInstall( + ctx, + h.firmware.Component, + h.firmware.Vendor, + h.action.Component.Model, + h.firmware.Version, + h.action.FirmwareTempFile, + h.action.ForceInstall, + ); err != nil { + if errors.Is(err, iutils.ErrRebootRequired) { + h.logger.WithFields( + logrus.Fields{ + "component": h.firmware.Component, + "update": h.firmware.FileName, + "version": h.firmware.Version, + "msg": err.Error(), + }).Info("firmware install requires a server power cycle") + return nil + } + + return err + } + } + + h.logger.WithFields( + logrus.Fields{ + "component": h.firmware.Component, + "update": h.firmware.FileName, + "version": h.firmware.Version, + }).Info("firmware installed") + + return nil +} + +func (h *handler) powerCycleServer(ctx context.Context) error { + if h.task.Parameters.DryRun { + h.logger.WithFields( + logrus.Fields{ + "component": h.firmware.Component, + "update": h.firmware.FileName, + "version": h.firmware.Version, + }).Info("power cycling server - dry-run") + + return nil + } + + if h.action.HostPowerCycleInitiated { + h.logger.WithFields( + logrus.Fields{ + "component": h.firmware.Component, + "update": h.firmware.FileName, + "version": h.firmware.Version, + }).Info("server previously power cycled, not attempting another.") + + return nil + } + + h.logger.WithFields( + logrus.Fields{ + "component": h.firmware.Component, + "update": h.firmware.FileName, + "version": h.firmware.Version, + }).Info("power cycling server") + + f, err := os.OpenFile(rebootFlag, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return err + } + defer f.Close() + + h.logger.Infof("%s reboot flag created, waiting for host power cycle..", rebootFlag) + + // we must be able to publish a status at this point + h.action.HostPowerCycleInitiated = true + h.task.Status.Append("server powercycle flag set, waiting for powercycle") + if errPub := h.publisher.Publish(ctx, h.task); errPub != nil { + h.logger.WithError(errPub).Info("publish failure") + return errPub + } + + return model.ErrHostPowerCycleRequired +} diff --git a/internal/inband/server.go b/internal/inband/server.go new file mode 100644 index 00000000..cb696eb2 --- /dev/null +++ b/internal/inband/server.go @@ -0,0 +1,74 @@ +package inband + +import ( + "context" + + "github.com/bmc-toolbox/common" + "github.com/metal-toolbox/flasher/internal/device" + "github.com/metal-toolbox/ironlib" + iactions "github.com/metal-toolbox/ironlib/actions" + ironlibm "github.com/metal-toolbox/ironlib/model" + iutils "github.com/metal-toolbox/ironlib/utils" + "github.com/sirupsen/logrus" +) + +type server struct { + logger *logrus.Logger + dm iactions.DeviceManager +} + +// NewDeviceQueryor returns a server queryor that implements the DeviceQueryor interface +func NewDeviceQueryor(logger *logrus.Entry) device.InbandQueryor { + return &server{logger: logger.Logger} +} + +func (s *server) Inventory(ctx context.Context) (*common.Device, error) { + dm, err := ironlib.New(s.logger) + if err != nil { + return nil, err + } + + s.dm = dm + + disabledCollectors := []ironlibm.CollectorUtility{ + iutils.UefiFirmwareParserUtility, + iutils.UefiVariableCollectorUtility, + iutils.LsblkUtility, + } + + return dm.GetInventory(ctx, iactions.WithDisabledCollectorUtilities(disabledCollectors)) +} + +func (s *server) FirmwareInstall(ctx context.Context, component, vendor, model, _, updateFile string, force bool) error { + params := &ironlibm.UpdateOptions{ + ForceInstall: force, + Slug: component, + UpdateFile: updateFile, + Vendor: vendor, + Model: model, + } + + if s.dm == nil { + dm, err := ironlib.New(s.logger) + if err != nil { + return err + } + + s.dm = dm + } + + return s.dm.InstallUpdates(ctx, params) +} + +func (s *server) FirmwareInstallRequirements(ctx context.Context, component, vendor, model string) (*ironlibm.UpdateRequirements, error) { + if s.dm == nil { + dm, err := ironlib.New(s.logger) + if err != nil { + return nil, err + } + + s.dm = dm + } + + return s.dm.UpdateRequirements(ctx, component, vendor, model) +} diff --git a/internal/model/component.go b/internal/model/component.go index 6d88d2b4..418ca4b9 100644 --- a/internal/model/component.go +++ b/internal/model/component.go @@ -1,6 +1,7 @@ package model import ( + "slices" "strconv" "strings" @@ -10,6 +11,22 @@ import ( rtypes "github.com/metal-toolbox/rivets/types" ) +// These components are in most cases are present in a single package and from one vendor in a server, +// in comparison to drives, nics, raid controllers which could be in multiples and from different vendors +// for these 'singular' components we don't require to compare the exact model number for a firmware install. +func SingularComponent(slug string) bool { + singular := []string{ + common.SlugBIOS, + common.SlugBMC, + common.SlugCPLD, + common.SlugCPU, + common.SlugChassis, + common.SlugBackplaneExpander, + } + + return slices.Contains(singular, strings.ToUpper(slug)) +} + // ComponentConvertor provides methods to convert a common.Device to its Component equivalents. type ComponentConverter struct { deviceVendor string From 3fb7ccd506bfdd208e7178ec77b82ffb041f43d9 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 12:27:30 +0200 Subject: [PATCH 07/20] runner: implement support to resume Actions and Steps Enables flasher to pick up from the action/step where it left off before a power cycle when executing inband work. --- internal/model/action.go | 8 +- internal/model/step.go | 11 + internal/outofband/action_handlers.go | 2 +- internal/outofband/action_handlers_test.go | 4 +- internal/outofband/actions.go | 11 +- internal/runner/runner.go | 262 ++++++++++-- internal/runner/runner_test.go | 447 ++++++++++++++++++++- 7 files changed, 690 insertions(+), 55 deletions(-) diff --git a/internal/model/action.go b/internal/model/action.go index 54bace25..9402c128 100644 --- a/internal/model/action.go +++ b/internal/model/action.go @@ -5,6 +5,12 @@ import ( "strconv" rctypes "github.com/metal-toolbox/rivets/condition" + rtypes "github.com/metal-toolbox/rivets/types" +) + +const ( + // Each action may be tried upto these many times. + ActionMaxAttempts = 3 ) // Action holds attributes for each firmware to be installed @@ -60,7 +66,7 @@ type Action struct { // HostPowerCycleInitiated indicates when a power cycle has been initated for the host. HostPowerCycleInitiated bool `json:"host_power_cycle_initiated"` - //HostPowerOffInitiated indicates a power off was initated on the host. + // HostPowerOffInitiated indicates a power off was initated on the host. HostPowerOffInitiated bool `json:"host_power_off_initiated"` // HostPowerOffPreInstall is set when the firmware install provider indicates diff --git a/internal/model/step.go b/internal/model/step.go index 8ccbd626..a91399c1 100644 --- a/internal/model/step.go +++ b/internal/model/step.go @@ -7,6 +7,16 @@ import ( "github.com/pkg/errors" ) +const ( + // Each action may be tried upto these many times. + StepMaxAttempts = 2 +) + +var ( + ErrInstalledFirmwareEqual = errors.New("installed and expected firmware are equal, no action necessary") + ErrHostPowerCycleRequired = errors.New("host powercycle required") +) + // A Task comprises of Action(s) for each firmware to be installed, // An Action includes multiple steps to have firmware installed. @@ -28,6 +38,7 @@ type Step struct { Description string `json:"doc"` State rctypes.State `json:"state"` Status string `json:"status"` + Attempts int `json:"attempts"` } func (s *Step) SetState(state rctypes.State) { diff --git a/internal/outofband/action_handlers.go b/internal/outofband/action_handlers.go index 6ee15f7e..f8b915dd 100644 --- a/internal/outofband/action_handlers.go +++ b/internal/outofband/action_handlers.go @@ -261,7 +261,7 @@ func (h *handler) checkCurrentFirmware(ctx context.Context) error { "expected": h.firmware.Version, }).Info("Installed firmware version equals expected") - return ErrInstalledFirmwareEqual + return model.ErrInstalledFirmwareEqual } func (h *handler) downloadFirmware(ctx context.Context) error { diff --git a/internal/outofband/action_handlers_test.go b/internal/outofband/action_handlers_test.go index cfd04753..be12582b 100644 --- a/internal/outofband/action_handlers_test.go +++ b/internal/outofband/action_handlers_test.go @@ -24,7 +24,7 @@ func newTestActionCtx() *runner.ActionHandlerContext { TaskHandlerContext: &runner.TaskHandlerContext{ Task: &model.Task{ Parameters: &rctypes.FirmwareInstallTaskParameters{}, - Server: &rtypes.Server{}, + Server: &rtypes.Server{}, State: model.StateActive, }, Logger: logrus.NewEntry(logrus.New()), @@ -174,7 +174,7 @@ func TestCheckCurrentFirmware(t *testing.T) { dq.EXPECT().Inventory(mock.Anything).Times(1).Return(&dev, nil) err := handler.checkCurrentFirmware(ctx) require.Error(t, err) - require.ErrorIs(t, err, ErrInstalledFirmwareEqual) + require.ErrorIs(t, err, model.ErrInstalledFirmwareEqual) }) t.Run("installed version does not match", func(t *testing.T) { t.Parallel() diff --git a/internal/outofband/actions.go b/internal/outofband/actions.go index f5172a24..f00b6c33 100644 --- a/internal/outofband/actions.go +++ b/internal/outofband/actions.go @@ -23,7 +23,6 @@ const ( uploadFirmwareInitiateInstall model.StepName = "uploadFirmwareInitiateInstall" installUploadedFirmware model.StepName = "installUploadedFirmware" pollInstallStatus model.StepName = "pollInstallStatus" - resetDevice model.StepName = "resetDevice" ) const ( @@ -187,60 +186,70 @@ func (o *ActionHandler) definitions() model.Steps { Group: PowerState, Handler: o.handler.powerOnServer, Description: "Power on server - if its currently powered off.", + State: model.StatePending, }, { Name: powerOffServer, Group: PowerState, Handler: o.handler.powerOffServer, Description: "Powercycle Device, if this is the final firmware to be installed and the device was powered off earlier.", + State: model.StatePending, }, { Name: checkInstalledFirmware, Group: PreInstall, Handler: o.handler.checkCurrentFirmware, Description: "Check firmware currently installed on component", + State: model.StatePending, }, { Name: downloadFirmware, Group: PreInstall, Handler: o.handler.downloadFirmware, Description: "Download and verify firmware file checksum.", + State: model.StatePending, }, { Name: preInstallResetBMC, Group: PreInstall, Handler: o.handler.resetBMC, Description: "Powercycle BMC before installing any firmware - for better chances of success.", + State: model.StatePending, }, { Name: uploadFirmwareInitiateInstall, Group: Install, Handler: o.handler.uploadFirmwareInitiateInstall, Description: "Initiate firmware install for component.", + State: model.StatePending, }, { Name: installUploadedFirmware, Group: Install, Handler: o.handler.installUploadedFirmware, Description: "Initiate firmware install for firmware uploaded.", + State: model.StatePending, }, { Name: pollInstallStatus, Group: Install, Handler: o.handler.pollFirmwareTaskStatus, Description: "Poll BMC for firmware install status until its identified to be in a finalized state.", + State: model.StatePending, }, { Name: uploadFirmware, Group: Install, Handler: o.handler.uploadFirmware, Description: "Upload firmware to the device.", + State: model.StatePending, }, { Name: pollUploadStatus, Group: Install, Handler: o.handler.pollFirmwareTaskStatus, Description: "Poll device with exponential backoff for firmware upload status until it's confirmed.", + State: model.StatePending, }, } } diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 4834fe97..92adb6ce 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -3,10 +3,10 @@ package runner import ( "context" "fmt" + "os" "runtime/debug" "time" - "github.com/metal-toolbox/flasher/internal/device" "github.com/metal-toolbox/flasher/internal/metrics" "github.com/metal-toolbox/flasher/internal/model" "github.com/metal-toolbox/flasher/internal/store" @@ -118,9 +118,18 @@ func (r *Runner) RunTask(ctx context.Context, task *model.Task, handler TaskHand return nil }() // nolint:errcheck // nope - // no error returned - task.SetState(model.StateActive) - handler.Publish(ctx) + // incase this ever happens + if rctypes.StateIsComplete(task.State) { + task.Status.Append("Task already in final state, nothing to do here") + return taskSuccess() + } + + // task was resumed + if task.State != model.StateActive { + // no error returned + task.SetState(model.StateActive) + handler.Publish(ctx) + } // initialize, plan actions for _, f := range funcs { @@ -149,70 +158,249 @@ func (r *Runner) runActions(ctx context.Context, task *model.Task, handler TaskH registerActionMetric(startTS, action, string(state)) } - // helper func to log and publish step status - publish := func(state rctypes.State, action *model.Action, stepName model.StepName, logger *logrus.Entry) { - logger.WithField("step", stepName).Debug("running step") - task.Status.Append(fmt.Sprintf( - "[%s] install version: %s, method: %s, state: %s, step %s", - action.Firmware.Component, - action.Firmware.Version, - action.Firmware.InstallMethod, - state, - stepName, - )) - + finalize := func(state rctypes.State, startTS time.Time, action *model.Action, err error) error { + action.SetState(state) handler.Publish(ctx) + registerMetric(startTS, action, state) + + return err } // each action corresponds to a firmware to be installed for _, action := range task.Data.ActionsPlanned { startTS := time.Now() + // return on context cancellation + if ctx.Err() != nil { + return finalize(rctypes.Failed, startTS, action, ctx.Err()) + } + actionLogger := r.logger.WithFields(logrus.Fields{ "action": action.ID, "component": action.Firmware.Component, "fwversion": action.Firmware.Version, }) + resumeAction, err := r.resumeAction(ctx, action, handler) + if err != nil { + return finalize(rctypes.Failed, startTS, action, err) + } + + if !resumeAction { + continue + } + // fetch action attributes from task action.SetState(model.StateActive) + handler.Publish(ctx) - // return on context cancellation + // return + runNext, err := r.runActionSteps(ctx, task, action, handler, actionLogger) + if err != nil { + if errors.Is(err, model.ErrHostPowerCycleRequired) { + actionLogger.Info("host powercycle required to proceed, exiting") + os.Exit(0) + } + + return finalize(rctypes.Failed, startTS, action, err) + } + + if !runNext { + info := "no further actions required" + actionLogger.Info(info) + task.Status.Append(info) + + return finalize(rctypes.Succeeded, startTS, action, nil) + } + + // log and publish status + action.SetState(rctypes.Succeeded) + handler.Publish(ctx) + registerMetric(startTS, action, rctypes.Succeeded) + actionLogger.Info("action steps for component completed successfully") + } + + return nil +} + +// resumeAction returns true when the action can be resumed, when a false is returned with no error, the action is to be skipped. +func (r *Runner) resumeAction(ctx context.Context, action *model.Action, handler TaskHandler) (resume bool, err error) { + errResumeAction := errors.New("error in resuming action") + + actionLogger := r.logger.WithFields(logrus.Fields{ + "action": action.ID, + "component": action.Firmware.Component, + "fwversion": action.Firmware.Version, + }) + + switch action.State { + case model.StatePending: + actionLogger.Info("running action") + return true, nil + + case model.StateSucceeded: + actionLogger.WithField("state", action.State).Info("skipping previously successful action") + return false, nil + + case model.StateActive: + if action.Attempts > model.ActionMaxAttempts { + info := "reached maximum attempts on action" + actionLogger.WithFields( + logrus.Fields{"state": action.State, "attempts": action.Attempts}, + ).Warn(info) + + action.SetState(model.StateFailed) + handler.Publish(ctx) + + return false, errors.Wrap(errResumeAction, fmt.Sprintf("%s: %d", info, action.Attempts)) + } + + actionLogger.WithFields( + logrus.Fields{"state": action.State, "attempts": action.Attempts}, + ).Info("resuming active action..") + + action.Attempts++ + return true, nil + + case model.StateFailed: + handler.Publish(ctx) + + return false, errors.Wrap(errResumeAction, "action previously failed, will not be re-attempted") + + default: + return false, errors.Wrap(errResumeAction, "unmanaged state: "+string(action.State)) + } +} + +func (r *Runner) runActionSteps(ctx context.Context, task *model.Task, action *model.Action, handler TaskHandler, logger *logrus.Entry) (proceed bool, err error) { + // helper func to log and publish step status + publish := func(state rctypes.State, action *model.Action, step *model.Step, logger *logrus.Entry) { + logger.WithField("step", step.Name).Debug("running step") + step.SetState(state) + method := string(model.RunOutofband) + if action.Firmware.InstallInband { + method = string(model.RunInband) + } + + task.Status.Append(fmt.Sprintf( + "[%s] install %s version: %s, state: %s, step %s", + action.Firmware.Component, + method, + action.Firmware.Version, + state, + step.Name, + )) + + handler.Publish(ctx) + } + + for _, step := range action.Steps { if ctx.Err() != nil { - registerMetric(startTS, action, rctypes.Failed) - return ctx.Err() + return false, ctx.Err() } - actionLogger.Info("running action steps for firmware install") - for _, step := range action.Steps { - publish(model.StateActive, action, step.Name, actionLogger) + resume, err := r.resumeStep(step, logger) + if err != nil { + publish(model.StateFailed, action, step, logger) + return false, err + } + + if !resume { + continue + } + + publish(model.StateActive, action, step, logger) - // run step - if err := step.Handler(ctx); err != nil { - action.SetState(model.StateFailed) - publish(model.StateFailed, action, step.Name, actionLogger) + if step.Handler == nil { + publish(model.StateFailed, action, step, logger) + // nolint:goerr113 // for this case, its preferable to have the error be defined within its context of use + return false, fmt.Errorf( + "error while running step=%s to install firmware on component=%s, handler was nil", + step.Name, + action.Firmware.Component, + ) + } - registerMetric(startTS, action, rctypes.Failed) - return errors.Wrap( - err, + // run step + if err := step.Handler(ctx); err != nil { + // installed firmware equals expected + if errors.Is(err, model.ErrInstalledFirmwareEqual) { + task.Status.Append( fmt.Sprintf( - "error while running step=%s to install firmware on component=%s", - step.Name, + "[%s] %s", action.Firmware.Component, + "Installed and expected firmware are equal", ), ) + + publish(model.StateSucceeded, action, step, logger) + + // no further actions + return false, nil + } + + // bubble this error up + if errors.Is(err, model.ErrHostPowerCycleRequired) { + return false, err } - // log and publish status - action.SetState(model.StateSucceeded) - publish(model.StateSucceeded, action, step.Name, actionLogger) + publish(model.StateFailed, action, step, logger) + return false, errors.Wrap( + err, + fmt.Sprintf( + "error while running step=%s to install firmware on component=%s", + step.Name, + action.Firmware.Component, + ), + ) } - registerMetric(startTS, action, rctypes.Succeeded) - actionLogger.Info("action steps for component completed successfully") + // publish step status + publish(model.StateSucceeded, action, step, logger) } - return nil + return true, nil +} + +// resumeStep returns true when the step can be resumed, when a false is returned with no error, the step is to be skipped. +func (r *Runner) resumeStep(step *model.Step, logger *logrus.Entry) (resume bool, err error) { + errResumeStep := errors.New("error in resuming step") + + le := logger.WithFields( + logrus.Fields{ + "stepName": step.Name, + "state": step.State, + "attempts": step.Attempts, + }, + ) + + switch step.State { + case model.StatePending: + return true, nil + + case model.StateSucceeded: + le.Info("skipping previously successful step") + return false, nil + + case model.StateActive: + if step.Attempts > model.StepMaxAttempts { + info := "reached maximum attempts on step" + le.Warn(info) + step.SetState(model.StateFailed) + return false, errors.Wrap(errResumeStep, fmt.Sprintf("%s: %d", info, step.Attempts)) + } + + le.Info("resuming active step..") + + step.Attempts++ + return true, nil + + case model.StateFailed: + return false, errors.Wrap(errResumeStep, "step previously failed, will not be re-attempted") + + default: + return false, errors.Wrap(errResumeStep, "unmanaged state: "+string(step.State)) + } } // conditionalFault is invoked before each runner method to induce a fault if specified diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index 1e544456..5703f392 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/metal-toolbox/flasher/internal/model" + rctypes "github.com/metal-toolbox/rivets/condition" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -14,55 +15,475 @@ import ( func TestRunTask(t *testing.T) { tests := []struct { name string + task *model.Task mockSetup func(*MockTaskHandler) - expectedState string + expectedState rctypes.State expectedError error }{ { - name: "Successful execution", + name: "Successful execution - new task", + task: &model.Task{ + State: model.StatePending, + Data: &model.TaskData{}, + }, mockSetup: func(m *MockTaskHandler) { m.On("Initialize", mock.Anything).Return(nil) m.On("Query", mock.Anything).Return(nil) m.On("PlanActions", mock.Anything).Return(nil) + m.On("Publish", mock.Anything).Return(nil) m.On("OnSuccess", mock.Anything, mock.Anything).Once() - m.On("Publish", mock.Anything).Maybe() }, - expectedState: string(model.StateSucceeded), + expectedState: model.StateSucceeded, expectedError: nil, }, { name: "Failure during Initialize", + task: &model.Task{ + State: model.StatePending, + Data: &model.TaskData{}, + }, mockSetup: func(m *MockTaskHandler) { m.On("Initialize", mock.Anything).Return(errors.New("Initialize failed")) + m.On("Publish", mock.Anything).Return(nil) m.On("OnFailure", mock.Anything, mock.Anything).Once() - m.On("Publish", mock.Anything, mock.Anything).Twice() }, - expectedState: string(model.StateFailed), + expectedState: model.StateFailed, expectedError: errors.New("Initialize failed"), }, + { + name: "Resume active task", + task: &model.Task{ + State: model.StateActive, + Data: &model.TaskData{}, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Initialize", mock.Anything).Return(nil) + m.On("Query", mock.Anything).Return(nil) + m.On("PlanActions", mock.Anything).Return(nil) + m.On("Publish", mock.Anything).Return(nil) + m.On("OnSuccess", mock.Anything, mock.Anything).Once() + }, + expectedState: model.StateSucceeded, + expectedError: nil, + }, + { + name: "Task already in final state", + task: &model.Task{ + State: model.StateSucceeded, + Data: &model.TaskData{}, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Publish", mock.Anything).Return(nil) + m.On("OnSuccess", mock.Anything, mock.Anything).Once() + }, + expectedState: model.StateSucceeded, + expectedError: nil, + }, + { + name: "Failure during runActions", + task: &model.Task{ + State: model.StatePending, + Data: &model.TaskData{ + ActionsPlanned: []*model.Action{ + { + ID: "action1", + State: model.StatePending, + Steps: []*model.Step{ + { + Name: "step1", + State: model.StatePending, + Handler: func(context.Context) error { return errors.New("Step failed") }, + }, + }, + }, + }, + }, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Initialize", mock.Anything).Return(nil) + m.On("Query", mock.Anything).Return(nil) + m.On("PlanActions", mock.Anything).Return(nil) + m.On("Publish", mock.Anything).Return(nil) + m.On("OnFailure", mock.Anything, mock.Anything).Once() + }, + expectedState: model.StateFailed, + expectedError: errors.New("error while running step=step1 to install firmware on component=: Step failed"), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockHandler := new(MockTaskHandler) - tt.mockSetup(mockHandler) // Set up the mock expectations + tt.mockSetup(mockHandler) r := New(logrus.NewEntry(logrus.New())) - task := &model.Task{ - Data: &model.TaskData{}, + err := r.RunTask(context.Background(), tt.task, mockHandler) + + assert.Equal(t, tt.expectedState, tt.task.State) + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) } - err := r.RunTask(context.Background(), task, mockHandler) + mockHandler.AssertExpectations(t) + }) + } +} + +func TestRunActions(t *testing.T) { + tests := []struct { + name string + task *model.Task + mockSetup func(*MockTaskHandler) + expectedError error + expectedActionState rctypes.State + }{ + { + name: "Successful execution of all actions", + task: &model.Task{ + Data: &model.TaskData{ + ActionsPlanned: []*model.Action{ + { + ID: "action1", + Firmware: model.Firmware{ + Component: "component1", + Version: "1.0", + }, + State: model.StatePending, + Steps: []*model.Step{ + { + Name: "step1", + State: model.StatePending, + Handler: func(context.Context) error { return nil }, + }, + { + Name: "step2", + State: model.StatePending, + Handler: func(context.Context) error { return nil }, + }, + }, + }, + }, + }, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Publish", mock.Anything).Return(nil) + }, + expectedError: nil, + expectedActionState: rctypes.Succeeded, + }, + { + name: "Action fails on second step", + task: &model.Task{ + Data: &model.TaskData{ + ActionsPlanned: []*model.Action{ + { + ID: "action1", + Firmware: model.Firmware{ + Component: "component1", + Version: "1.0", + }, + State: model.StatePending, + Steps: []*model.Step{ + { + Name: "step1", + State: model.StatePending, + Handler: func(context.Context) error { return nil }, + }, + { + Name: "step2", + State: model.StatePending, + Handler: func(context.Context) error { return errors.New("step failed") }, + }, + }, + }, + }, + }, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Publish", mock.Anything).Return(nil) + }, + expectedError: errors.New("error while running step=step2 to install firmware on component=component1: step failed"), + expectedActionState: rctypes.Failed, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHandler := new(MockTaskHandler) + tt.mockSetup(mockHandler) + + r := New(logrus.NewEntry(logrus.New())) + err := r.runActions(context.Background(), tt.task, mockHandler) - // Assert task state and error expectations - assert.Equal(t, tt.expectedState, string(task.State)) if tt.expectedError != nil { assert.EqualError(t, err, tt.expectedError.Error()) } else { assert.NoError(t, err) } - mockHandler.AssertExpectations(t) + assert.Equal(t, tt.expectedActionState, tt.task.Data.ActionsPlanned[0].State) + }) + } +} + +func TestResumeAction(t *testing.T) { + tests := []struct { + name string + action *model.Action + mockSetup func(*MockTaskHandler) + expectedResume bool + expectedError error + }{ + { + name: "Run pending action", + action: &model.Action{ + State: model.StatePending, + }, + mockSetup: func(m *MockTaskHandler) {}, + expectedResume: true, + expectedError: nil, + }, + { + name: "Skip succeeded action", + action: &model.Action{ + State: model.StateSucceeded, + }, + mockSetup: func(m *MockTaskHandler) {}, + expectedResume: false, + expectedError: nil, + }, + { + name: "Resume active action", + action: &model.Action{ + State: model.StateActive, + Attempts: 1, + }, + mockSetup: func(m *MockTaskHandler) {}, + expectedResume: true, + expectedError: nil, + }, + { + name: "Fail action with max attempts", + action: &model.Action{ + State: model.StateActive, + Attempts: model.ActionMaxAttempts + 1, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Publish", mock.Anything).Return(nil) + }, + expectedResume: false, + expectedError: errors.New("reached maximum attempts on action: 4: error in resuming action"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHandler := new(MockTaskHandler) + tt.mockSetup(mockHandler) + + r := New(logrus.NewEntry(logrus.New())) + resume, err := r.resumeAction(context.Background(), tt.action, mockHandler) + + assert.Equal(t, tt.expectedResume, resume) + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + } + + }) + } +} + +func TestRunActionSteps(t *testing.T) { + tests := []struct { + name string + task *model.Task + action *model.Action + mockSetup func(*MockTaskHandler) + expectedProceed bool + expectedError error + }{ + { + name: "All steps succeed", + task: &model.Task{Data: &model.TaskData{}}, + action: &model.Action{ + Firmware: model.Firmware{Component: "test", Version: "1.0"}, + Steps: []*model.Step{ + { + Name: "step1", + State: model.StatePending, + Handler: func(context.Context) error { return nil }, + }, + { + Name: "step2", + State: model.StatePending, + Handler: func(context.Context) error { return nil }, + }, + }, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Publish", mock.Anything).Return(nil) + }, + expectedProceed: true, + expectedError: nil, + }, + { + name: "Step fails", + task: &model.Task{Data: &model.TaskData{}}, + action: &model.Action{ + Firmware: model.Firmware{Component: "test", Version: "1.0"}, + Steps: []*model.Step{ + { + Name: "step1", + State: model.StatePending, + Handler: func(context.Context) error { return errors.New("step failed") }, + }, + }, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Publish", mock.Anything).Return(nil) + }, + expectedProceed: false, + expectedError: errors.New("error while running step=step1 to install firmware on component=test: step failed"), + }, + { + name: "Installed firmware equals expected", + task: &model.Task{Data: &model.TaskData{}}, + action: &model.Action{ + Firmware: model.Firmware{Component: "test", Version: "1.0"}, + Steps: []*model.Step{ + { + Name: "step1", + State: model.StatePending, + Handler: func(context.Context) error { return model.ErrInstalledFirmwareEqual }, + }, + }, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Publish", mock.Anything).Return(nil) + }, + expectedProceed: false, + expectedError: nil, + }, + { + name: "Host power cycle required", + task: &model.Task{Data: &model.TaskData{}}, + action: &model.Action{ + Firmware: model.Firmware{Component: "test", Version: "1.0"}, + Steps: []*model.Step{ + { + Name: "step1", + State: model.StatePending, + Handler: func(context.Context) error { return model.ErrHostPowerCycleRequired }, + }, + }, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Publish", mock.Anything).Return(nil) + }, + expectedProceed: false, + expectedError: model.ErrHostPowerCycleRequired, + }, + { + name: "Nil step handler", + task: &model.Task{Data: &model.TaskData{}}, + action: &model.Action{ + Firmware: model.Firmware{Component: "test", Version: "1.0"}, + Steps: []*model.Step{ + { + Name: "borky step2", + State: model.StatePending, + Handler: nil, + }, + }, + }, + mockSetup: func(m *MockTaskHandler) { + m.On("Publish", mock.Anything).Return(nil) + }, + expectedProceed: false, + expectedError: errors.New("error while running step=borky step2 to install firmware on component=test, handler was nil"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockHandler := new(MockTaskHandler) + tt.mockSetup(mockHandler) + + r := New(logrus.NewEntry(logrus.New())) + proceed, err := r.runActionSteps(context.Background(), tt.task, tt.action, mockHandler, r.logger) + + assert.Equal(t, tt.expectedProceed, proceed) + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestResumeStep(t *testing.T) { + tests := []struct { + name string + step *model.Step + expectedResume bool + expectedError error + }{ + { + name: "Resume pending step", + step: &model.Step{ + Name: "step1", + State: model.StatePending, + }, + expectedResume: true, + expectedError: nil, + }, + { + name: "Skip succeeded step", + step: &model.Step{ + Name: "step1", + State: model.StateSucceeded, + }, + expectedResume: false, + expectedError: nil, + }, + { + name: "Resume active step", + step: &model.Step{ + Name: "step1", + State: model.StateActive, + Attempts: 1, + }, + expectedResume: true, + expectedError: nil, + }, + { + name: "Fail step with max attempts", + step: &model.Step{ + Name: "step1", + State: model.StateActive, + Attempts: model.StepMaxAttempts + 1, + }, + expectedResume: false, + expectedError: errors.New("reached maximum attempts on step: 3: error in resuming step"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := New(logrus.NewEntry(logrus.New())) + resume, err := r.resumeStep(tt.step, r.logger) + + assert.Equal(t, tt.expectedResume, resume) + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + } }) } } From 4df09c4ee66fac304792d83e8a51e5f84f503a58 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 14:40:59 +0200 Subject: [PATCH 08/20] worker/task_handler: implement support for both inband and outofband installs --- internal/worker/inband.go | 112 ++++++++++++++++ internal/worker/task_handler.go | 185 +++++++++++++++++++-------- internal/worker/task_handler_test.go | 153 +++++++++++++++++----- 3 files changed, 371 insertions(+), 79 deletions(-) create mode 100644 internal/worker/inband.go diff --git a/internal/worker/inband.go b/internal/worker/inband.go new file mode 100644 index 00000000..1abb8209 --- /dev/null +++ b/internal/worker/inband.go @@ -0,0 +1,112 @@ +package worker + +import ( + "context" + + "github.com/metal-toolbox/ctrl" + "github.com/metal-toolbox/flasher/internal/model" + "github.com/metal-toolbox/flasher/internal/runner" + "github.com/metal-toolbox/flasher/internal/store" + "github.com/metal-toolbox/flasher/internal/version" + rctypes "github.com/metal-toolbox/rivets/condition" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "go.opentelemetry.io/otel" +) + +// implements the controller.TaskHandler interface +type InbandConditionTaskHandler struct { + store store.Repository + logger *logrus.Logger + facilityCode string + dryrun bool + faultInjection bool +} + +// RunInband initializes the inband installer +func RunInband( + ctx context.Context, + dryrun, + faultInjection bool, + facilityCode string, + repository store.Repository, + nc *ctrl.HTTPController, + logger *logrus.Logger, +) { + ctx, span := otel.Tracer(pkgName).Start( + ctx, + "Run", + ) + defer span.End() + + v := version.Current() + logger.WithFields( + logrus.Fields{ + "version": v.AppVersion, + "commit": v.GitCommit, + "branch": v.GitBranch, + "dry-run": dryrun, + "faultInjection": faultInjection, + }, + ).Info("flasher inband installer running") + + inbHandler := InbandConditionTaskHandler{ + store: repository, + logger: logger, + dryrun: dryrun, + faultInjection: faultInjection, + facilityCode: facilityCode, + } + + if err := nc.Run(ctx, &inbHandler); err != nil { + logger.Fatal(err) + } +} + +// Handle implements the controller.ConditionHandler interface +func (h *InbandConditionTaskHandler) HandleTask( + ctx context.Context, + genericTask *rctypes.Task[any, any], + publisher ctrl.Publisher, +) error { + if genericTask == nil { + return errors.Wrap(errInitTask, "expected a generic Task object, got nil") + } + + task, err := model.CopyAsFwInstallTask(genericTask) + if err != nil { + return errors.Wrap(errInitTask, err.Error()) + } + + // prepare logger + l := logrus.New() + l.Formatter = h.logger.Formatter + l.Level = h.logger.Level + hLogger := l.WithFields( + logrus.Fields{ + "conditionID": genericTask.ID.String(), + "serverID": task.Server.ID, + }, + ) + + // init handler + handler := newHandler( + model.RunInband, + task, + h.store, + model.NewTaskStatusPublisher(hLogger, publisher), + hLogger, + ) + + // init runner + r := runner.New(hLogger) + + hLogger.WithField("mode", model.RunInband).Info("running task for device") + if err := r.RunTask(ctx, task, handler); err != nil { + hLogger.WithError(err).Error("task for device failed") + return err + } + + hLogger.Info("task for device completed") + return nil +} diff --git a/internal/worker/task_handler.go b/internal/worker/task_handler.go index d42d7686..1312600d 100644 --- a/internal/worker/task_handler.go +++ b/internal/worker/task_handler.go @@ -7,6 +7,8 @@ import ( "strings" "github.com/bmc-toolbox/common" + "github.com/metal-toolbox/flasher/internal/device" + "github.com/metal-toolbox/flasher/internal/inband" "github.com/metal-toolbox/flasher/internal/model" "github.com/metal-toolbox/flasher/internal/outofband" "github.com/metal-toolbox/flasher/internal/runner" @@ -50,12 +52,12 @@ func newHandler( func (t *handler) Initialize(ctx context.Context) error { if t.DeviceQueryor == nil { - // TODO(joel): DeviceQueryor is to be instantiated based on the method(s) for the firmwares to be installed - // if its a mix of inband, out of band firmware to be installed, then both are to be queried and - // so this DeviceQueryor would have to be extended - // - // For this to work with both inband and out of band, the firmware set data should include the install method. - t.DeviceQueryor = outofband.NewDeviceQueryor(ctx, t.Task.Server, t.Logger) + switch t.mode { + case model.RunInband: + t.DeviceQueryor = inband.NewDeviceQueryor(t.Logger) + case model.RunOutofband: + t.DeviceQueryor = outofband.NewDeviceQueryor(ctx, t.Task.Server, t.Logger) + } } return nil @@ -64,19 +66,19 @@ func (t *handler) Initialize(ctx context.Context) error { func (t *handler) Query(ctx context.Context) error { t.Logger.Debug("run query step") - t.Task.Status.Append("connecting to device BMC") - t.Publish(ctx) - - if err := t.DeviceQueryor.Open(ctx); err != nil { - return err - } - - t.Task.Status.Append("collecting inventory from device BMC") - t.Publish(ctx) - - deviceCommon, err := t.DeviceQueryor.Inventory(ctx) - if err != nil { - return errors.Wrap(errTaskQueryInventory, err.Error()) + var err error + var deviceCommon *common.Device + switch t.mode { + case model.RunInband: + deviceCommon, err = t.inventoryInband(ctx) + if err != nil { + return err + } + case model.RunOutofband: + deviceCommon, err = t.inventoryOutofband(ctx) + if err != nil { + return err + } } if t.Task.Server.Vendor == "" { @@ -102,6 +104,40 @@ func (t *handler) Query(ctx context.Context) error { return errors.Wrap(errTaskQueryInventory, "failed to query device component inventory") } +func (t handler) inventoryOutofband(ctx context.Context) (*common.Device, error) { + if err := t.DeviceQueryor.(device.OutofbandQueryor).Open(ctx); err != nil { + return nil, err + } + + t.Task.Status.Append("connecting to device BMC") + t.Publish(ctx) + if err := t.DeviceQueryor.(device.OutofbandQueryor).Open(ctx); err != nil { + return nil, err + } + + t.Task.Status.Append("collecting inventory from device BMC") + t.Publish(ctx) + + deviceCommon, err := t.DeviceQueryor.(device.OutofbandQueryor).Inventory(ctx) + if err != nil { + return nil, errors.Wrap(errTaskQueryInventory, err.Error()) + } + + return deviceCommon, nil +} + +func (t handler) inventoryInband(ctx context.Context) (*common.Device, error) { + t.Task.Status.Append("collecting inventory from server") + t.Publish(ctx) + + deviceCommon, err := t.DeviceQueryor.(device.InbandQueryor).Inventory(ctx) + if err != nil { + return nil, errors.Wrap(errTaskQueryInventory, err.Error()) + } + + return deviceCommon, nil +} + func (t *handler) PlanActions(ctx context.Context) error { switch t.Task.Data.FirmwarePlanMethod { case model.FromFirmwareSet: @@ -125,38 +161,46 @@ func (t *handler) planFromFirmwareSet(ctx context.Context) error { return errors.Wrap(errTaskPlanActions, "planFromFirmwareSet(): firmware set lacks any members") } - // plan actions based and update task action list - t.Task.Data.ActionsPlanned, err = t.planInstall(ctx, applicable) + actions, err := t.planInstallActions(ctx, applicable) if err != nil { return err } + t.Task.Data.ActionsPlanned = append(t.Task.Data.ActionsPlanned, actions...) + return nil } // planInstall sets up the firmware install plan // // This returns a list of actions to added to the task and a list of action state machines for those actions. -func (t *handler) planInstall(ctx context.Context, firmwares []*model.Firmware) (model.Actions, error) { - actions := model.Actions{} +func (t *handler) planInstallActions(ctx context.Context, firmwares []*model.Firmware) (model.Actions, error) { + toInstall := []*model.Firmware{} + + for _, fw := range firmwares { + if t.mode == model.RunOutofband && !fw.InstallInband { + toInstall = append(toInstall, fw) + } + + if t.mode == model.RunInband && fw.InstallInband { + toInstall = append(toInstall, fw) + } + } t.Logger.WithFields(logrus.Fields{ "condition.id": t.Task.ID, - "requested.firmware.count": fmt.Sprintf("%d", len(firmwares)), + "requested.firmware.count": fmt.Sprintf("%d", len(toInstall)), }).Debug("checking against current inventory") - toInstall := firmwares - // purge any firmware that are already installed if !t.Task.Parameters.ForceInstall { - toInstall = t.removeFirmwareAlreadyAtDesiredVersion(firmwares) + toInstall = t.removeFirmwareAlreadyAtDesiredVersion(toInstall) } if len(toInstall) == 0 { - info := "no actions required for this task" - + info := fmt.Sprintf("no %s firmware installs required", t.mode) + t.Task.Status.Append(info) t.Publish(ctx) - t.Logger.Info(info) return nil, nil } @@ -164,8 +208,27 @@ func (t *handler) planInstall(ctx context.Context, firmwares []*model.Firmware) // sort firmware in order of install t.sortFirmwareByInstallOrder(toInstall) + actions := model.Actions{} // each firmware applicable results in an ActionPlan and an Action for idx, firmware := range toInstall { + var actionHander runner.ActionHandler + + if t.mode == model.RunOutofband { + if firmware.InstallInband { + continue + } + + actionHander = &outofband.ActionHandler{} + } + + if t.mode == model.RunInband { + if !firmware.InstallInband { + continue + } + + actionHander = &inband.ActionHandler{} + } + actionCtx := &runner.ActionHandlerContext{ TaskHandlerContext: t.TaskHandlerContext, Firmware: firmware, @@ -173,33 +236,27 @@ func (t *handler) planInstall(ctx context.Context, firmwares []*model.Firmware) Last: (idx == len(toInstall)-1), } - // rig install method until field is added into firmware table - firmware.InstallMethod = model.InstallMethodOutofband - - var aHandler runner.ActionHandler - - switch firmware.InstallMethod { - case model.InstallMethodInband: - return nil, errors.Wrap(errTaskPlanActions, "inband install method not supported") - case model.InstallMethodOutofband: - aHandler = &outofband.ActionHandler{} - default: - return nil, errors.Wrap( - errTaskPlanActions, - "unsupported install method: "+string(firmware.InstallMethod)) - } - - action, err := aHandler.ComposeAction(ctx, actionCtx) + action, err := actionHander.ComposeAction(ctx, actionCtx) if err != nil { return nil, errors.Wrap(errTaskPlanActions, err.Error()) } action.SetID(t.Task.ID.String(), firmware.Component, idx) action.SetState(model.StatePending) - actions = append(actions, action) } + var info string + if len(actions) > 0 { + info = fmt.Sprintf("planned firmware installs, method: %s, count: %d", t.mode, len(actions)) + } else { + info = fmt.Sprintf("no %s firmware installs required", t.mode) + } + + t.Task.Status.Append(info) + t.Publish(ctx) + t.Logger.Info(info) + return actions, nil } @@ -215,6 +272,15 @@ func (t *handler) sortFirmwareByInstallOrder(firmwares []*model.Firmware) { func (t *handler) removeFirmwareAlreadyAtDesiredVersion(fws []*model.Firmware) []*model.Firmware { var toInstall []*model.Firmware + // TODO: The current invMap key is set to the component name, + // This means if theres multiple Drives of different vendors only the last one in the + // component list will be included. Consider a different approach where the key consists + // of the name, model. + // + // key := func(cmpName, cmpModel string) string { + // return fmt.Sprintf("%s.%s", strings.ToLower(cmpName), strings.ToLower(cmpModel)) + // } + invMap := make(map[string]string) for _, cmp := range t.Task.Server.Components { invMap[strings.ToLower(cmp.Name)] = cmp.Firmware.Installed @@ -235,6 +301,25 @@ func (t *handler) removeFirmwareAlreadyAtDesiredVersion(fws []*model.Firmware) [ for _, fw := range fws { currentVersion, ok := invMap[strings.ToLower(fw.Component)] + // skip install if current firmware version was not identified + if currentVersion == "" && !t.Task.Parameters.ForceInstall { + info := "Current firmware version returned empty, skipped install, use force to override" + t.Task.Status.Append( + fmtCause( + fw.Component, + info, + currentVersion, + fw.Version, + ), + ) + + t.Logger.WithFields(logrus.Fields{ + "component": fw.Component, + }).Warn() + + continue + } + switch { case !ok: cause := "component not found in inventory" @@ -272,7 +357,7 @@ func (t *handler) removeFirmwareAlreadyAtDesiredVersion(fws []*model.Firmware) [ } func (t *handler) OnSuccess(ctx context.Context, _ *model.Task) { - if t.DeviceQueryor == nil { + if t.mode == model.RunInband || t.DeviceQueryor == nil { return } @@ -282,7 +367,7 @@ func (t *handler) OnSuccess(ctx context.Context, _ *model.Task) { } func (t *handler) OnFailure(ctx context.Context, _ *model.Task) { - if t.DeviceQueryor == nil { + if t.mode == model.RunInband || t.DeviceQueryor == nil { return } diff --git a/internal/worker/task_handler_test.go b/internal/worker/task_handler_test.go index 93dd0274..3b4eaa80 100644 --- a/internal/worker/task_handler_test.go +++ b/internal/worker/task_handler_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/google/uuid" + "github.com/metal-toolbox/ctrl" "github.com/metal-toolbox/flasher/internal/device" "github.com/metal-toolbox/flasher/internal/model" "github.com/metal-toolbox/flasher/internal/runner" @@ -16,6 +17,7 @@ import ( bconsts "github.com/bmc-toolbox/bmclib/v2/constants" "github.com/bmc-toolbox/common" + ironlibm "github.com/metal-toolbox/ironlib/model" rctypes "github.com/metal-toolbox/rivets/condition" rtypes "github.com/metal-toolbox/rivets/types" ) @@ -129,6 +131,7 @@ func TestRemoveFirmwareAlreadyAtDesiredVersion(t *testing.T) { }, }, }, + Parameters: &rctypes.FirmwareInstallTaskParameters{}, }, } @@ -150,44 +153,44 @@ func TestRemoveFirmwareAlreadyAtDesiredVersion(t *testing.T) { require.Equal(t, expected[0], got[0]) } -func TestPlanInstall(t *testing.T) { +func TestPlanInstall_Outofband(t *testing.T) { t.Parallel() fwSet := []*model.Firmware{ { - Version: "5.10.00.00", - URL: "https://downloads.dell.com/FOLDER06303849M/1/BMC_5_10_00_00.EXE", - FileName: "BMC_5_10_00_00.EXE", - Models: []string{"r6515"}, - Checksum: "4189d3cb123a781d09a4f568bb686b23c6d8e6b82038eba8222b91c380a25281", - Component: "bmc", - InstallMethod: model.InstallMethodOutofband, + Version: "5.10.00.00", + URL: "https://downloads.dell.com/FOLDER06303849M/1/BMC_5_10_00_00.EXE", + FileName: "BMC_5_10_00_00.EXE", + Models: []string{"r6515"}, + Checksum: "4189d3cb123a781d09a4f568bb686b23c6d8e6b82038eba8222b91c380a25281", + Component: "bmc", }, { - Version: "2.19.6", - URL: "https://dl.dell.com/FOLDER08105057M/1/BIOS_C4FT0_WN64_2.19.6.EXE", - FileName: "BIOS_C4FT0_WN64_2.19.6.EXE", - Models: []string{"r6515"}, - Checksum: "1ddcb3c3d0fc5925ef03a3dde768e9e245c579039dd958fc0f3a9c6368b6c5f4", - Component: "bios", - InstallMethod: model.InstallMethodOutofband, + Version: "2.19.6", + URL: "https://dl.dell.com/FOLDER08105057M/1/BIOS_C4FT0_WN64_2.19.6.EXE", + FileName: "BIOS_C4FT0_WN64_2.19.6.EXE", + Models: []string{"r6515"}, + Checksum: "1ddcb3c3d0fc5925ef03a3dde768e9e245c579039dd958fc0f3a9c6368b6c5f4", + Component: "bios", }, { - Version: "1.2.3", - URL: "https://foo/BLOB.exx", - FileName: "NIC_1.2.3.EXE", - Models: []string{"r6515"}, - Checksum: "1ddcb3c3d0fc5925ef03a3dde768e9e245c579039dd958fc0f3a9c63aaaaaaa", - Component: "nic", - InstallMethod: model.InstallMethodOutofband, + Version: "1.2.3", + URL: "https://foo/BLOB.exx", + FileName: "NIC_1.2.3.EXE", + Models: []string{"r6515"}, + Checksum: "1ddcb3c3d0fc5925ef03a3dde768e9e245c579039dd958fc0f3a9c63aaaaaaa", + Component: "nic", }, } - dq := new(device.MockQueryor) + logger := logrus.NewEntry(logrus.New()) + dq := new(device.MockOutofbandQueryor) + publisher := ctrl.NewMockPublisher(t) serverID := uuid.MustParse("fa125199-e9dd-47d4-8667-ce1d26f58c4a") taskID := uuid.MustParse("05c3296d-be5d-473a-b90c-4ce66cfdec65") taskHandlerCtx := &runner.TaskHandlerContext{ - Logger: logrus.NewEntry(logrus.New()), + Logger: logger, + Publisher: model.NewTaskStatusPublisher(logger, publisher), Task: &model.Task{ ID: taskID, WorkerID: registry.GetID("test-app").String(), @@ -224,8 +227,11 @@ func TestPlanInstall(t *testing.T) { bconsts.FirmwareInstallStepInstallStatus, }, nil) + publisher.EXPECT(). + Publish(mock.Anything, mock.Anything, mock.Anything).Return(nil) + h := handler{mode: model.RunOutofband, TaskHandlerContext: taskHandlerCtx} - actions, err := h.planInstall(context.Background(), fwSet) + actions, err := h.planInstallActions(context.Background(), fwSet) require.NoError(t, err, "no errors returned") require.Equal(t, 2, len(actions), "expect two actions to be performed") require.True(t, actions[0].BMCResetPreInstall, "expect BMCResetPreInstall is true on the first action") @@ -237,7 +243,7 @@ func TestPlanInstall(t *testing.T) { require.Equal(t, "nic", actions[1].Firmware.Component, "expect nic component action") } -func TestPlanInstall2(t *testing.T) { +func TestPlanInstall2_Outofband(t *testing.T) { t.Parallel() fwSet := []*model.Firmware{ { @@ -266,12 +272,15 @@ func TestPlanInstall2(t *testing.T) { }, } - dq := new(device.MockQueryor) + logger := logrus.NewEntry(logrus.New()) + dq := new(device.MockOutofbandQueryor) + publisher := ctrl.NewMockPublisher(t) serverID := uuid.MustParse("fa125199-e9dd-47d4-8667-ce1d26f58c4a") taskID := uuid.MustParse("05c3296d-be5d-473a-b90c-4ce66cfdec65") taskHandlerCtx := &runner.TaskHandlerContext{ - Logger: logrus.NewEntry(logrus.New()), + Logger: logger, + Publisher: model.NewTaskStatusPublisher(logger, publisher), Task: &model.Task{ ID: taskID, WorkerID: registry.GetID("test-app").String(), @@ -300,6 +309,9 @@ func TestPlanInstall2(t *testing.T) { DeviceQueryor: dq, } + publisher.EXPECT(). + Publish(mock.Anything, mock.Anything, mock.Anything).Return(nil) + dq.EXPECT().FirmwareInstallSteps(mock.Anything, mock.Anything). Times(3). Return([]bconsts.FirmwareInstallStep{ @@ -309,7 +321,7 @@ func TestPlanInstall2(t *testing.T) { }, nil) h := handler{mode: model.RunOutofband, TaskHandlerContext: taskHandlerCtx} - actions, err := h.planInstall(context.Background(), fwSet) + actions, err := h.planInstallActions(context.Background(), fwSet) require.NoError(t, err, "no errors returned") require.Equal(t, 3, len(actions), "expect three actions to be performed") require.False(t, actions[0].BMCResetPreInstall, "expect BMCResetPreInstall is false on the first action") @@ -326,3 +338,86 @@ func TestPlanInstall2(t *testing.T) { require.Equal(t, "bios", actions[1].Firmware.Component, "expect bios component action") require.Equal(t, "nic", actions[2].Firmware.Component, "expect nic component action") } + +func TestPlanInstall_Inband(t *testing.T) { + t.Parallel() + fwSet := []*model.Firmware{ + { + Version: "2.19.6", + URL: "https://dl.dell.com/FOLDER08105057M/1/BIOS_C4FT0_WN64_2.19.6.EXE", + FileName: "BIOS_C4FT0_WN64_2.19.6.EXE", + Models: []string{"r6515"}, + Checksum: "1ddcb3c3d0fc5925ef03a3dde768e9e245c579039dd958fc0f3a9c6368b6c5f4", + Component: "bios", + }, + { + Version: "4.2.1", + URL: "https://foo/BLOB2.exx", + FileName: "Drive_4.2.1.EXE", + Models: []string{"000"}, + Checksum: "1ddcb3c3d0fc5925ef03a3dde768e9e245c579039dd958fc0f3a9c63aaaaabb", + Component: "drive", + InstallInband: true, + }, + { + Version: "1.2.3", + URL: "https://foo/BLOB.exx", + FileName: "NIC_1.2.3.EXE", + Models: []string{"0001"}, + Checksum: "1ddcb3c3d0fc5925ef03a3dde768e9e245c579039dd958fc0f3a9c63aaaaaaa", + Component: "nic", + InstallInband: true, + }, + } + + logger := logrus.NewEntry(logrus.New()) + dq := new(device.MockInbandQueryor) + publisher := ctrl.NewMockPublisher(t) + + serverID := uuid.MustParse("fa125199-e9dd-47d4-8667-ce1d26f58c4a") + taskID := uuid.MustParse("05c3296d-be5d-473a-b90c-4ce66cfdec65") + taskHandlerCtx := &runner.TaskHandlerContext{ + Logger: logger, + Publisher: model.NewTaskStatusPublisher(logger, publisher), + Task: &model.Task{ + ID: taskID, + WorkerID: registry.GetID("test-app").String(), + Parameters: &rctypes.FirmwareInstallTaskParameters{ + AssetID: serverID, + ForceInstall: true, + }, + Server: &rtypes.Server{ + ID: serverID.String(), + Components: rtypes.Components{ + { + Name: "nic", + Model: "0001", + Firmware: &common.Firmware{Installed: "1.2.2"}, + }, + { + Name: "drive", + Model: "000", + Firmware: &common.Firmware{Installed: "4.2.1"}, + }, + }, + }, + }, + DeviceQueryor: dq, + } + + publisher.EXPECT(). + Publish(mock.Anything, mock.Anything, mock.Anything).Return(nil) + dq.EXPECT().FirmwareInstallRequirements(mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Times(2). + Return(&ironlibm.UpdateRequirements{}, nil) + + h := handler{mode: model.RunInband, TaskHandlerContext: taskHandlerCtx} + actions, err := h.planInstallActions(context.Background(), fwSet) + require.NoError(t, err, "no errors returned") + require.Equal(t, 2, len(actions), "expect 2 actions") + require.True(t, actions[1].Last, "expect Last bool is true on the last action") + require.True(t, actions[0].ForceInstall, "expect ForceInstall set to true when task is forced") + require.True(t, actions[1].ForceInstall, "expect ForceInstall set to true when task is forced") + require.Equal(t, "drive", actions[0].Firmware.Component, "expect drive component action") + require.Equal(t, "nic", actions[1].Firmware.Component, "expect nic component action") +} From 4b6baa2f87f69b40f70932885bcf4bdd3c3cdceb Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 14:41:49 +0200 Subject: [PATCH 09/20] cmd: update cli to run inband and outofband installs --- cmd/install.go | 1 + cmd/run.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/cmd/install.go b/cmd/install.go index d005253c..112a4cba 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -38,6 +38,7 @@ func runInstall(ctx context.Context) { cfgFile, logLevel, enableProfiling, + model.RunOutofband, ) if err != nil { log.Fatal(err) diff --git a/cmd/run.go b/cmd/run.go index 0314a4e4..935f5947 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -5,12 +5,15 @@ import ( "log" "github.com/equinix-labs/otel-init-go/otelinit" + "github.com/google/uuid" "github.com/metal-toolbox/ctrl" "github.com/metal-toolbox/flasher/internal/app" "github.com/metal-toolbox/flasher/internal/metrics" "github.com/metal-toolbox/flasher/internal/model" "github.com/metal-toolbox/flasher/internal/store" "github.com/metal-toolbox/flasher/internal/worker" + + rctypes "github.com/metal-toolbox/rivets/condition" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -23,29 +26,40 @@ var cmdRun = &cobra.Command{ Use: "run", Short: "Run flasher service to listen for events and install firmware", Run: func(cmd *cobra.Command, _ []string) { - runWorker(cmd.Context()) + var mode model.RunMode + if runsInband { + mode = model.RunInband + } else { + mode = model.RunOutofband + } + + runWorker(cmd.Context(), mode) }, } // run worker command var ( dryrun bool + runsInband bool + runsOutofband bool faultInjection bool facilityCode string storeKind string + inbandServerID string ) var ( ErrInventoryStore = errors.New("inventory store error") ) -func runWorker(ctx context.Context) { +func runWorker(ctx context.Context, mode model.RunMode) { flasher, termCh, err := app.New( model.AppKindWorker, model.StoreKind(storeKind), cfgFile, logLevel, enableProfiling, + mode, ) if err != nil { log.Fatal(err) @@ -76,6 +90,19 @@ func runWorker(ctx context.Context) { flasher.Logger.Fatal("--facility-code parameter required") } + switch mode { + case model.RunInband: + runInband(ctx, flasher, repository) + return + case model.RunOutofband: + runOutofband(ctx, flasher, repository) + return + default: + flasher.Logger.Fatal("unsupported run mode: " + mode) + } +} + +func runOutofband(ctx context.Context, flasher *app.App, repository store.Repository) { natsCfg, err := flasher.NatsParams() if err != nil { flasher.Logger.Fatal(err) @@ -84,10 +111,10 @@ func runWorker(ctx context.Context) { nc := ctrl.NewNatsController( model.AppName, facilityCode, - "firmwareInstall", + string(rctypes.FirmwareInstall), natsCfg.NatsURL, natsCfg.CredsFile, - "firmwareInstall", + rctypes.FirmwareInstall, ctrl.WithConcurrency(flasher.Config.Concurrency), ctrl.WithKVReplicas(natsCfg.KVReplicas), ctrl.WithLogger(flasher.Logger), @@ -108,6 +135,42 @@ func runWorker(ctx context.Context) { ) } +func runInband(ctx context.Context, flasher *app.App, repository store.Repository) { + cfgOrcAPI := flasher.Config.OrchestratorAPIParams + orcConfig := &ctrl.OrchestratorAPIConfig{ + Endpoint: cfgOrcAPI.Endpoint, + AuthDisabled: cfgOrcAPI.AuthDisabled, + AuthToken: cfgOrcAPI.AuthToken, + OidcIssuerEndpoint: cfgOrcAPI.OidcIssuerEndpoint, + OidcAudienceEndpoint: cfgOrcAPI.OidcAudienceEndpoint, + OidcClientSecret: cfgOrcAPI.OidcClientSecret, + OidcClientID: cfgOrcAPI.OidcClientID, + OidcClientScopes: cfgOrcAPI.OidcClientScopes, + } + + nc, err := ctrl.NewHTTPController( + "flasher-inband", + facilityCode, + uuid.MustParse(flasher.Config.ServerID), + rctypes.FirmwareInstallInband, + orcConfig, + ctrl.WithNATSHTTPLogger(flasher.Logger), + ) + if err != nil { + flasher.Logger.Fatal(err) + } + + worker.RunInband( + ctx, + dryrun, + faultInjection, + facilityCode, + repository, + nc, + flasher.Logger, + ) +} + func initStore(ctx context.Context, config *app.Configuration, logger *logrus.Logger) (store.Repository, error) { if storeKind == string(model.InventoryStoreServerservice) { return store.NewServerserviceStore(ctx, config.FleetDBAPIOptions, logger) @@ -117,8 +180,11 @@ func initStore(ctx context.Context, config *app.Configuration, logger *logrus.Lo } func init() { - cmdRun.PersistentFlags().StringVar(&storeKind, "store", "", "Inventory store to lookup devices for update - 'serverservice' or an inventory file with a .yml/.yaml extenstion") + cmdRun.PersistentFlags().StringVar(&storeKind, "store", "", "Inventory store to lookup devices for update - fleetdb.") + cmdRun.PersistentFlags().StringVar(&inbandServerID, "server-id", "", "ServerID when running inband") cmdRun.PersistentFlags().BoolVarP(&dryrun, "dry-run", "", false, "In dryrun mode, the worker actions the task without installing firmware") + cmdRun.PersistentFlags().BoolVarP(&runsInband, "inband", "", false, "Runs worker in inband firmware install mode") + cmdRun.PersistentFlags().BoolVarP(&runsOutofband, "outofband", "", false, "Runs worker in out-of-band firmware install mode") cmdRun.PersistentFlags().BoolVarP(&faultInjection, "fault-injection", "", false, "Tasks can include a Fault attribute to allow fault injection for development purposes") cmdRun.PersistentFlags().StringVar(&facilityCode, "facility-code", "", "The facility code this flasher instance is associated with") @@ -126,5 +192,8 @@ func init() { log.Fatal(err) } + cmdRun.MarkFlagsMutuallyExclusive("inband", "outofband") + cmdRun.MarkFlagsOneRequired("inband", "outofband") + rootCmd.AddCommand(cmdRun) } From eddcd6a552c6878ac3d79b8cd74ecf713fcd1ae0 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Fri, 16 Aug 2024 15:12:14 +0200 Subject: [PATCH 10/20] go: pin to ctrl, rivets with required changes --- go.mod | 8 +++++--- go.sum | 10 ++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8fd6bd38..dae727af 100644 --- a/go.mod +++ b/go.mod @@ -15,10 +15,10 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 github.com/jeremywohl/flatten v1.0.1 github.com/jpillora/backoff v1.0.0 - github.com/metal-toolbox/ctrl v0.2.1 + github.com/metal-toolbox/ctrl v0.2.2-0.20240816125955-248ee65e0c94 github.com/metal-toolbox/fleetdb v1.19.3 - github.com/metal-toolbox/ironlib v0.4.1 - github.com/metal-toolbox/rivets v1.2.1-0.20240809101507-f574730a9963 + github.com/metal-toolbox/ironlib v0.4.2-0.20240815083723-fab996eb8e80 + github.com/metal-toolbox/rivets v1.2.1-0.20240816124954-75fd3d3b2629 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 @@ -163,3 +163,5 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/metal-toolbox/ctrl => ../ctrl diff --git a/go.sum b/go.sum index bf172eb2..9f08c3d8 100644 --- a/go.sum +++ b/go.sum @@ -560,14 +560,12 @@ github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/metal-toolbox/conditionorc v1.1.1-0.20240805163108-b1c018c91b87 h1:C3Gkj7+bV18HgXJuzeJ6RAttzqsO6sDBF7I20x5JvWM= github.com/metal-toolbox/conditionorc v1.1.1-0.20240805163108-b1c018c91b87/go.mod h1:0Rh7BPun3VcJzxsBfbZ94lxxxSUNLtugAeZPRWPcD3c= -github.com/metal-toolbox/ctrl v0.2.1 h1:CYOOsmX7SNBFJjxDIWWW76imGXlIfxO8KEKlUffXAak= -github.com/metal-toolbox/ctrl v0.2.1/go.mod h1:VfYYvY7SAvHjN+PTPsaahaazM7gKWq5s37aS3kX4KKo= github.com/metal-toolbox/fleetdb v1.19.3 h1:Dk7MVi5rS42a4OI46Ye/etg8R5hm1iajaI4U0k1GSec= github.com/metal-toolbox/fleetdb v1.19.3/go.mod h1:v9agAzF7BhBBjA4rY+H2eZzQaBTEXO4b/Qikn4jmA7A= -github.com/metal-toolbox/ironlib v0.4.1 h1:AL9xYNjViIdR7xEGxu8KHZb92uzSISYEFkU094s+21A= -github.com/metal-toolbox/ironlib v0.4.1/go.mod h1:YNS/N5Q61awwVEXJ20V04AzLrgSsT/Xrh8bHD6Iwi7M= -github.com/metal-toolbox/rivets v1.2.1-0.20240809101507-f574730a9963 h1:VtVyh556iekeYNrxUiD5Q3L5rY4F4O7X3ZULnKWNHvk= -github.com/metal-toolbox/rivets v1.2.1-0.20240809101507-f574730a9963/go.mod h1:i78a1x0w2uNyMlvUgyO6ST552u9wV2Xa4+A73oA4WJY= +github.com/metal-toolbox/ironlib v0.4.2-0.20240815083723-fab996eb8e80 h1:alVuAOH9916LUjIAynnG/5Bkle8juvOn5/21Azo6SmU= +github.com/metal-toolbox/ironlib v0.4.2-0.20240815083723-fab996eb8e80/go.mod h1:YNS/N5Q61awwVEXJ20V04AzLrgSsT/Xrh8bHD6Iwi7M= +github.com/metal-toolbox/rivets v1.2.1-0.20240816124954-75fd3d3b2629 h1:Q9ssrrui7qwYDlv17k2NZQa2CiZ4irgzdo1Sq27Kpvg= +github.com/metal-toolbox/rivets v1.2.1-0.20240816124954-75fd3d3b2629/go.mod h1:i78a1x0w2uNyMlvUgyO6ST552u9wV2Xa4+A73oA4WJY= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= From a789d1edcd8aecb352398fcafa21a08054f667ce Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Mon, 19 Aug 2024 13:19:08 +0200 Subject: [PATCH 11/20] oob/helpers: fixes incorrect BMC user parameter --- internal/outofband/bmc_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/outofband/bmc_helpers.go b/internal/outofband/bmc_helpers.go index 92726946..cb780e03 100644 --- a/internal/outofband/bmc_helpers.go +++ b/internal/outofband/bmc_helpers.go @@ -94,7 +94,7 @@ func newBmclibv2Client(_ context.Context, asset *rtypes.Server, l *logrus.Entry) bmcClient := bmclib.NewClient( asset.BMCAddress, - asset.BMCAddress, + asset.BMCUser, asset.BMCPassword, bmclib.WithLogger(logruslogr), bmclib.WithHTTPClient(newHTTPClient()), From adfe3b1e75663346ecd8c6f318651a7f1dedaf36 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Mon, 19 Aug 2024 13:19:47 +0200 Subject: [PATCH 12/20] download: increment timeout for slow connections The 60 second timeout crossed easily in the sandbox when downloaded large update files like the dell BMC firmware blob --- internal/download/download.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/download/download.go b/internal/download/download.go index dcac9fe9..437369b5 100644 --- a/internal/download/download.go +++ b/internal/download/download.go @@ -16,8 +16,9 @@ import ( ) var ( - downloadRetryDelay = 4 * time.Second - downloadClientTimeout = 60 * time.Second + downloadRetryDelay = 4 * time.Second + // allow upto 5 minutes of timeout for downloading over slow connections + downloadClientTimeout = 300 * time.Second ErrDownload = errors.New("error downloading file") ErrChecksum = errors.New("error validating file checksum") @@ -84,8 +85,6 @@ func ChecksumValidate(filename, checksum string) error { } } -// TODO: firmware-syncer needs to prefix firmware checksums values with the type of checksum -// so consumers can validate it accordingly func checksumValidateMD5(filename, checksum string) error { var err error From 6236eb6e189aa0f683625344481a6c30f7f5c37d Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Mon, 19 Aug 2024 15:13:59 +0200 Subject: [PATCH 13/20] go mod tidy --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dae727af..15222c44 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/metal-toolbox/ctrl v0.2.2-0.20240816125955-248ee65e0c94 github.com/metal-toolbox/fleetdb v1.19.3 github.com/metal-toolbox/ironlib v0.4.2-0.20240815083723-fab996eb8e80 - github.com/metal-toolbox/rivets v1.2.1-0.20240816124954-75fd3d3b2629 + github.com/metal-toolbox/rivets v1.3.1 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 9f08c3d8..5304d3c4 100644 --- a/go.sum +++ b/go.sum @@ -564,8 +564,8 @@ github.com/metal-toolbox/fleetdb v1.19.3 h1:Dk7MVi5rS42a4OI46Ye/etg8R5hm1iajaI4U github.com/metal-toolbox/fleetdb v1.19.3/go.mod h1:v9agAzF7BhBBjA4rY+H2eZzQaBTEXO4b/Qikn4jmA7A= github.com/metal-toolbox/ironlib v0.4.2-0.20240815083723-fab996eb8e80 h1:alVuAOH9916LUjIAynnG/5Bkle8juvOn5/21Azo6SmU= github.com/metal-toolbox/ironlib v0.4.2-0.20240815083723-fab996eb8e80/go.mod h1:YNS/N5Q61awwVEXJ20V04AzLrgSsT/Xrh8bHD6Iwi7M= -github.com/metal-toolbox/rivets v1.2.1-0.20240816124954-75fd3d3b2629 h1:Q9ssrrui7qwYDlv17k2NZQa2CiZ4irgzdo1Sq27Kpvg= -github.com/metal-toolbox/rivets v1.2.1-0.20240816124954-75fd3d3b2629/go.mod h1:i78a1x0w2uNyMlvUgyO6ST552u9wV2Xa4+A73oA4WJY= +github.com/metal-toolbox/rivets v1.3.1 h1:UD70qh6KNNgCPivAP7Ed4U8H8aJ3+3Bw8FtyMXiM7Qg= +github.com/metal-toolbox/rivets v1.3.1/go.mod h1:i78a1x0w2uNyMlvUgyO6ST552u9wV2Xa4+A73oA4WJY= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= From 237d2e19904ec50b79a1f7ebcd236c739cdaa658 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Tue, 20 Aug 2024 17:51:11 +0200 Subject: [PATCH 14/20] store/fleetdb: copy inbandInstall, Oem bool values from firmware set response The InbandInstall bool is required for flasher inband to plan and execute the inband install --- internal/store/fleetdb.go | 40 +++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/internal/store/fleetdb.go b/internal/store/fleetdb.go index 6ac748ac..88cdf194 100644 --- a/internal/store/fleetdb.go +++ b/internal/store/fleetdb.go @@ -313,18 +313,38 @@ func intoFirmwaresSlice(componentFirmware []fleetdbapi.ComponentFirmwareVersion) firmwares := make([]*model.Firmware, 0, len(componentFirmware)) + booleanIsTrue := func(b *bool) bool { + if b != nil && *b { + return true + } + + return false + } + // nolint:gocritic // rangeValCopy - componentFirmware is returned by fleetdb API in this form. for _, firmware := range componentFirmware { - firmwares = append(firmwares, &model.Firmware{ - ID: firmware.UUID.String(), - Vendor: strings.ToLower(firmware.Vendor), - Models: strSliceToLower(firmware.Model), - FileName: firmware.Filename, - Version: firmware.Version, - Component: strings.ToLower(firmware.Component), - Checksum: firmware.Checksum, - URL: firmware.RepositoryURL, - }) + fw := &model.Firmware{ + ID: firmware.UUID.String(), + Vendor: strings.ToLower(firmware.Vendor), + Models: strSliceToLower(firmware.Model), + FileName: firmware.Filename, + Version: firmware.Version, + Component: strings.ToLower(firmware.Component), + Checksum: firmware.Checksum, + URL: firmware.RepositoryURL, + InstallInband: *firmware.InstallInband, + Oem: *firmware.OEM, + } + + if booleanIsTrue(firmware.InstallInband) { + fw.InstallInband = true + } + + if booleanIsTrue(firmware.OEM) { + fw.Oem = true + } + + firmwares = append(firmwares, fw) } return firmwares From 41f987ad988de7b085b8b8b8635e2ee5cf2a73e3 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Tue, 20 Aug 2024 17:58:54 +0200 Subject: [PATCH 15/20] inband: powercycle in the final component fw install action instead of powercycling post each component firwmare install --- internal/inband/actions.go | 21 ++++++++++++--------- internal/inband/actions_handlers.go | 7 +++++++ internal/model/task.go | 4 ++++ internal/worker/task_handler_test.go | 1 + 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/internal/inband/actions.go b/internal/inband/actions.go index 68ddd423..41ecb3f6 100644 --- a/internal/inband/actions.go +++ b/internal/inband/actions.go @@ -108,24 +108,23 @@ func (i *ActionHandler) ComposeAction(ctx context.Context, actionCtx *runner.Act Info("No firmware install requirements were identified for component") } - steps, err := i.composeSteps(required) - if err != nil { - return nil, errors.Wrap(errCompose, err.Error()) - } - - action := &model.Action{ + i.handler.action = &model.Action{ InstallMethod: model.InstallMethodInband, Firmware: *actionCtx.Firmware, ForceInstall: actionCtx.Task.Parameters.ForceInstall, - Steps: steps, First: actionCtx.First, Last: actionCtx.Last, Component: component, } - i.handler.action = action + steps, err := i.composeSteps(required) + if err != nil { + return nil, errors.Wrap(errCompose, err.Error()) + } + + i.handler.action.Steps = steps - return action, nil + return i.handler.action, nil } func initHandler(actionCtx *runner.ActionHandlerContext, queryor device.InbandQueryor) *handler { @@ -158,6 +157,10 @@ func (i *ActionHandler) composeSteps(required *imodel.UpdateRequirements) (model final = append(final, install...) if required != nil && required.PostInstallHostPowercycle { + i.handler.task.Data.HostPowercycleRequired = true + } + + if i.handler.action.Last && i.handler.task.Data.HostPowercycleRequired { powerCycle, errDef := i.definitions().ByName(powerCycleServer) if errDef != nil { return nil, err diff --git a/internal/inband/actions_handlers.go b/internal/inband/actions_handlers.go index 54dd30f2..0c4b708c 100644 --- a/internal/inband/actions_handlers.go +++ b/internal/inband/actions_handlers.go @@ -196,6 +196,7 @@ func (h *handler) installFirmware(ctx context.Context) error { h.action.FirmwareTempFile, h.action.ForceInstall, ); err != nil { + // component update could not be applied because it requires a host power cycle if errors.Is(err, iutils.ErrRebootRequired) { h.logger.WithFields( logrus.Fields{ @@ -204,6 +205,12 @@ func (h *handler) installFirmware(ctx context.Context) error { "version": h.firmware.Version, "msg": err.Error(), }).Info("firmware install requires a server power cycle") + + // force power cycle if we're on the last action + if h.action.Last { + return h.powerCycleServer(ctx) + } + return nil } diff --git a/internal/model/task.go b/internal/model/task.go index 318c7a10..00c0ab69 100644 --- a/internal/model/task.go +++ b/internal/model/task.go @@ -67,6 +67,10 @@ func (t *Task) MustMarshal() json.RawMessage { type TaskData struct { StructVersion string `json:"struct_version"` + + // This flag is set when a action requires a host power cycle. + HostPowercycleRequired bool `json:"host_powercycle_required,omitempty"` + // Flasher determines the firmware to be installed for each component based on the firmware plan method. FirmwarePlanMethod FirmwarePlanMethod `json:"firmware_plan_method,omitempty"` diff --git a/internal/worker/task_handler_test.go b/internal/worker/task_handler_test.go index 3b4eaa80..14deef50 100644 --- a/internal/worker/task_handler_test.go +++ b/internal/worker/task_handler_test.go @@ -382,6 +382,7 @@ func TestPlanInstall_Inband(t *testing.T) { Task: &model.Task{ ID: taskID, WorkerID: registry.GetID("test-app").String(), + Data: &model.TaskData{}, Parameters: &rctypes.FirmwareInstallTaskParameters{ AssetID: serverID, ForceInstall: true, From bf6442a9c74fb1e3db9aed3912c8761da27b5124 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Tue, 20 Aug 2024 18:00:28 +0200 Subject: [PATCH 16/20] go: pin ctrl, ironlib deps --- go.mod | 8 +++----- go.sum | 12 ++++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 15222c44..d781afc9 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22 require ( github.com/banzaicloud/logrus-runtime-formatter v0.0.0-20190729070250-5ae5475bae5e github.com/bmc-toolbox/bmclib/v2 v2.2.6 - github.com/bmc-toolbox/common v0.0.0-20240723142833-87832458b53b + github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3 github.com/bombsimon/logrusr/v2 v2.0.1 github.com/coreos/go-oidc/v3 v3.11.0 github.com/emicklei/dot v1.6.2 @@ -15,9 +15,9 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 github.com/jeremywohl/flatten v1.0.1 github.com/jpillora/backoff v1.0.0 - github.com/metal-toolbox/ctrl v0.2.2-0.20240816125955-248ee65e0c94 + github.com/metal-toolbox/ctrl v0.2.2-0.20240820153810-9f5f8ffcb086 github.com/metal-toolbox/fleetdb v1.19.3 - github.com/metal-toolbox/ironlib v0.4.2-0.20240815083723-fab996eb8e80 + github.com/metal-toolbox/ironlib v0.4.2-0.20240820154349-f72c0f707be4 github.com/metal-toolbox/rivets v1.3.1 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/mapstructure v1.5.0 @@ -163,5 +163,3 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) - -replace github.com/metal-toolbox/ctrl => ../ctrl diff --git a/go.sum b/go.sum index 5304d3c4..53d71e3a 100644 --- a/go.sum +++ b/go.sum @@ -112,8 +112,10 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmc-toolbox/bmclib/v2 v2.2.6 h1:ol2jVa1ERfBFZS5Ye1chORai+nkzt9J1LgTTMxjZ0mw= github.com/bmc-toolbox/bmclib/v2 v2.2.6/go.mod h1:V2XVg0Scpm16+0gE7WnI+5bU/M0c/o/nPZKHKzyVjAo= -github.com/bmc-toolbox/common v0.0.0-20240723142833-87832458b53b h1:0LHjikaGWlqEMczrCEZ6w1N/ZqcYlx6WRHkhabRUQEk= -github.com/bmc-toolbox/common v0.0.0-20240723142833-87832458b53b/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I= +github.com/bmc-toolbox/bmclib/v2 v2.2.7-0.20240820122530-fe8af4779053 h1:zSWrFVbjHH2uiOp74MoIv81VGUp2l0H9HKbEEv0poqQ= +github.com/bmc-toolbox/bmclib/v2 v2.2.7-0.20240820122530-fe8af4779053/go.mod h1:xnaECeoiRhy4MLR2JTDFKzyDaAykiVdVcT9CuzLDj6o= +github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3 h1:/BjZSX/sphptIdxpYo4wxAQkgMLyMMgfdl48J9DKNeE= +github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I= github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM= github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio= github.com/bytedance/sonic v1.12.0 h1:YGPgxF9xzaCNvd/ZKdQ28yRovhfMFZQjuk6fKBzZ3ls= @@ -560,10 +562,12 @@ github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/metal-toolbox/conditionorc v1.1.1-0.20240805163108-b1c018c91b87 h1:C3Gkj7+bV18HgXJuzeJ6RAttzqsO6sDBF7I20x5JvWM= github.com/metal-toolbox/conditionorc v1.1.1-0.20240805163108-b1c018c91b87/go.mod h1:0Rh7BPun3VcJzxsBfbZ94lxxxSUNLtugAeZPRWPcD3c= +github.com/metal-toolbox/ctrl v0.2.2-0.20240820153810-9f5f8ffcb086 h1:zHwQHtYDkoz806ZtHhf5/EZ2TRjZ+XVxeMl1vQSkDVM= +github.com/metal-toolbox/ctrl v0.2.2-0.20240820153810-9f5f8ffcb086/go.mod h1:pN64bE8tqvG2x1YpS62lfwEPOzMh98lXwyF11t8VLYw= github.com/metal-toolbox/fleetdb v1.19.3 h1:Dk7MVi5rS42a4OI46Ye/etg8R5hm1iajaI4U0k1GSec= github.com/metal-toolbox/fleetdb v1.19.3/go.mod h1:v9agAzF7BhBBjA4rY+H2eZzQaBTEXO4b/Qikn4jmA7A= -github.com/metal-toolbox/ironlib v0.4.2-0.20240815083723-fab996eb8e80 h1:alVuAOH9916LUjIAynnG/5Bkle8juvOn5/21Azo6SmU= -github.com/metal-toolbox/ironlib v0.4.2-0.20240815083723-fab996eb8e80/go.mod h1:YNS/N5Q61awwVEXJ20V04AzLrgSsT/Xrh8bHD6Iwi7M= +github.com/metal-toolbox/ironlib v0.4.2-0.20240820154349-f72c0f707be4 h1:Y8/TwjcBmCryADtIbGlPVpzaHcmnTbBBkpqFMD/Alk0= +github.com/metal-toolbox/ironlib v0.4.2-0.20240820154349-f72c0f707be4/go.mod h1:YNS/N5Q61awwVEXJ20V04AzLrgSsT/Xrh8bHD6Iwi7M= github.com/metal-toolbox/rivets v1.3.1 h1:UD70qh6KNNgCPivAP7Ed4U8H8aJ3+3Bw8FtyMXiM7Qg= github.com/metal-toolbox/rivets v1.3.1/go.mod h1:i78a1x0w2uNyMlvUgyO6ST552u9wV2Xa4+A73oA4WJY= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= From 27c22dc6a2d818a3bc6aef4ad499d3c5b7159677 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Tue, 20 Aug 2024 18:05:11 +0200 Subject: [PATCH 17/20] go: update ironlib pin --- go.mod | 14 +++++++------- go.sum | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index d781afc9..dbb0dfdb 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/jpillora/backoff v1.0.0 github.com/metal-toolbox/ctrl v0.2.2-0.20240820153810-9f5f8ffcb086 github.com/metal-toolbox/fleetdb v1.19.3 - github.com/metal-toolbox/ironlib v0.4.2-0.20240820154349-f72c0f707be4 + github.com/metal-toolbox/ironlib v0.4.2-0.20240820160227-1a7996f5b77c github.com/metal-toolbox/rivets v1.3.1 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/mapstructure v1.5.0 @@ -31,7 +31,7 @@ require ( go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/trace v1.28.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/net v0.27.0 + golang.org/x/net v0.28.0 golang.org/x/oauth2 v0.22.0 ) @@ -40,7 +40,7 @@ require ( github.com/Jeffail/gabs/v2 v2.7.0 // indirect github.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230 // indirect github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 // indirect - github.com/beevik/etree v1.4.0 // indirect + github.com/beevik/etree v1.4.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.12.0 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect @@ -124,7 +124,7 @@ require ( github.com/stmcginnis/gofish v0.15.1-0.20231121142100-22a60a77be91 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tidwall/gjson v1.17.1 // indirect + github.com/tidwall/gjson v1.17.3 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -149,9 +149,9 @@ require ( go.uber.org/zap v1.27.0 // indirect gocloud.dev v0.38.0 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect google.golang.org/api v0.189.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf // indirect diff --git a/go.sum b/go.sum index 53d71e3a..640ee5b7 100644 --- a/go.sum +++ b/go.sum @@ -105,6 +105,8 @@ github.com/banzaicloud/logrus-runtime-formatter v0.0.0-20190729070250-5ae5475bae github.com/banzaicloud/logrus-runtime-formatter v0.0.0-20190729070250-5ae5475bae5e/go.mod h1:hEvEpPmuwKO+0TbrDQKIkmX0gW2s2waZHF8pIhEEmpM= github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= +github.com/beevik/etree v1.4.1 h1:PmQJDDYahBGNKDcpdX8uPy1xRCwoCGVUiW669MEirVI= +github.com/beevik/etree v1.4.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -112,8 +114,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmc-toolbox/bmclib/v2 v2.2.6 h1:ol2jVa1ERfBFZS5Ye1chORai+nkzt9J1LgTTMxjZ0mw= github.com/bmc-toolbox/bmclib/v2 v2.2.6/go.mod h1:V2XVg0Scpm16+0gE7WnI+5bU/M0c/o/nPZKHKzyVjAo= -github.com/bmc-toolbox/bmclib/v2 v2.2.7-0.20240820122530-fe8af4779053 h1:zSWrFVbjHH2uiOp74MoIv81VGUp2l0H9HKbEEv0poqQ= -github.com/bmc-toolbox/bmclib/v2 v2.2.7-0.20240820122530-fe8af4779053/go.mod h1:xnaECeoiRhy4MLR2JTDFKzyDaAykiVdVcT9CuzLDj6o= github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3 h1:/BjZSX/sphptIdxpYo4wxAQkgMLyMMgfdl48J9DKNeE= github.com/bmc-toolbox/common v0.0.0-20240806132831-ba8adc6a35e3/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I= github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM= @@ -568,6 +568,8 @@ github.com/metal-toolbox/fleetdb v1.19.3 h1:Dk7MVi5rS42a4OI46Ye/etg8R5hm1iajaI4U github.com/metal-toolbox/fleetdb v1.19.3/go.mod h1:v9agAzF7BhBBjA4rY+H2eZzQaBTEXO4b/Qikn4jmA7A= github.com/metal-toolbox/ironlib v0.4.2-0.20240820154349-f72c0f707be4 h1:Y8/TwjcBmCryADtIbGlPVpzaHcmnTbBBkpqFMD/Alk0= github.com/metal-toolbox/ironlib v0.4.2-0.20240820154349-f72c0f707be4/go.mod h1:YNS/N5Q61awwVEXJ20V04AzLrgSsT/Xrh8bHD6Iwi7M= +github.com/metal-toolbox/ironlib v0.4.2-0.20240820160227-1a7996f5b77c h1:j9X/iktguadGJMMQ6UuI+qGA8r5bM/CQQM7HWF/pEww= +github.com/metal-toolbox/ironlib v0.4.2-0.20240820160227-1a7996f5b77c/go.mod h1:WQZ8tZ8+bCts6mgBhN9M2r6Mer0aS8M0JNcaZm1dIS4= github.com/metal-toolbox/rivets v1.3.1 h1:UD70qh6KNNgCPivAP7Ed4U8H8aJ3+3Bw8FtyMXiM7Qg= github.com/metal-toolbox/rivets v1.3.1/go.mod h1:i78a1x0w2uNyMlvUgyO6ST552u9wV2Xa4+A73oA4WJY= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= @@ -743,6 +745,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= @@ -864,6 +868,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -958,6 +964,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1089,6 +1097,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1109,6 +1119,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 4a72d802c7d4c6603480f23a30eab371e82eea62 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Tue, 20 Aug 2024 18:10:58 +0200 Subject: [PATCH 18/20] inband/action_handlers: set powercycle required bool if its not the final install action --- internal/inband/actions_handlers.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/inband/actions_handlers.go b/internal/inband/actions_handlers.go index 0c4b708c..79ad63d4 100644 --- a/internal/inband/actions_handlers.go +++ b/internal/inband/actions_handlers.go @@ -211,6 +211,7 @@ func (h *handler) installFirmware(ctx context.Context) error { return h.powerCycleServer(ctx) } + h.task.Data.HostPowercycleRequired = true return nil } From d5ef06deaebd290ab35c722d0bde5bd30c278528 Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Wed, 21 Aug 2024 10:09:50 +0200 Subject: [PATCH 19/20] inband/action: fail if no update utility was identified --- internal/inband/actions.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/inband/actions.go b/internal/inband/actions.go index 41ecb3f6..95011cd5 100644 --- a/internal/inband/actions.go +++ b/internal/inband/actions.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" + ironlibactions "github.com/metal-toolbox/ironlib/actions" imodel "github.com/metal-toolbox/ironlib/model" ) @@ -100,7 +101,11 @@ func (i *ActionHandler) ComposeAction(ctx context.Context, actionCtx *runner.Act component.Model, ) if err != nil { - // not a fatal error + // fatal error only if the updater utility is not identified + if errors.Is(err, ironlibactions.ErrUpdaterUtilNotIdentified) { + return nil, err + } + i.handler.logger.WithFields(logrus.Fields{ "component": actionCtx.Firmware.Component, "model": actionCtx.Firmware.Models, From 12372871032d124a4aebb913fd3dbf97100cdcbe Mon Sep 17 00:00:00 2001 From: Joel Rebello Date: Thu, 22 Aug 2024 17:01:07 +0200 Subject: [PATCH 20/20] go: pin to rivets, ctrl release --- go.mod | 4 ++-- go.sum | 26 ++++++-------------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index dbb0dfdb..6e3fc278 100644 --- a/go.mod +++ b/go.mod @@ -15,10 +15,10 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 github.com/jeremywohl/flatten v1.0.1 github.com/jpillora/backoff v1.0.0 - github.com/metal-toolbox/ctrl v0.2.2-0.20240820153810-9f5f8ffcb086 + github.com/metal-toolbox/ctrl v0.2.2 github.com/metal-toolbox/fleetdb v1.19.3 github.com/metal-toolbox/ironlib v0.4.2-0.20240820160227-1a7996f5b77c - github.com/metal-toolbox/rivets v1.3.1 + github.com/metal-toolbox/rivets v1.3.2 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 640ee5b7..090816d7 100644 --- a/go.sum +++ b/go.sum @@ -103,8 +103,6 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/banzaicloud/logrus-runtime-formatter v0.0.0-20190729070250-5ae5475bae5e h1:ZOnKnYG1LLgq4W7wZUYj9ntn3RxQ65EZyYqdtFpP2Dw= github.com/banzaicloud/logrus-runtime-formatter v0.0.0-20190729070250-5ae5475bae5e/go.mod h1:hEvEpPmuwKO+0TbrDQKIkmX0gW2s2waZHF8pIhEEmpM= -github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= -github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/beevik/etree v1.4.1 h1:PmQJDDYahBGNKDcpdX8uPy1xRCwoCGVUiW669MEirVI= github.com/beevik/etree v1.4.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -562,16 +560,14 @@ github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/metal-toolbox/conditionorc v1.1.1-0.20240805163108-b1c018c91b87 h1:C3Gkj7+bV18HgXJuzeJ6RAttzqsO6sDBF7I20x5JvWM= github.com/metal-toolbox/conditionorc v1.1.1-0.20240805163108-b1c018c91b87/go.mod h1:0Rh7BPun3VcJzxsBfbZ94lxxxSUNLtugAeZPRWPcD3c= -github.com/metal-toolbox/ctrl v0.2.2-0.20240820153810-9f5f8ffcb086 h1:zHwQHtYDkoz806ZtHhf5/EZ2TRjZ+XVxeMl1vQSkDVM= -github.com/metal-toolbox/ctrl v0.2.2-0.20240820153810-9f5f8ffcb086/go.mod h1:pN64bE8tqvG2x1YpS62lfwEPOzMh98lXwyF11t8VLYw= +github.com/metal-toolbox/ctrl v0.2.2 h1:8OFId6uxyVpwyS+Asmehs9PLwZrnBRWOc6viMVd6/vc= +github.com/metal-toolbox/ctrl v0.2.2/go.mod h1:pN64bE8tqvG2x1YpS62lfwEPOzMh98lXwyF11t8VLYw= github.com/metal-toolbox/fleetdb v1.19.3 h1:Dk7MVi5rS42a4OI46Ye/etg8R5hm1iajaI4U0k1GSec= github.com/metal-toolbox/fleetdb v1.19.3/go.mod h1:v9agAzF7BhBBjA4rY+H2eZzQaBTEXO4b/Qikn4jmA7A= -github.com/metal-toolbox/ironlib v0.4.2-0.20240820154349-f72c0f707be4 h1:Y8/TwjcBmCryADtIbGlPVpzaHcmnTbBBkpqFMD/Alk0= -github.com/metal-toolbox/ironlib v0.4.2-0.20240820154349-f72c0f707be4/go.mod h1:YNS/N5Q61awwVEXJ20V04AzLrgSsT/Xrh8bHD6Iwi7M= github.com/metal-toolbox/ironlib v0.4.2-0.20240820160227-1a7996f5b77c h1:j9X/iktguadGJMMQ6UuI+qGA8r5bM/CQQM7HWF/pEww= github.com/metal-toolbox/ironlib v0.4.2-0.20240820160227-1a7996f5b77c/go.mod h1:WQZ8tZ8+bCts6mgBhN9M2r6Mer0aS8M0JNcaZm1dIS4= -github.com/metal-toolbox/rivets v1.3.1 h1:UD70qh6KNNgCPivAP7Ed4U8H8aJ3+3Bw8FtyMXiM7Qg= -github.com/metal-toolbox/rivets v1.3.1/go.mod h1:i78a1x0w2uNyMlvUgyO6ST552u9wV2Xa4+A73oA4WJY= +github.com/metal-toolbox/rivets v1.3.2 h1:wazODpX94E2IbjuqEAmARl1r9TZE0NS1vBiGSpBW/tE= +github.com/metal-toolbox/rivets v1.3.2/go.mod h1:i78a1x0w2uNyMlvUgyO6ST552u9wV2Xa4+A73oA4WJY= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -743,8 +739,6 @@ github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= -github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= @@ -866,8 +860,6 @@ golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -962,8 +954,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1002,8 +992,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1095,8 +1085,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1117,8 +1105,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=