Skip to content
Merged
11 changes: 10 additions & 1 deletion api/v1alpha1/kubevirtmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,18 @@ const (
MachineFinalizer = "kubevirtmachine.infrastructure.cluster.x-k8s.io"
)

// VirtualMachineTemplateSpec defines the desired state of the kubevirt VM.
type VirtualMachineTemplateSpec struct {
// +kubebuilder:pruning:PreserveUnknownFields
// +nullable
ObjectMeta metav1.ObjectMeta `json:"metadata,omitempty"`
// VirtualMachineSpec contains the VirtualMachine specification.
Spec kubevirtv1.VirtualMachineSpec `json:"spec,omitempty" valid:"required"`
}

// KubevirtMachineSpec defines the desired state of KubevirtMachine.
type KubevirtMachineSpec struct {
VMSpec kubevirtv1.VirtualMachineInstanceSpec `json:"vmSpec,omitempty"`
VirtualMachineTemplate VirtualMachineTemplateSpec `json:"virtualMachineTemplate,omitempty"`

// ProviderID TBD what to use for Kubevirt
// +optional
Expand Down
25 changes: 23 additions & 2 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6,292 changes: 3,670 additions & 2,622 deletions config/crd/bases/infrastructure.cluster.x-k8s.io_kubevirtmachines.yaml

Large diffs are not rendered by default.

6,951 changes: 4,136 additions & 2,815 deletions config/crd/bases/infrastructure.cluster.x-k8s.io_kubevirtmachinetemplates.yaml

Large diffs are not rendered by default.

31 changes: 21 additions & 10 deletions controllers/kubevirtmachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,18 +202,29 @@ func (r *KubevirtMachineReconciler) reconcileNormal(ctx *context.MachineContext)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Second}, errors.Wrap(err, "failed to generate infra cluster client")
}

// If there is not a namespace explicitly set on the vm template, then
// use the infra namespace as a default. For internal clusters, the infraNamespace
// will be the same as the KubeVirtCluster object, for external clusters the
// infraNamespace will attempt to be detected from the infraClusterSecretRef's
// kubeconfig
vmNamespace := ctx.KubevirtMachine.Spec.VirtualMachineTemplate.ObjectMeta.Namespace
if vmNamespace == "" {
vmNamespace = infraClusterNamespace
}

if infraClusterClient == nil {
ctx.Logger.Info("Waiting for infra cluster client...")
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}

if err := r.reconcileKubevirtBootstrapSecret(ctx, infraClusterClient, infraClusterNamespace, clusterNodeSshKeys); err != nil {
if err := r.reconcileKubevirtBootstrapSecret(ctx, infraClusterClient, vmNamespace, clusterNodeSshKeys); err != nil {
conditions.MarkFalse(ctx.KubevirtMachine, infrav1.VMProvisionedCondition, infrav1.WaitingForBootstrapDataReason, clusterv1.ConditionSeverityInfo, "")
return ctrl.Result{RequeueAfter: 10 * time.Second}, errors.Wrap(err, "failed to fetch kubevirt bootstrap secret")
}

// Create a helper for managing the KubeVirt VM hosting the machine.
externalMachine, err := kubevirthandler.NewMachine(ctx, infraClusterClient, infraClusterNamespace, clusterNodeSshKeys)
externalMachine, err := kubevirthandler.NewMachine(ctx, infraClusterClient, vmNamespace, clusterNodeSshKeys)
if err != nil {
return ctrl.Result{}, errors.Wrapf(err, "failed to create helper for managing the externalMachine")
}
Expand Down Expand Up @@ -328,7 +339,7 @@ func (r *KubevirtMachineReconciler) updateNodeProviderID(ctx *context.MachineCon
}

func (r *KubevirtMachineReconciler) reconcileDelete(ctx *context.MachineContext) (ctrl.Result, error) {
infraClusterClient, infraClusterNamespace, err := r.InfraCluster.GenerateInfraClusterClient(ctx.ClusterContext())
infraClusterClient, vmNamespace, err := r.InfraCluster.GenerateInfraClusterClient(ctx.ClusterContext())
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Second}, errors.Wrap(err, "failed to generate infra cluster client")
}
Expand All @@ -348,12 +359,12 @@ func (r *KubevirtMachineReconciler) reconcileDelete(ctx *context.MachineContext)
}

