diff --git a/go.mod b/go.mod index 5a21cbc..435c419 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/onsi/ginkgo/v2 v2.11.0 github.com/onsi/gomega v1.27.10 github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.8.2 github.com/urfave/cli/v2 v2.25.7 github.com/vmware/goipmi v0.0.0-20181114221114-2333cd82d702 k8s.io/api v0.28.3 @@ -50,12 +51,14 @@ require ( github.com/openshift/custom-resource-status v1.1.2 // indirect github.com/pborman/uuid v1.2.1 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect diff --git a/go.sum b/go.sum index 83b72b6..18083a3 100644 --- a/go.sum +++ b/go.sum @@ -201,6 +201,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/pkg/virtbmc/k8s_test.go b/pkg/virtbmc/k8s_test.go new file mode 100644 index 0000000..4e9219b --- /dev/null +++ b/pkg/virtbmc/k8s_test.go @@ -0,0 +1,323 @@ +package virtbmc + +import ( + "context" + "testing" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + kubevirtv1 "kubevirt.io/api/core/v1" + kubevirttypev1 "kubevirt.io/kubevirtbmc/pkg/generated/clientset/versioned/typed/core/v1" +) + +type MockKubevirtClient struct { + mock.Mock +} + +func (m *MockKubevirtClient) VirtualMachines(namespace string) kubevirttypev1.VirtualMachineInterface { + args := m.Called(namespace) + return args.Get(0).(kubevirttypev1.VirtualMachineInterface) +} + +type MockVirtualMachineInterface struct { + mock.Mock +} + +func (m *MockVirtualMachineInterface) Get( + ctx context.Context, name string, options v1.GetOptions, +) (*kubevirtv1.VirtualMachine, error) { + args := m.Called(ctx, name, options) + return args.Get(0).(*kubevirtv1.VirtualMachine), args.Error(1) +} + +func (m *MockVirtualMachineInterface) Create( + ctx context.Context, vm *kubevirtv1.VirtualMachine, options v1.CreateOptions, +) (*kubevirtv1.VirtualMachine, error) { + panic("implement me") +} + +func (m *MockVirtualMachineInterface) Update( + ctx context.Context, vm *kubevirtv1.VirtualMachine, options v1.UpdateOptions, +) (*kubevirtv1.VirtualMachine, error) { + args := m.Called(ctx, vm, options) + return args.Get(0).(*kubevirtv1.VirtualMachine), args.Error(1) +} + +func (m *MockVirtualMachineInterface) Delete(ctx context.Context, name string, options v1.DeleteOptions) error { + panic("implement me") +} + +func (m *MockVirtualMachineInterface) DeleteCollection( + ctx context.Context, options v1.DeleteOptions, listOptions v1.ListOptions, +) error { + panic("implement me") +} + +func (m *MockVirtualMachineInterface) List( + ctx context.Context, options v1.ListOptions, +) (*kubevirtv1.VirtualMachineList, error) { + panic("implement me") +} + +func (m *MockVirtualMachineInterface) Patch( + ctx context.Context, + name string, + pt types.PatchType, + data []byte, + options v1.PatchOptions, + subresources ...string, +) (*kubevirtv1.VirtualMachine, error) { + panic("implement me") +} + +func (m *MockVirtualMachineInterface) UpdateStatus( + ctx context.Context, vm *kubevirtv1.VirtualMachine, options v1.UpdateOptions, +) (*kubevirtv1.VirtualMachine, error) { + panic("implement me") +} + +func (m *MockVirtualMachineInterface) Watch(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + panic("implement me") +} + +func (m *MockKubevirtClient) VirtualMachineInstances(namespace string) kubevirttypev1.VirtualMachineInstanceInterface { + args := m.Called(namespace) + return args.Get(0).(kubevirttypev1.VirtualMachineInstanceInterface) +} + +type MockVirtualMachineInstanceInterface struct { + mock.Mock +} + +func (m *MockVirtualMachineInstanceInterface) Get( + ctx context.Context, name string, options v1.GetOptions, +) (*kubevirtv1.VirtualMachineInstance, error) { + panic("implement me") +} + +func (m *MockVirtualMachineInstanceInterface) Create( + ctx context.Context, vmi *kubevirtv1.VirtualMachineInstance, options v1.CreateOptions, +) (*kubevirtv1.VirtualMachineInstance, error) { + panic("implement me") +} + +func (m *MockVirtualMachineInstanceInterface) Update( + ctx context.Context, vmi *kubevirtv1.VirtualMachineInstance, options v1.UpdateOptions, +) (*kubevirtv1.VirtualMachineInstance, error) { + args := m.Called(ctx, vmi, options) + return args.Get(0).(*kubevirtv1.VirtualMachineInstance), args.Error(1) +} + +func (m *MockVirtualMachineInstanceInterface) Delete(ctx context.Context, name string, options v1.DeleteOptions) error { + args := m.Called(ctx, name, options) + return args.Error(0) +} + +func (m *MockVirtualMachineInstanceInterface) DeleteCollection( + ctx context.Context, options v1.DeleteOptions, listOptions v1.ListOptions, +) error { + panic("implement me") +} + +func (m *MockVirtualMachineInstanceInterface) List( + ctx context.Context, options v1.ListOptions, +) (*kubevirtv1.VirtualMachineInstanceList, error) { + panic("implement me") +} + +func (m *MockVirtualMachineInstanceInterface) Patch( + ctx context.Context, + name string, + pt types.PatchType, + data []byte, + options v1.PatchOptions, + subresources ...string, +) (*kubevirtv1.VirtualMachineInstance, error) { + panic("implement me") +} + +func (m *MockVirtualMachineInstanceInterface) UpdateStatus( + ctx context.Context, vm *kubevirtv1.VirtualMachineInstance, options v1.UpdateOptions, +) (*kubevirtv1.VirtualMachineInstance, error) { + panic("implement me") +} + +func (m *MockVirtualMachineInstanceInterface) Watch( + ctx context.Context, options v1.ListOptions, +) (watch.Interface, error) { + panic("implement me") +} + +func TestGetVirtualMachinePowerStatus(t *testing.T) { + mockClient := new(MockKubevirtClient) + mockVMInterface := new(MockVirtualMachineInterface) + mockClient.On("VirtualMachines", "default").Return(mockVMInterface) + + mockVM := &kubevirtv1.VirtualMachine{ + Status: kubevirtv1.VirtualMachineStatus{ + Ready: true, + }, + } + mockVMInterface.On("Get", mock.Anything, "test-vm", mock.Anything).Return(mockVM, nil) + + bmc := &VirtBMC{ + context: context.TODO(), + address: "127.0.0.1", + port: 623, + vmNamespace: "default", + vmName: "test-vm", + kvClient: mockClient, + } + + // Test getVirtualMachinePowerStatus + status, err := bmc.getVirtualMachinePowerStatus() + require.NoError(t, err) + require.True(t, status) + + // Add another test case where the VM is not ready + mockVM.Status.Ready = false + mockVMInterface.On("Get", mock.Anything, "test-vm", mock.Anything).Return(mockVM, nil) + + status, err = bmc.getVirtualMachinePowerStatus() + require.NoError(t, err) + require.False(t, status) +} + +// Helper function to create a fake VM and set up common mock expectations +func setupVMTest(running bool) (*VirtBMC, *MockVirtualMachineInterface, *kubevirtv1.VirtualMachine) { + mockClient := new(MockKubevirtClient) + mockVMInterface := new(MockVirtualMachineInterface) + mockClient.On("VirtualMachines", "default").Return(mockVMInterface) + + fakeVM := &kubevirtv1.VirtualMachine{ + Spec: kubevirtv1.VirtualMachineSpec{ + Running: func(b bool) *bool { return &b }(running), + }, + } + mockVMInterface.On("Get", mock.Anything, "test-vm", mock.Anything).Return(fakeVM, nil) + mockVMInterface.On("Update", mock.Anything, fakeVM, mock.Anything).Return(fakeVM, nil) + + bmc := &VirtBMC{ + context: context.TODO(), + address: "127.0.0.1", + port: 623, + vmNamespace: "default", + vmName: "test-vm", + kvClient: mockClient, + } + + return bmc, mockVMInterface, fakeVM +} + +func TestStopVirtualMachine(t *testing.T) { + bmc, mockVMInterface, fakeVM := setupVMTest(true) + + // Test stopVirtualMachine + err := bmc.stopVirtualMachine() + require.NoError(t, err) + + // Assertion + mockVMInterface.AssertCalled(t, "Get", mock.Anything, "test-vm", mock.Anything) + mockVMInterface.AssertCalled(t, "Update", mock.Anything, fakeVM, mock.Anything) + + require.NotNil(t, fakeVM.Spec.Running) + require.False(t, *fakeVM.Spec.Running) +} + +func TestStartVirtualMachine(t *testing.T) { + bmc, mockVMInterface, fakeVM := setupVMTest(false) + + // Test startVirtualMachine + err := bmc.startVirtualMachine() + require.NoError(t, err) + + // Assertion + mockVMInterface.AssertCalled(t, "Get", mock.Anything, "test-vm", mock.Anything) + mockVMInterface.AssertCalled(t, "Update", mock.Anything, fakeVM, mock.Anything) + + require.NotNil(t, fakeVM.Spec.Running) + require.True(t, *fakeVM.Spec.Running) +} + +func TestRebootVirtualMachine(t *testing.T) { + mockClient := new(MockKubevirtClient) + mockVMIInterface := new(MockVirtualMachineInstanceInterface) + mockClient.On("VirtualMachineInstances", "default").Return(mockVMIInterface) + + mockVMIInterface.On("Delete", mock.Anything, "test-vm", mock.Anything).Return(nil) + + bmc := &VirtBMC{ + context: context.TODO(), + address: "127.0.0.1", + port: 623, + vmNamespace: "default", + vmName: "test-vm", + kvClient: mockClient, + } + + // Test rebootVirtualMachine + err := bmc.rebootVirtualMachine() + require.NoError(t, err) + + // Assertion + mockVMIInterface.AssertCalled(t, "Delete", mock.Anything, "test-vm", mock.Anything) +} + +func TestSetVirtualMachineBootDevice(t *testing.T) { + mockClient := new(MockKubevirtClient) + mockVMInterface := new(MockVirtualMachineInterface) + mockClient.On("VirtualMachines", "default").Return(mockVMInterface) + + fakeVM := &kubevirtv1.VirtualMachine{ + Spec: kubevirtv1.VirtualMachineSpec{ + Template: &kubevirtv1.VirtualMachineInstanceTemplateSpec{ + Spec: kubevirtv1.VirtualMachineInstanceSpec{ + Domain: kubevirtv1.DomainSpec{ + Devices: kubevirtv1.Devices{ + Disks: []kubevirtv1.Disk{ + { + Name: "disk1", + BootOrder: new(uint), + }, + }, + Interfaces: []kubevirtv1.Interface{ + { + Name: "net1", + BootOrder: new(uint), + }, + }, + }, + }, + }, + }, + }, + } + mockVMInterface.On("Get", mock.Anything, "test-vm", mock.Anything).Return(fakeVM, nil) + mockVMInterface.On("Update", mock.Anything, fakeVM, mock.Anything).Return(fakeVM, nil) + + bmc := &VirtBMC{ + context: context.TODO(), + address: "127.0.0.1", + port: 623, + vmNamespace: "default", + vmName: "test-vm", + kvClient: mockClient, + } + + // Test PXE boot device + err := bmc.setVirtualMachineBootDevice(Pxe) + require.NoError(t, err) + + require.Equal(t, uint(1), *fakeVM.Spec.Template.Spec.Domain.Devices.Interfaces[0].BootOrder) + + // Test Disk boot device + err = bmc.setVirtualMachineBootDevice(Disk) + require.NoError(t, err) + + require.Equal(t, uint(1), *fakeVM.Spec.Template.Spec.Domain.Devices.Disks[0].BootOrder) + + mockVMInterface.AssertCalled(t, "Update", mock.Anything, fakeVM, mock.Anything) +} diff --git a/pkg/virtbmc/virtbmc.go b/pkg/virtbmc/virtbmc.go index 1cf1380..1d5426b 100644 --- a/pkg/virtbmc/virtbmc.go +++ b/pkg/virtbmc/virtbmc.go @@ -20,13 +20,18 @@ type Options struct { Port int } +type KubeVirtClientInterface interface { + VirtualMachines(namespace string) kubevirtv1.VirtualMachineInterface + VirtualMachineInstances(namespace string) kubevirtv1.VirtualMachineInstanceInterface +} + type VirtBMC struct { context context.Context address string port int vmNamespace string vmName string - kvClient *kubevirtv1.KubevirtV1Client + kvClient KubeVirtClientInterface sim *ipmi.Simulator }