diff --git a/pkg/internal/checkup/checkup.go b/pkg/internal/checkup/checkup.go index 97d50063..7e9fb580 100644 --- a/pkg/internal/checkup/checkup.go +++ b/pkg/internal/checkup/checkup.go @@ -59,6 +59,7 @@ type kubeVirtStorageClient interface { CreateDataVolume(ctx context.Context, namespace string, dv *cdiv1.DataVolume) (*cdiv1.DataVolume, error) DeleteDataVolume(ctx context.Context, namespace, name string) error DeletePersistentVolumeClaim(ctx context.Context, namespace, name string) error + ListNodes(ctx context.Context) (*corev1.NodeList, error) ListNamespaces(ctx context.Context) (*corev1.NamespaceList, error) ListStorageClasses(ctx context.Context) (*storagev1.StorageClassList, error) ListStorageProfiles(ctx context.Context) (*cdiv1.StorageProfileList, error) @@ -93,6 +94,7 @@ const ( ErrGoldenImageNoDataSource = "dataSource has no PVC or Snapshot source" MessageSkipNoGoldenImage = "Skip check - no golden image PVC or Snapshot" MessageSkipNoVMI = "Skip check - no VMI" + MessageSkipSingleNode = "Skip check - single node" pollInterval = 5 * time.Second ) @@ -823,6 +825,16 @@ func (c *Checkup) checkVMILiveMigration(ctx context.Context, errStr *string) err return nil } + nodes, err := c.client.ListNodes(ctx) + if err != nil { + return err + } + if len(nodes.Items) == 1 { + log.Print(MessageSkipSingleNode) + c.results.VMLiveMigration = MessageSkipSingleNode + return nil + } + vmi, err := c.client.GetVirtualMachineInstance(ctx, c.namespace, c.vmUnderTest.Name) if err != nil { return err diff --git a/pkg/internal/checkup/checkup_test.go b/pkg/internal/checkup/checkup_test.go index 39e0f93c..37e715ad 100644 --- a/pkg/internal/checkup/checkup_test.go +++ b/pkg/internal/checkup/checkup_test.go @@ -46,6 +46,7 @@ import ( const ( testNamespace = "target-ns" + testNode = "test-node" ) var ( @@ -146,6 +147,11 @@ func TestCheckupShouldReturnErrorWhen(t *testing.T) { expectedResults: map[string]string{reporter.VMLiveMigrationKey: "failed waiting for VMI \"%s\" migration completed: migration failed"}, expectedErr: "migration failed", }, + "skipMigrationOnSingleNode": { + clientConfig: clientConfig{singleNode: true}, + expectedResults: map[string]string{reporter.VMLiveMigrationKey: "Skip check - single node"}, + expectedErr: "", + }, } for name, tc := range tests { @@ -247,6 +253,7 @@ type clientConfig struct { expectNoVMI bool cloneFallback bool failMigration bool + singleNode bool } type clientStub struct { @@ -403,6 +410,23 @@ func (cs *clientStub) DeletePersistentVolumeClaim(ctx context.Context, namespace return nil } +func (cs *clientStub) ListNodes(ctx context.Context) (*corev1.NodeList, error) { + nodeList := &corev1.NodeList{} + itemCount := 2 + if cs.singleNode { + itemCount = 1 + } + for i := 0; i < itemCount; i++ { + nodeList.Items = append(nodeList.Items, corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("node-%d", i), + }, + }) + } + + return nodeList, nil +} + func (cs *clientStub) ListNamespaces(ctx context.Context) (*corev1.NamespaceList, error) { nsList := &corev1.NamespaceList{ Items: []corev1.Namespace{ diff --git a/pkg/internal/client/client.go b/pkg/internal/client/client.go index cef6c2f9..dc433749 100644 --- a/pkg/internal/client/client.go +++ b/pkg/internal/client/client.go @@ -100,6 +100,10 @@ func (c *Client) DeletePersistentVolumeClaim(ctx context.Context, namespace, nam return c.CoreV1().PersistentVolumeClaims(namespace).Delete(ctx, name, metav1.DeleteOptions{}) } +func (c *Client) ListNodes(ctx context.Context) (*corev1.NodeList, error) { + return c.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) +} + func (c *Client) ListNamespaces(ctx context.Context) (*corev1.NamespaceList, error) { return c.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) }