ctx.Logger.Info("Deleting VM bootstrap secret...")
if err := r.deleteKubevirtBootstrapSecret(ctx, infraClusterClient, infraClusterNamespace); err != nil {
if err := r.deleteKubevirtBootstrapSecret(ctx, infraClusterClient, vmNamespace); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Second}, errors.Wrap(err, "failed to delete bootstrap secret")
}

ctx.Logger.Info("Deleting VM...")
externalMachine, err := kubevirthandler.NewMachine(ctx, infraClusterClient, infraClusterNamespace, clusterNodeSshKeys)
externalMachine, err := kubevirthandler.NewMachine(ctx, infraClusterClient, vmNamespace, clusterNodeSshKeys)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Second}, errors.Wrap(err, "failed to create helper for externalMachine access")
}
Expand Down Expand Up @@ -444,14 +455,14 @@ func (r *KubevirtMachineReconciler) KubevirtClusterToKubevirtMachines(o client.O
}

// reconcileKubevirtBootstrapSecret creates bootstrap cloud-init secret for KubeVirt virtual machines
func (r *KubevirtMachineReconciler) reconcileKubevirtBootstrapSecret(ctx *context.MachineContext, infraClusterClient client.Client, infraClusterNamespace string, sshKeys *ssh.ClusterNodeSshKeys) error {
func (r *KubevirtMachineReconciler) reconcileKubevirtBootstrapSecret(ctx *context.MachineContext, infraClusterClient client.Client, vmNamespace string, sshKeys *ssh.ClusterNodeSshKeys) error {
if ctx.Machine.Spec.Bootstrap.DataSecretName == nil {
return errors.New("error retrieving bootstrap data: linked Machine's bootstrap.dataSecretName is nil")
}

// Exit early if exists.
bootstrapDataSecret := &corev1.Secret{}
bootstrapDataSecretKey := client.ObjectKey{Namespace: infraClusterNamespace, Name: *ctx.Machine.Spec.Bootstrap.DataSecretName + "-userdata"}
bootstrapDataSecretKey := client.ObjectKey{Namespace: vmNamespace, Name: *ctx.Machine.Spec.Bootstrap.DataSecretName + "-userdata"}
if err := infraClusterClient.Get(ctx, bootstrapDataSecretKey, bootstrapDataSecret); err == nil {
ctx.BootstrapDataSecret = bootstrapDataSecret
return nil
Expand All @@ -476,7 +487,7 @@ func (r *KubevirtMachineReconciler) reconcileKubevirtBootstrapSecret(ctx *contex
newBootstrapDataSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name + "-userdata",
Namespace: infraClusterNamespace,
Namespace: vmNamespace,
},
}
ctx.BootstrapDataSecret = newBootstrapDataSecret
Expand All @@ -498,9 +509,9 @@ func (r *KubevirtMachineReconciler) reconcileKubevirtBootstrapSecret(ctx *contex
}

// deleteKubevirtBootstrapSecret deletes bootstrap cloud-init secret for KubeVirt virtual machines
func (r *KubevirtMachineReconciler) deleteKubevirtBootstrapSecret(ctx *context.MachineContext, infraClusterClient client.Client, infraClusterNamespace string) error {
func (r *KubevirtMachineReconciler) deleteKubevirtBootstrapSecret(ctx *context.MachineContext, infraClusterClient client.Client, vmNamespace string) error {
bootstrapDataSecret := &corev1.Secret{}
bootstrapDataSecretKey := client.ObjectKey{Namespace: infraClusterNamespace, Name: *ctx.Machine.Spec.Bootstrap.DataSecretName + "-userdata"}
bootstrapDataSecretKey := client.ObjectKey{Namespace: vmNamespace, Name: *ctx.Machine.Spec.Bootstrap.DataSecretName + "-userdata"}
if err := infraClusterClient.Get(ctx, bootstrapDataSecretKey, bootstrapDataSecret); err != nil {
// the secret does not exist, exit without error
return nil
Expand Down
42 changes: 40 additions & 2 deletions controllers/kubevirtmachine_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import (
. "github.com/onsi/gomega"
"github.com/pkg/errors"
"sigs.k8s.io/cluster-api-provider-kubevirt/pkg/context"
"sigs.k8s.io/cluster-api-provider-kubevirt/pkg/testing"
infraclustermock "sigs.k8s.io/cluster-api-provider-kubevirt/pkg/infracluster/mock"
"sigs.k8s.io/cluster-api-provider-kubevirt/pkg/testing"
workloadclustermock "sigs.k8s.io/cluster-api-provider-kubevirt/pkg/workloadcluster/mock"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
Expand Down Expand Up @@ -262,6 +262,44 @@ var _ = Describe("reconcile a kubevirt machine", func() {
Expect(machineContext.KubevirtMachine.Spec.ProviderID).To(BeNil())
})

It("should create KubeVirt VM in custom namespace", func() {

customNamespace := "custom"
kubevirtMachine.Spec.VirtualMachineTemplate.ObjectMeta.Namespace = customNamespace

objects := []client.Object{
cluster,
kubevirtCluster,
machine,
kubevirtMachine,
sshKeySecret,
bootstrapSecret,
bootstrapUserDataSecret,
}

setupClient(objects)

clusterContext := &context.ClusterContext{Context: machineContext.Context, Cluster: machineContext.Cluster, KubevirtCluster: machineContext.KubevirtCluster, Logger: machineContext.Logger}
infraClusterMock.EXPECT().GenerateInfraClusterClient(clusterContext).Return(fakeClient, cluster.Namespace, nil)

out, err := kubevirtMachineReconciler.reconcileNormal(machineContext)

Expect(err).ShouldNot(HaveOccurred())

// should expect to re-enqueue while waiting for VMI to come online
Expect(out).To(Equal(ctrl.Result{RequeueAfter: 20 * time.Second}))

// should expect VM to be created with expected name
vm := &kubevirtv1.VirtualMachine{}
vmKey := client.ObjectKey{Namespace: customNamespace, Name: kubevirtMachine.Name}
err = fakeClient.Get(gocontext.Background(), vmKey, vm)
Expect(err).NotTo(HaveOccurred())

// Should expect kubevirt machine is still not ready
Expect(machineContext.KubevirtMachine.Status.Ready).To(BeFalse())
Expect(machineContext.KubevirtMachine.Spec.ProviderID).To(BeNil())
})

It("should detect when VMI is ready and mark KubevirtMachine ready", func() {
vmi.Status.Conditions = []kubevirtv1.VirtualMachineInstanceCondition{
{
Expand Down Expand Up @@ -333,7 +371,7 @@ var _ = Describe("updateNodeProviderID", func() {
kubevirtMachineReconciler = KubevirtMachineReconciler{
Client: fakeClient,
WorkloadCluster: workloadClusterMock,
InfraCluster: infraClusterMock,
InfraCluster: infraClusterMock,
}

workloadClusterObjects := []client.Object{
Expand Down
92 changes: 76 additions & 16 deletions pkg/kubevirt/machine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
kubevirtv1 "kubevirt.io/api/core/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
Expand Down Expand Up @@ -57,23 +58,24 @@ var (

logger = zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)).WithName("machine_test")

machineContext = &context.MachineContext{
Context: gocontext.TODO(),
Cluster: cluster,
KubevirtCluster: kubevirtCluster,
Machine: machine,
KubevirtMachine: kubevirtMachine,
BootstrapDataSecret: bootstrapDataSecret,
Logger: logger,
}

fakeClient client.Client
fakeVMCommandExecutor FakeVMCommandExecutor
)

var _ = Describe("Without KubeVirt VM running", func() {
var machineContext *context.MachineContext

BeforeEach(func() {
machineContext = &context.MachineContext{
Context: gocontext.TODO(),
Cluster: cluster,
KubevirtCluster: kubevirtCluster,
Machine: machine,
KubevirtMachine: kubevirtMachine,
BootstrapDataSecret: bootstrapDataSecret,
Logger: logger,
}

objects := []client.Object{
cluster,
kubevirtCluster,
Expand Down Expand Up @@ -139,18 +141,30 @@ var _ = Describe("Without KubeVirt VM running", func() {
Expect(err).NotTo(HaveOccurred())

// read the vm before creation
validateVMNotExist(fakeClient)
validateVMNotExist(fakeClient, machineContext)

err = externalMachine.Create(machineContext.Context)
Expect(err).NotTo(HaveOccurred())

// read the vm before creation
validateVMExist(fakeClient)
validateVMExist(fakeClient, machineContext)
})
})

var _ = Describe("With KubeVirt VM running", func() {
var machineContext *context.MachineContext

BeforeEach(func() {
machineContext = &context.MachineContext{
Context: gocontext.TODO(),
Cluster: cluster,
KubevirtCluster: kubevirtCluster,
Machine: machine,
KubevirtMachine: kubevirtMachine,
BootstrapDataSecret: bootstrapDataSecret,
Logger: logger,
}

virtualMachineInstance.Status.Conditions = []kubevirtv1.VirtualMachineInstanceCondition{
{
Type: kubevirtv1.VirtualMachineInstanceReady,
Expand Down Expand Up @@ -224,17 +238,63 @@ var _ = Describe("With KubeVirt VM running", func() {
Expect(err).NotTo(HaveOccurred())

// read the vm before creation
validateVMNotExist(fakeClient)
validateVMNotExist(fakeClient, machineContext)

err = externalMachine.Create(machineContext.Context)
Expect(err).NotTo(HaveOccurred())

// read the new created vm
validateVMExist(fakeClient)
validateVMExist(fakeClient, machineContext)
})
})

var _ = Describe("util functions", func() {
var machineContext *context.MachineContext

BeforeEach(func() {
machineContext = &context.MachineContext{
Context: gocontext.TODO(),
Cluster: cluster,
KubevirtCluster: kubevirtCluster,
Machine: machine,
KubevirtMachine: kubevirtMachine.DeepCopy(),
BootstrapDataSecret: bootstrapDataSecret,
Logger: logger,
}
})

It("GenerateProviderID should succeed", func() {
dataVolumeTemplates := []kubevirtv1.DataVolumeTemplateSpec{
{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"my": "label"},
Annotations: map[string]string{"my": "annotation"},
Name: "dv1",
},
},
}
volumes := []kubevirtv1.Volume{
{
Name: "test1",
VolumeSource: kubevirtv1.VolumeSource{
DataVolume: &kubevirtv1.DataVolumeSource{
Name: "dv1",
},
},
},
}

machineContext.KubevirtMachine.Spec.VirtualMachineTemplate.Spec.DataVolumeTemplates = dataVolumeTemplates
machineContext.KubevirtMachine.Spec.VirtualMachineTemplate.Spec.Template.Spec.Volumes = volumes

newVM := newVirtualMachineFromKubevirtMachine(machineContext, "default")

Expect(newVM.Spec.DataVolumeTemplates[0].ObjectMeta.Name).To(Equal(kubevirtMachineName + "-dv1"))
Expect(newVM.Spec.Template.Spec.Volumes[0].VolumeSource.DataVolume.Name).To(Equal(kubevirtMachineName + "-dv1"))
})
})

func validateVMNotExist(fakeClient client.Client) {
func validateVMNotExist(fakeClient client.Client, machineContext *context.MachineContext) {
vm := &kubevirtv1.VirtualMachine{}
key := client.ObjectKey{Name: virtualMachineInstance.Name, Namespace: virtualMachineInstance.Namespace}

Expand All @@ -243,7 +303,7 @@ func validateVMNotExist(fakeClient client.Client) {
ExpectWithOffset(1, apierrors.IsNotFound(err)).To(BeTrue())
}

func validateVMExist(fakeClient client.Client) {
func validateVMExist(fakeClient client.Client, machineContext *context.MachineContext) {
vm := &kubevirtv1.VirtualMachine{}
key := client.ObjectKey{Name: virtualMachineInstance.Name, Namespace: virtualMachineInstance.Namespace}

Expand Down
Loading