-
Notifications
You must be signed in to change notification settings - Fork 51
feat: support cdrom hotplug volume #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3687464
697880d
316d5e0
e6ca9bc
9205729
fff2646
76b1927
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ import ( | |
| "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" | ||
| "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| "k8s.io/client-go/rest" | ||
| kubevirtv1 "kubevirt.io/api/core/v1" | ||
|
|
||
|
|
@@ -487,6 +488,178 @@ resource "harvester_virtualmachine" "test-acc-labels" { | |
| }) | ||
| } | ||
|
|
||
| func TestAccVirtualMachine_hotplug_cdrom_volume(t *testing.T) { | ||
| var ( | ||
| testAccImageName = "test-acc-hp-cdrom-img" | ||
| testAccVirtualMachineName = "test-acc-hp-cdrom" | ||
| testAccVirtualMachineNamespace = "default" | ||
| testAccImageResourceName = constants.ResourceTypeImage + "." + testAccImageName | ||
| testAccVirtualMachineResourceName = constants.ResourceTypeVirtualMachine + "." + testAccVirtualMachineName | ||
| vm = &kubevirtv1.VirtualMachine{} | ||
| vmiUid types.UID | ||
| ctx = context.Background() | ||
| ) | ||
| resource.Test(t, resource.TestCase{ | ||
| PreCheck: func() { testAccPreCheck(t) }, | ||
| Providers: testAccProviders, | ||
| CheckDestroy: testAccCheckVirtualMachineDestroy(ctx), | ||
| Steps: []resource.TestStep{ | ||
| { | ||
| Config: fmt.Sprintf(` | ||
| resource "harvester_virtualmachine" "%s" { | ||
| name = "%s" | ||
| namespace = "%s" | ||
|
|
||
| cpu = 1 | ||
| memory = "1Gi" | ||
|
|
||
| run_strategy = "RerunOnFailure" | ||
| machine_type = "q35" | ||
|
|
||
| network_interface { | ||
| name = "default" | ||
| } | ||
|
|
||
| disk { | ||
| name = "rootdisk" | ||
| type = "disk" | ||
| bus = "virtio" | ||
| boot_order = 1 | ||
|
|
||
| container_image_name = "kubevirt/fedora-cloud-container-disk-demo:v0.35.0" | ||
| } | ||
|
|
||
| disk { | ||
| name = "cd1" | ||
| type = "cd-rom" | ||
| bus = "sata" | ||
| boot_order = 2 | ||
| } | ||
| } | ||
| `, | ||
| testAccVirtualMachineName, testAccVirtualMachineName, testAccVirtualMachineNamespace, | ||
| ), | ||
| Check: resource.ComposeAggregateTestCheckFunc( | ||
| testAccVirtualMachineExists(ctx, testAccVirtualMachineResourceName, vm), | ||
| testAccCheckCdRomSpec(ctx, testAccVirtualMachineNamespace, testAccVirtualMachineName, 2, 1), | ||
| func(s *terraform.State) error { | ||
| c, err := testAccProvider.Meta().(*config.Config).K8sClient() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| vmi, err := c.HarvesterClient.KubevirtV1().VirtualMachineInstances(testAccVirtualMachineNamespace).Get(ctx, testAccVirtualMachineName, metav1.GetOptions{}) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| vmiUid = vmi.UID | ||
| return nil | ||
| }, | ||
| ), | ||
| }, | ||
| { | ||
| Config: fmt.Sprintf(` | ||
| resource harvester_image "%s" { | ||
| name = "%s" | ||
| namespace = "%s" | ||
| display_name = "%s" | ||
| source_type = "download" | ||
| url = "https://distro.ibiblio.org/tinycorelinux/16.x/x86/release/TinyCore-current.iso" | ||
| storage_class_name = "harvester-longhorn" | ||
| } | ||
|
|
||
| resource "harvester_virtualmachine" "%s" { | ||
| name = "%s" | ||
| namespace = "%s" | ||
|
|
||
| cpu = 1 | ||
| memory = "1Gi" | ||
|
|
||
| run_strategy = "RerunOnFailure" | ||
| machine_type = "q35" | ||
|
|
||
| network_interface { | ||
| name = "default" | ||
| } | ||
|
|
||
| disk { | ||
| name = "rootdisk" | ||
| type = "disk" | ||
| bus = "virtio" | ||
| boot_order = 1 | ||
|
|
||
| container_image_name = "kubevirt/fedora-cloud-container-disk-demo:v0.35.0" | ||
| } | ||
|
|
||
| disk { | ||
| name = "cd1" | ||
| type = "cd-rom" | ||
| bus = "sata" | ||
| boot_order = 2 | ||
|
|
||
| size = "1Gi" | ||
| hot_plug = true | ||
| image = "%s/%s" | ||
| auto_delete = true | ||
| } | ||
| } | ||
| `, | ||
| testAccImageName, testAccImageName, testAccVirtualMachineNamespace, testAccImageName, | ||
| testAccVirtualMachineName, testAccVirtualMachineName, testAccVirtualMachineNamespace, | ||
| testAccVirtualMachineNamespace, testAccImageName, | ||
| ), | ||
| Check: resource.ComposeAggregateTestCheckFunc( | ||
| resource.TestCheckResourceAttr(testAccImageResourceName, constants.FieldCommonName, "test-acc-hp-cdrom-img"), | ||
| resource.TestCheckResourceAttr(testAccImageResourceName, constants.FieldCommonNamespace, "default"), | ||
| testAccVirtualMachineExists(ctx, testAccVirtualMachineResourceName, vm), | ||
| testAccCheckCdRomSpec(ctx, testAccVirtualMachineNamespace, testAccVirtualMachineName, 2, 2), | ||
| testAccCheckVmiUid(ctx, testAccVirtualMachineNamespace, testAccVirtualMachineName, &vmiUid), | ||
| ), | ||
| }, | ||
| { | ||
| Config: fmt.Sprintf(` | ||
| resource "harvester_virtualmachine" "%s" { | ||
| name = "%s" | ||
| namespace = "%s" | ||
|
|
||
| cpu = 1 | ||
| memory = "1Gi" | ||
|
|
||
| run_strategy = "RerunOnFailure" | ||
| machine_type = "q35" | ||
|
|
||
| network_interface { | ||
| name = "default" | ||
| } | ||
|
|
||
| disk { | ||
| name = "rootdisk" | ||
| type = "disk" | ||
| bus = "virtio" | ||
| boot_order = 1 | ||
|
|
||
| container_image_name = "kubevirt/fedora-cloud-container-disk-demo:v0.35.0" | ||
| } | ||
|
|
||
| disk { | ||
| name = "cd1" | ||
| type = "cd-rom" | ||
| bus = "sata" | ||
| boot_order = 2 | ||
| } | ||
| } | ||
| `, | ||
| testAccVirtualMachineName, testAccVirtualMachineName, testAccVirtualMachineNamespace, | ||
| ), | ||
| Check: resource.ComposeAggregateTestCheckFunc( | ||
| testAccVirtualMachineExists(ctx, testAccVirtualMachineResourceName, vm), | ||
| testAccCheckCdRomSpec(ctx, testAccVirtualMachineNamespace, testAccVirtualMachineName, 2, 1), | ||
| testAccCheckVmiUid(ctx, testAccVirtualMachineNamespace, testAccVirtualMachineName, &vmiUid), | ||
| ), | ||
| }, | ||
| }, | ||
| }) | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although we got the "Complex Method" notice from CodeFactor, it's intentional to have
Therefore, I think we can ignore this warning from CodeFactor. |
||
|
|
||
| func testAccVirtualMachineExists(ctx context.Context, n string, vm *kubevirtv1.VirtualMachine) resource.TestCheckFunc { | ||
| return func(s *terraform.State) error { | ||
| foundVM, err := testAccGetVirtualMachine(ctx, s, n) | ||
|
|
@@ -498,6 +671,46 @@ func testAccVirtualMachineExists(ctx context.Context, n string, vm *kubevirtv1.V | |
| } | ||
| } | ||
|
|
||
| func testAccCheckCdRomSpec(ctx context.Context, vmNamespace, vmName string, expectedDisksCnt, expectedVolumeCnts int) resource.TestCheckFunc { | ||
| return func(s *terraform.State) error { | ||
| c, err := testAccProvider.Meta().(*config.Config).K8sClient() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| vm, err := c.HarvesterClient.KubevirtV1().VirtualMachines(vmNamespace).Get(ctx, vmName, metav1.GetOptions{}) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| disksCnt := len(vm.Spec.Template.Spec.Domain.Devices.Disks) | ||
| if disksCnt != expectedDisksCnt { | ||
| return fmt.Errorf("Should have %d disk devices but got %d", expectedDisksCnt, disksCnt) | ||
| } | ||
| volumeCnts := len(vm.Spec.Template.Spec.Volumes) | ||
| if volumeCnts != expectedVolumeCnts { | ||
| return fmt.Errorf("Should have %d volumes but got %d", expectedVolumeCnts, volumeCnts) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
| } | ||
|
|
||
| func testAccCheckVmiUid(ctx context.Context, vmNamespace, vmName string, vmiUid *types.UID) resource.TestCheckFunc { | ||
| return func(s *terraform.State) error { | ||
| c, err := testAccProvider.Meta().(*config.Config).K8sClient() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| vmi, err := c.HarvesterClient.KubevirtV1().VirtualMachineInstances(vmNamespace).Get(ctx, vmName, metav1.GetOptions{}) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if vmi.UID != *vmiUid { | ||
| return fmt.Errorf("Shouldn't trigger VMI re-creation. Expected: %s, Got: %s", *vmiUid, vmi.UID) | ||
| } | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| func testAccVirtualMachineLabels(ctx context.Context, n string, labels map[string]string) resource.TestCheckFunc { | ||
| return func(s *terraform.State) error { | ||
| vm, err := testAccGetVirtualMachine(ctx, s, n) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -267,47 +267,51 @@ func (v *VMImporter) Volume() ([]map[string]interface{}, []map[string]interface{ | |
| var ( | ||
| disks = v.VirtualMachine.Spec.Template.Spec.Domain.Devices.Disks | ||
| volumes = v.VirtualMachine.Spec.Template.Spec.Volumes | ||
| volumesMap = make(map[string]kubevirtv1.Volume, len(volumes)) | ||
| cloudInitState = make([]map[string]interface{}, 0, 1) | ||
| diskStates = make([]map[string]interface{}, 0, len(disks)) | ||
| ) | ||
|
|
||
| for _, volume := range volumes { | ||
| volumesMap[volume.Name] = volume | ||
| } | ||
|
|
||
| for _, disk := range disks { | ||
| diskState := make(map[string]interface{}) | ||
| for _, disk := range disks { | ||
| if volume.Name != disk.Name { | ||
| continue | ||
| } | ||
|
Comment on lines
-276
to
-278
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this fixed the missing hotplug disk issue, right? The original behavior is iterating over volumes, and skip disk if its name not match the volume name. That could be a problem since hotplug empty cd-rom is not defined in the volume section.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly! This change is for empty cdrom. |
||
| var ( | ||
| diskType string | ||
| diskBus string | ||
| ) | ||
| if disk.CDRom != nil { | ||
| diskType = builder.DiskTypeCDRom | ||
| diskBus = string(disk.CDRom.Bus) | ||
| } else if disk.Disk != nil { | ||
| diskType = builder.DiskTypeDisk | ||
| diskBus = string(disk.Disk.Bus) | ||
| } else { | ||
| return nil, nil, fmt.Errorf("unsupported volume type found on volume %s. ", disk.Name) | ||
| } | ||
| diskState[constants.FieldDiskName] = disk.Name | ||
| diskState[constants.FieldDiskBootOrder] = disk.BootOrder | ||
| diskState[constants.FieldDiskType] = diskType | ||
| diskState[constants.FieldDiskBus] = diskBus | ||
| } | ||
| if volume.CloudInitNoCloud != nil || volume.CloudInitConfigDrive != nil { | ||
| cloudInitState = v.cloudInit(volume) | ||
| var ( | ||
| diskType string | ||
| diskBus string | ||
| ) | ||
| if disk.CDRom != nil { | ||
| diskType = builder.DiskTypeCDRom | ||
| diskBus = string(disk.CDRom.Bus) | ||
| } else if disk.Disk != nil { | ||
| diskType = builder.DiskTypeDisk | ||
| diskBus = string(disk.Disk.Bus) | ||
| } else { | ||
| if volume.PersistentVolumeClaim != nil { | ||
| if err := v.pvcVolume(volume, diskState); err != nil { | ||
| return nil, nil, err | ||
| } | ||
| } else if volume.ContainerDisk != nil { | ||
| diskState[constants.FieldDiskContainerImageName] = volume.ContainerDisk.Image | ||
| return nil, nil, fmt.Errorf("unsupported volume type found on volume %s. ", disk.Name) | ||
| } | ||
| diskState[constants.FieldDiskName] = disk.Name | ||
| diskState[constants.FieldDiskBootOrder] = disk.BootOrder | ||
| diskState[constants.FieldDiskType] = diskType | ||
| diskState[constants.FieldDiskBus] = diskBus | ||
|
|
||
| if volume, hasVolume := volumesMap[disk.Name]; hasVolume { | ||
| if volume.CloudInitNoCloud != nil || volume.CloudInitConfigDrive != nil { | ||
| cloudInitState = v.cloudInit(volume) | ||
| } else { | ||
| return nil, nil, fmt.Errorf("unsupported volume type found on volume %s. ", volume.Name) | ||
| if volume.PersistentVolumeClaim != nil { | ||
| if err := v.pvcVolume(volume, diskState); err != nil { | ||
| return nil, nil, err | ||
| } | ||
| } else if volume.ContainerDisk != nil { | ||
| diskState[constants.FieldDiskContainerImageName] = volume.ContainerDisk.Image | ||
| } else { | ||
| return nil, nil, fmt.Errorf("unsupported volume type found on volume %s. ", volume.Name) | ||
| } | ||
| } | ||
| diskStates = append(diskStates, diskState) | ||
| } | ||
| diskStates = append(diskStates, diskState) | ||
| } | ||
| return diskStates, cloudInitState, nil | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you please explain a bit more about this? Is this related to cdrom hotplug?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is important for hotplug actions (not just for the cdrom hotplug but also for the existing hotplug volume). Without this change, the updated VM will have different firmware
serialanduuid, causing theRestartRequiredcondition.Take this example using binary from master branch (hotplug volume),
For cdrom hotplug, check the integration test.
This change fixed it by preserving the old firmware's
uuidandserial.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation!