diff --git a/pkg/cluster/generate.go b/pkg/cluster/generate.go index 0076f15d61b..2eb197fb00b 100644 --- a/pkg/cluster/generate.go +++ b/pkg/cluster/generate.go @@ -4,4 +4,6 @@ package cluster // Licensed under the Apache License 2.0. //go:generate go run ../../vendor/github.com/golang/mock/mockgen -destination=../util/mocks/$GOPACKAGE/$GOPACKAGE.go github.com/Azure/ARO-RP/pkg/$GOPACKAGE Interface +//go:generate go run ../../vendor/github.com/golang/mock/mockgen -destination=../util/mocks/samplesclient/versioned.go github.com/openshift/client-go/samples/clientset/versioned Interface +//go:generate go run ../../vendor/github.com/golang/mock/mockgen -destination=../util/mocks/samples/samples.go github.com/openshift/client-go/samples/clientset/versioned/typed/samples/v1 SamplesV1Interface,ConfigInterface //go:generate go run ../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ../util/mocks/$GOPACKAGE/$GOPACKAGE.go diff --git a/pkg/cluster/samples.go b/pkg/cluster/samples.go index c4d4ef32015..befa7962422 100644 --- a/pkg/cluster/samples.go +++ b/pkg/cluster/samples.go @@ -20,10 +20,12 @@ func (m *manager) disableSamples(ctx context.Context) error { return nil } - return retry.OnError(retry.DefaultRetry, + return retry.OnError( + retry.DefaultRetry, func(err error) bool { return errors.IsConflict(err) || errors.IsNotFound(err) - }, func() error { + }, + func() error { c, err := m.samplescli.SamplesV1().Configs().Get(ctx, "cluster", metav1.GetOptions{}) if err != nil { return err diff --git a/pkg/cluster/samples_test.go b/pkg/cluster/samples_test.go new file mode 100644 index 00000000000..1fb4ecb5981 --- /dev/null +++ b/pkg/cluster/samples_test.go @@ -0,0 +1,105 @@ +package cluster + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "errors" + "testing" + + "github.com/golang/mock/gomock" + operatorv1 "github.com/openshift/api/operator/v1" + samplesv1 "github.com/openshift/api/samples/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/Azure/ARO-RP/pkg/api" + mock_env "github.com/Azure/ARO-RP/pkg/util/mocks/env" + mock_samples "github.com/Azure/ARO-RP/pkg/util/mocks/samples" + mock_samplesclient "github.com/Azure/ARO-RP/pkg/util/mocks/samplesclient" + utilerror "github.com/Azure/ARO-RP/test/util/error" +) + +func Test_manager_disableSamples(t *testing.T) { + ctx := context.Background() + samplesConfig := &samplesv1.Config{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{}, + Spec: samplesv1.ConfigSpec{}, + Status: samplesv1.ConfigStatus{}, + } + tests := []struct { + name string + samplesConfig *samplesv1.Config + samplesCRGetError error + samplesCRUpdateError error + expectedMinNumberOfGetCalls int + expectedMaxNumberOfGetCalls int + wantErr string + }{ + { + name: "samples cr is found and updated", + samplesConfig: samplesConfig, + expectedMinNumberOfGetCalls: 1, + expectedMaxNumberOfGetCalls: 1, + wantErr: "", + }, + { + name: "samples cr is not found and retried", + samplesCRGetError: kerrors.NewNotFound(schema.GroupResource{}, "samples"), + expectedMinNumberOfGetCalls: 2, + expectedMaxNumberOfGetCalls: 15, + wantErr: " \"samples\" not found", + }, + { + name: "samples cr update is conflicting and retried", + samplesConfig: samplesConfig, + expectedMinNumberOfGetCalls: 2, + expectedMaxNumberOfGetCalls: 15, + samplesCRUpdateError: kerrors.NewConflict(schema.GroupResource{}, "samples", errors.New("conflict")), + wantErr: "Operation cannot be fulfilled on \"samples\": conflict", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + env := mock_env.NewMockInterface(controller) + samplescli := mock_samplesclient.NewMockInterface(controller) + samplesInterface := mock_samples.NewMockSamplesV1Interface(controller) + configInterface := mock_samples.NewMockConfigInterface(controller) + + env.EXPECT().IsLocalDevelopmentMode().Return(false) + samplescli.EXPECT().SamplesV1().AnyTimes().Return(samplesInterface) + samplesInterface.EXPECT().Configs().AnyTimes().Return(configInterface) + configInterface.EXPECT().Get(gomock.Any(), "cluster", metav1.GetOptions{}). + MinTimes(tt.expectedMinNumberOfGetCalls). + MaxTimes(tt.expectedMaxNumberOfGetCalls). + Return(tt.samplesConfig, tt.samplesCRGetError) + + if tt.samplesConfig != nil { + samplesConfig.Spec.ManagementState = operatorv1.Removed + configInterface.EXPECT().Update(gomock.Any(), samplesConfig, metav1.UpdateOptions{}).AnyTimes().Return(samplesConfig, tt.samplesCRUpdateError) + } + + m := &manager{ + env: env, + doc: &api.OpenShiftClusterDocument{ + OpenShiftCluster: &api.OpenShiftCluster{ + Properties: api.OpenShiftClusterProperties{ + ClusterProfile: api.ClusterProfile{ + ResourceGroupID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clusterRGName", + }, + }, + }, + }, + samplescli: samplescli, + } + + err := m.disableSamples(ctx) + utilerror.AssertErrorMessage(t, err, tt.wantErr) + }) + } +} diff --git a/pkg/util/mocks/samples/samples.go b/pkg/util/mocks/samples/samples.go new file mode 100644 index 00000000000..c14a29c2b60 --- /dev/null +++ b/pkg/util/mocks/samples/samples.go @@ -0,0 +1,230 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/openshift/client-go/samples/clientset/versioned/typed/samples/v1 (interfaces: SamplesV1Interface,ConfigInterface) + +// Package mock_v1 is a generated GoMock package. +package mock_v1 + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1 "github.com/openshift/api/samples/v1" + v10 "github.com/openshift/client-go/samples/clientset/versioned/typed/samples/v1" + v11 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// MockSamplesV1Interface is a mock of SamplesV1Interface interface. +type MockSamplesV1Interface struct { + ctrl *gomock.Controller + recorder *MockSamplesV1InterfaceMockRecorder +} + +// MockSamplesV1InterfaceMockRecorder is the mock recorder for MockSamplesV1Interface. +type MockSamplesV1InterfaceMockRecorder struct { + mock *MockSamplesV1Interface +} + +// NewMockSamplesV1Interface creates a new mock instance. +func NewMockSamplesV1Interface(ctrl *gomock.Controller) *MockSamplesV1Interface { + mock := &MockSamplesV1Interface{ctrl: ctrl} + mock.recorder = &MockSamplesV1InterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSamplesV1Interface) EXPECT() *MockSamplesV1InterfaceMockRecorder { + return m.recorder +} + +// Configs mocks base method. +func (m *MockSamplesV1Interface) Configs() v10.ConfigInterface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Configs") + ret0, _ := ret[0].(v10.ConfigInterface) + return ret0 +} + +// Configs indicates an expected call of Configs. +func (mr *MockSamplesV1InterfaceMockRecorder) Configs() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Configs", reflect.TypeOf((*MockSamplesV1Interface)(nil).Configs)) +} + +// RESTClient mocks base method. +func (m *MockSamplesV1Interface) RESTClient() rest.Interface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RESTClient") + ret0, _ := ret[0].(rest.Interface) + return ret0 +} + +// RESTClient indicates an expected call of RESTClient. +func (mr *MockSamplesV1InterfaceMockRecorder) RESTClient() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTClient", reflect.TypeOf((*MockSamplesV1Interface)(nil).RESTClient)) +} + +// MockConfigInterface is a mock of ConfigInterface interface. +type MockConfigInterface struct { + ctrl *gomock.Controller + recorder *MockConfigInterfaceMockRecorder +} + +// MockConfigInterfaceMockRecorder is the mock recorder for MockConfigInterface. +type MockConfigInterfaceMockRecorder struct { + mock *MockConfigInterface +} + +// NewMockConfigInterface creates a new mock instance. +func NewMockConfigInterface(ctrl *gomock.Controller) *MockConfigInterface { + mock := &MockConfigInterface{ctrl: ctrl} + mock.recorder = &MockConfigInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConfigInterface) EXPECT() *MockConfigInterfaceMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockConfigInterface) Create(arg0 context.Context, arg1 *v1.Config, arg2 v11.CreateOptions) (*v1.Config, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1, arg2) + ret0, _ := ret[0].(*v1.Config) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockConfigInterfaceMockRecorder) Create(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockConfigInterface)(nil).Create), arg0, arg1, arg2) +} + +// Delete mocks base method. +func (m *MockConfigInterface) Delete(arg0 context.Context, arg1 string, arg2 v11.DeleteOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockConfigInterfaceMockRecorder) Delete(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockConfigInterface)(nil).Delete), arg0, arg1, arg2) +} + +// DeleteCollection mocks base method. +func (m *MockConfigInterface) DeleteCollection(arg0 context.Context, arg1 v11.DeleteOptions, arg2 v11.ListOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteCollection", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteCollection indicates an expected call of DeleteCollection. +func (mr *MockConfigInterfaceMockRecorder) DeleteCollection(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCollection", reflect.TypeOf((*MockConfigInterface)(nil).DeleteCollection), arg0, arg1, arg2) +} + +// Get mocks base method. +func (m *MockConfigInterface) Get(arg0 context.Context, arg1 string, arg2 v11.GetOptions) (*v1.Config, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) + ret0, _ := ret[0].(*v1.Config) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockConfigInterfaceMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockConfigInterface)(nil).Get), arg0, arg1, arg2) +} + +// List mocks base method. +func (m *MockConfigInterface) List(arg0 context.Context, arg1 v11.ListOptions) (*v1.ConfigList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", arg0, arg1) + ret0, _ := ret[0].(*v1.ConfigList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockConfigInterfaceMockRecorder) List(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockConfigInterface)(nil).List), arg0, arg1) +} + +// Patch mocks base method. +func (m *MockConfigInterface) Patch(arg0 context.Context, arg1 string, arg2 types.PatchType, arg3 []byte, arg4 v11.PatchOptions, arg5 ...string) (*v1.Config, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1, arg2, arg3, arg4} + for _, a := range arg5 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(*v1.Config) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Patch indicates an expected call of Patch. +func (mr *MockConfigInterfaceMockRecorder) Patch(arg0, arg1, arg2, arg3, arg4 interface{}, arg5 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1, arg2, arg3, arg4}, arg5...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockConfigInterface)(nil).Patch), varargs...) +} + +// Update mocks base method. +func (m *MockConfigInterface) Update(arg0 context.Context, arg1 *v1.Config, arg2 v11.UpdateOptions) (*v1.Config, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", arg0, arg1, arg2) + ret0, _ := ret[0].(*v1.Config) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockConfigInterfaceMockRecorder) Update(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockConfigInterface)(nil).Update), arg0, arg1, arg2) +} + +// UpdateStatus mocks base method. +func (m *MockConfigInterface) UpdateStatus(arg0 context.Context, arg1 *v1.Config, arg2 v11.UpdateOptions) (*v1.Config, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateStatus", arg0, arg1, arg2) + ret0, _ := ret[0].(*v1.Config) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateStatus indicates an expected call of UpdateStatus. +func (mr *MockConfigInterfaceMockRecorder) UpdateStatus(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockConfigInterface)(nil).UpdateStatus), arg0, arg1, arg2) +} + +// Watch mocks base method. +func (m *MockConfigInterface) Watch(arg0 context.Context, arg1 v11.ListOptions) (watch.Interface, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Watch", arg0, arg1) + ret0, _ := ret[0].(watch.Interface) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Watch indicates an expected call of Watch. +func (mr *MockConfigInterfaceMockRecorder) Watch(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Watch", reflect.TypeOf((*MockConfigInterface)(nil).Watch), arg0, arg1) +} diff --git a/pkg/util/mocks/samplesclient/versioned.go b/pkg/util/mocks/samplesclient/versioned.go new file mode 100644 index 00000000000..cd7a8c6f0dc --- /dev/null +++ b/pkg/util/mocks/samplesclient/versioned.go @@ -0,0 +1,64 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/openshift/client-go/samples/clientset/versioned (interfaces: Interface) + +// Package mock_versioned is a generated GoMock package. +package mock_versioned + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1 "github.com/openshift/client-go/samples/clientset/versioned/typed/samples/v1" + discovery "k8s.io/client-go/discovery" +) + +// MockInterface is a mock of Interface interface. +type MockInterface struct { + ctrl *gomock.Controller + recorder *MockInterfaceMockRecorder +} + +// MockInterfaceMockRecorder is the mock recorder for MockInterface. +type MockInterfaceMockRecorder struct { + mock *MockInterface +} + +// NewMockInterface creates a new mock instance. +func NewMockInterface(ctrl *gomock.Controller) *MockInterface { + mock := &MockInterface{ctrl: ctrl} + mock.recorder = &MockInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { + return m.recorder +} + +// Discovery mocks base method. +func (m *MockInterface) Discovery() discovery.DiscoveryInterface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Discovery") + ret0, _ := ret[0].(discovery.DiscoveryInterface) + return ret0 +} + +// Discovery indicates an expected call of Discovery. +func (mr *MockInterfaceMockRecorder) Discovery() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discovery", reflect.TypeOf((*MockInterface)(nil).Discovery)) +} + +// SamplesV1 mocks base method. +func (m *MockInterface) SamplesV1() v1.SamplesV1Interface { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SamplesV1") + ret0, _ := ret[0].(v1.SamplesV1Interface) + return ret0 +} + +// SamplesV1 indicates an expected call of SamplesV1. +func (mr *MockInterfaceMockRecorder) SamplesV1() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SamplesV1", reflect.TypeOf((*MockInterface)(nil).SamplesV1)) +}