// capability_id: rainbond.vm-power.start-existing-or-create // capability_id: rainbond.vm-power.direct-ops-event-close // capability_id: rainbond.vm-power.stop-restoring-fallback package handler import ( "context" "errors" "testing" "github.com/golang/mock/gomock" apimodel "github.com/goodrain/rainbond/api/model" ctxutil "github.com/goodrain/rainbond/api/util/ctx" "github.com/goodrain/rainbond/db" dbmodel "github.com/goodrain/rainbond/db/model" mqpb "github.com/goodrain/rainbond/mq/api/grpc/pb" mqclient "github.com/goodrain/rainbond/mq/client" "google.golang.org/grpc" k8sapierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" kubevirtv1 "kubevirt.io/api/core/v1" kubecli "kubevirt.io/client-go/kubecli" ) type recordingMQClient struct { tasks []mqclient.TaskStruct } func (m *recordingMQClient) Enqueue(context.Context, *mqpb.EnqueueRequest, ...grpc.CallOption) (*mqpb.TaskReply, error) { return nil, nil } func (m *recordingMQClient) Topics(context.Context, *mqpb.TopicRequest, ...grpc.CallOption) (*mqpb.TaskReply, error) { return nil, nil } func (m *recordingMQClient) Dequeue(context.Context, *mqpb.DequeueRequest, ...grpc.CallOption) (*mqpb.TaskMessage, error) { return nil, nil } func (m *recordingMQClient) Close() {} func (m *recordingMQClient) SendBuilderTopic(t mqclient.TaskStruct) error { m.tasks = append(m.tasks, t) return nil } func newVMOperationEventContext(eventID string) context.Context { return context.WithValue(context.Background(), ctxutil.ContextKey("event"), &dbmodel.ServiceEvent{EventID: eventID}) } func TestStartOrCreateVMStartsExistingStoppedVM(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) mq := &recordingMQClient{} mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusStopped, }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Start(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{MQClient: mq, kubevirtClient: mockClient} err := action.StartOrCreateVM(context.Background(), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "start", }, "deploy-v1") if err != nil { t.Fatalf("expected no error, got %v", err) } if len(mq.tasks) != 0 { t.Fatalf("expected no worker start task, got %#v", mq.tasks) } } func TestStartOrCreateVMStartsExistingStoppedVMWhenSpecSyncFindsMissingRootImport(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Start(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{ kubevirtClient: mockClient, syncVirtualMachineSpecHook: func(serviceID string) error { return errors.New("validate vm boot path failure: missing imported root data volume for vm boot source format \"qcow2\"") }, getVirtualMachineByServiceIDHook: func(serviceID string) (*kubevirtv1.VirtualMachine, error) { return &kubevirtv1.VirtualMachine{ ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusStopped, }, }, nil }, } err := action.StartOrCreateVM(context.Background(), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "start", }, "deploy-v1") if err != nil { t.Fatalf("expected existing legacy vm to start despite missing root import sync error, got %v", err) } } func TestStartOrCreateVMStartsStoppedAlwaysRunStrategyVM(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) eventDao := &resourceSyncEventDao{} db.SetTestManager(resourceSyncTestManager{eventDao: eventDao}) defer db.SetTestManager(nil) runStrategy := kubevirtv1.RunStrategyAlways mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Spec: kubevirtv1.VirtualMachineSpec{ RunStrategy: &runStrategy, }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusStopped, }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Start(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{kubevirtClient: mockClient} err := action.StartOrCreateVM(newVMOperationEventContext("event-1"), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "start", }, "deploy-v1") if err != nil { t.Fatalf("expected stopped Always run strategy VM to start, got %v", err) } if len(eventDao.statuses) != 1 || eventDao.statuses[0] != dbmodel.EventStatusSuccess { t.Fatalf("expected success event status update, got %#v", eventDao.statuses) } } func TestStartOrCreateVMStartsExistingVMWithUnknownPrintableStatus(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Start(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{kubevirtClient: mockClient} err := action.StartOrCreateVM(context.Background(), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "start", }, "deploy-v1") if err != nil { t.Fatalf("expected VM with unknown printable status to start, got %v", err) } } func TestStartOrCreateVMMarksDirectStartEventSuccess(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) eventDao := &resourceSyncEventDao{} db.SetTestManager(resourceSyncTestManager{eventDao: eventDao}) defer db.SetTestManager(nil) mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusStopped, }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Start(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{kubevirtClient: mockClient} err := action.StartOrCreateVM(newVMOperationEventContext("event-1"), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "start", }, "deploy-v1") if err != nil { t.Fatalf("expected no error, got %v", err) } if len(eventDao.statuses) != 1 || eventDao.statuses[0] != dbmodel.EventStatusSuccess { t.Fatalf("expected success event status update, got %#v", eventDao.statuses) } } func TestStartOrCreateVMMarksDirectStartEventFailure(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) eventDao := &resourceSyncEventDao{} db.SetTestManager(resourceSyncTestManager{eventDao: eventDao}) defer db.SetTestManager(nil) mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusStopped, }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Start(gomock.Any(), "demo-vm", gomock.Any()).Return(errors.New("boom")) action := &ServiceAction{kubevirtClient: mockClient} err := action.StartOrCreateVM(newVMOperationEventContext("event-1"), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "start", }, "deploy-v1") if err == nil { t.Fatal("expected direct VM start failure to be returned") } if len(eventDao.statuses) != 1 || eventDao.statuses[0] != dbmodel.EventStatusFailure { t.Fatalf("expected failure event status update, got %#v", eventDao.statuses) } } func TestStartOrCreateVMTreatsTransientStartConflictAsSuccessAfterRefresh(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) eventDao := &resourceSyncEventDao{} db.SetTestManager(resourceSyncTestManager{eventDao: eventDao}) defer db.SetTestManager(nil) originalDelay := vmStartRetryDelay vmStartRetryDelay = 0 defer func() { vmStartRetryDelay = originalDelay }() gomock.InOrder( mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface), mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusStopped, }, }, }, }, nil), mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface), mockVMInterface.EXPECT().Start(gomock.Any(), "demo-vm", gomock.Any()).Return( k8sapierrors.NewConflict(schema.GroupResource{Group: "kubevirt.io", Resource: "virtualmachines"}, "demo-vm", errors.New("conflict")), ), mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface), mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusStarting, }, }, }, }, nil), ) action := &ServiceAction{kubevirtClient: mockClient} err := action.StartOrCreateVM(newVMOperationEventContext("event-1"), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "start", }, "deploy-v1") if err != nil { t.Fatalf("expected transient conflict to be treated as success after refresh, got %v", err) } if len(eventDao.statuses) != 1 || eventDao.statuses[0] != dbmodel.EventStatusSuccess { t.Fatalf("expected success event status update, got %#v", eventDao.statuses) } } func TestStartOrCreateVMFallsBackToWorkerStartWhenVMIsMissing(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) mq := &recordingMQClient{} mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{}, nil) action := &ServiceAction{MQClient: mq, kubevirtClient: mockClient} err := action.StartOrCreateVM(context.Background(), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "start", }, "deploy-v1") if err != nil { t.Fatalf("expected no error, got %v", err) } if len(mq.tasks) != 1 { t.Fatalf("expected one worker start task, got %#v", mq.tasks) } if mq.tasks[0].TaskType != "start" { t.Fatalf("expected worker task type start, got %q", mq.tasks[0].TaskType) } } func TestStopVMStopsExistingVMWithoutWorkerTask(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) mq := &recordingMQClient{} mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Stop(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{MQClient: mq, kubevirtClient: mockClient} err := action.StopVM(context.Background(), "service-1") if err != nil { t.Fatalf("expected no error, got %v", err) } if len(mq.tasks) != 0 { t.Fatalf("expected no worker stop task, got %#v", mq.tasks) } } func TestStopVMMarksDirectStopEventSuccess(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) eventDao := &resourceSyncEventDao{} db.SetTestManager(resourceSyncTestManager{eventDao: eventDao}) defer db.SetTestManager(nil) mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusRunning, }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Stop(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{kubevirtClient: mockClient} err := action.StopVM(newVMOperationEventContext("event-1"), "service-1") if err != nil { t.Fatalf("expected no error, got %v", err) } if len(eventDao.statuses) != 1 || eventDao.statuses[0] != dbmodel.EventStatusSuccess { t.Fatalf("expected success event status update, got %#v", eventDao.statuses) } } func TestStopVMFallsBackToHaltingProvisioningVMWhenDirectStopFails(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMList := kubecli.NewMockVirtualMachineInterface(ctrl) mockVMUpdate := kubecli.NewMockVirtualMachineInterface(ctrl) gomock.InOrder( mockClient.EXPECT().VirtualMachine("").Return(mockVMList), mockVMList.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Spec: kubevirtv1.VirtualMachineSpec{ RunStrategy: pointerToRunStrategy(kubevirtv1.RunStrategyAlways), }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusProvisioning, }, }, }, }, nil), mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMUpdate), mockVMUpdate.EXPECT().Stop(gomock.Any(), "demo-vm", gomock.Any()).Return(errors.New("stop not allowed while provisioning")), mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMUpdate), mockVMUpdate.EXPECT().Update(gomock.Any(), gomock.AssignableToTypeOf(&kubevirtv1.VirtualMachine{}), gomock.Any()).DoAndReturn( func(_ context.Context, vm *kubevirtv1.VirtualMachine, _ metav1.UpdateOptions) (*kubevirtv1.VirtualMachine, error) { if vm.Spec.RunStrategy == nil || *vm.Spec.RunStrategy != kubevirtv1.RunStrategyHalted { t.Fatalf("expected fallback update to halt vm, got %#v", vm.Spec.RunStrategy) } if vm.Spec.Running != nil { t.Fatalf("expected fallback update to clear legacy running flag, got %#v", vm.Spec.Running) } return vm, nil }, ), ) action := &ServiceAction{kubevirtClient: mockClient} err := action.StopVM(context.Background(), "service-1") if err != nil { t.Fatalf("expected no error, got %v", err) } } func TestStopVMFallsBackToHaltingDataVolumeErrorVMWhenDirectStopFails(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMList := kubecli.NewMockVirtualMachineInterface(ctrl) mockVMUpdate := kubecli.NewMockVirtualMachineInterface(ctrl) gomock.InOrder( mockClient.EXPECT().VirtualMachine("").Return(mockVMList), mockVMList.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Spec: kubevirtv1.VirtualMachineSpec{ RunStrategy: pointerToRunStrategy(kubevirtv1.RunStrategyAlways), }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusDataVolumeError, }, }, }, }, nil), mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMUpdate), mockVMUpdate.EXPECT().Stop(gomock.Any(), "demo-vm", gomock.Any()).Return(errors.New("stop not allowed while datavolume error")), mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMUpdate), mockVMUpdate.EXPECT().Update(gomock.Any(), gomock.AssignableToTypeOf(&kubevirtv1.VirtualMachine{}), gomock.Any()).DoAndReturn( func(_ context.Context, vm *kubevirtv1.VirtualMachine, _ metav1.UpdateOptions) (*kubevirtv1.VirtualMachine, error) { if vm.Spec.RunStrategy == nil || *vm.Spec.RunStrategy != kubevirtv1.RunStrategyHalted { t.Fatalf("expected fallback update to halt vm, got %#v", vm.Spec.RunStrategy) } return vm, nil }, ), ) action := &ServiceAction{kubevirtClient: mockClient} err := action.StopVM(context.Background(), "service-1") if err != nil { t.Fatalf("expected no error, got %v", err) } } func TestRestartVMRestartsExistingRunningVMWithoutWorkerTask(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) mq := &recordingMQClient{} mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusRunning, }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Restart(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{MQClient: mq, kubevirtClient: mockClient} err := action.RestartVM(context.Background(), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "restart", }, "deploy-v1") if err != nil { t.Fatalf("expected no error, got %v", err) } if len(mq.tasks) != 0 { t.Fatalf("expected no worker restart task, got %#v", mq.tasks) } } func TestRestartVMMarksDirectRestartEventSuccess(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) eventDao := &resourceSyncEventDao{} db.SetTestManager(resourceSyncTestManager{eventDao: eventDao}) defer db.SetTestManager(nil) mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusRunning, }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Restart(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{kubevirtClient: mockClient} err := action.RestartVM(newVMOperationEventContext("event-1"), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "restart", }, "deploy-v1") if err != nil { t.Fatalf("expected no error, got %v", err) } if len(eventDao.statuses) != 1 || eventDao.statuses[0] != dbmodel.EventStatusSuccess { t.Fatalf("expected success event status update, got %#v", eventDao.statuses) } } func TestRestartVMStartsStoppedVMWithoutWorkerTask(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := kubecli.NewMockKubevirtClient(ctrl) mockVMInterface := kubecli.NewMockVirtualMachineInterface(ctrl) mq := &recordingMQClient{} mockClient.EXPECT().VirtualMachine("").Return(mockVMInterface) mockVMInterface.EXPECT().List(gomock.Any(), metav1.ListOptions{LabelSelector: "service_id=service-1"}).Return(&kubevirtv1.VirtualMachineList{ Items: []kubevirtv1.VirtualMachine{ { ObjectMeta: metav1.ObjectMeta{ Name: "demo-vm", Namespace: "demo-ns", }, Status: kubevirtv1.VirtualMachineStatus{ PrintableStatus: kubevirtv1.VirtualMachineStatusStopped, }, }, }, }, nil) mockClient.EXPECT().VirtualMachine("demo-ns").Return(mockVMInterface) mockVMInterface.EXPECT().Start(gomock.Any(), "demo-vm", gomock.Any()).Return(nil) action := &ServiceAction{MQClient: mq, kubevirtClient: mockClient} err := action.RestartVM(context.Background(), &apimodel.StartStopStruct{ TenantID: "tenant-1", ServiceID: "service-1", EventID: "event-1", TaskType: "restart", }, "deploy-v1") if err != nil { t.Fatalf("expected no error, got %v", err) } if len(mq.tasks) != 0 { t.Fatalf("expected no worker restart task, got %#v", mq.tasks) } }