mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-04 12:32:59 +00:00
fix(vm): handle update of disks moved during VM clone (#1849)
* fix(vm): handle update of disks moved during VM clone Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
1eee8cdbd0
commit
09d3e97d02
1
.github/workflows/golangci-lint.yml
vendored
1
.github/workflows/golangci-lint.yml
vendored
@ -43,4 +43,5 @@ jobs:
|
|||||||
uses: golangci/golangci-lint-action@v7
|
uses: golangci/golangci-lint-action@v7
|
||||||
with:
|
with:
|
||||||
version: v2.0.2 # renovate: depName=golangci/golangci-lint datasource=github-releases
|
version: v2.0.2 # renovate: depName=golangci/golangci-lint datasource=github-releases
|
||||||
|
skip-cache: true
|
||||||
args: -v --timeout=10m
|
args: -v --timeout=10m
|
||||||
|
3
Makefile
3
Makefile
@ -112,7 +112,8 @@ testacc:
|
|||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
# NOTE: This target is for local runs only. For linting in CI see .github/workflows/golangci-lint.yml
|
# NOTE: This target is for local runs only. For linting in CI see .github/workflows/golangci-lint.yml
|
||||||
@docker run -t --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$(GOLANGCI_LINT_VERSION):/root/.cache -w /app golangci/golangci-lint:$(GOLANGCI_LINT_VERSION) golangci-lint format run --fix
|
@docker run -t --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$(GOLANGCI_LINT_VERSION):/root/.cache -w /app golangci/golangci-lint:$(GOLANGCI_LINT_VERSION) golangci-lint fmt
|
||||||
|
@docker run -t --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$(GOLANGCI_LINT_VERSION):/root/.cache -w /app golangci/golangci-lint:$(GOLANGCI_LINT_VERSION) golangci-lint run
|
||||||
|
|
||||||
.PHONY: release-build
|
.PHONY: release-build
|
||||||
release-build:
|
release-build:
|
||||||
|
@ -422,7 +422,7 @@ func TestAccResourceVMDisks(t *testing.T) {
|
|||||||
|
|
||||||
disk {
|
disk {
|
||||||
interface = "scsi0"
|
interface = "scsi0"
|
||||||
//size = 10
|
size = 10
|
||||||
}
|
}
|
||||||
}`),
|
}`),
|
||||||
Check: ResourceAttributes("proxmox_virtual_environment_vm.test_disk", map[string]string{
|
Check: ResourceAttributes("proxmox_virtual_environment_vm.test_disk", map[string]string{
|
||||||
@ -432,7 +432,7 @@ func TestAccResourceVMDisks(t *testing.T) {
|
|||||||
"disk.0.interface": "scsi0",
|
"disk.0.interface": "scsi0",
|
||||||
"disk.0.iothread": "true",
|
"disk.0.iothread": "true",
|
||||||
"disk.0.path_in_datastore": `base-\d+-disk-\d+`,
|
"disk.0.path_in_datastore": `base-\d+-disk-\d+`,
|
||||||
"disk.0.size": "8",
|
"disk.0.size": "10",
|
||||||
"disk.0.ssd": "true",
|
"disk.0.ssd": "true",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -535,6 +535,85 @@ func TestAccResourceVMDisks(t *testing.T) {
|
|||||||
RefreshState: true,
|
RefreshState: true,
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
|
{"clone with updating disk attributes", []resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_vm" "template" {
|
||||||
|
node_name = "{{.NodeName}}"
|
||||||
|
started = false
|
||||||
|
disk {
|
||||||
|
datastore_id = "local-lvm"
|
||||||
|
interface = "virtio0"
|
||||||
|
file_format = "raw"
|
||||||
|
size = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "proxmox_virtual_environment_vm" "clone" {
|
||||||
|
node_name = "{{.NodeName}}"
|
||||||
|
started = false
|
||||||
|
clone {
|
||||||
|
vm_id = proxmox_virtual_environment_vm.template.vm_id
|
||||||
|
}
|
||||||
|
disk {
|
||||||
|
datastore_id = "local-lvm"
|
||||||
|
interface = "virtio0"
|
||||||
|
iothread = true
|
||||||
|
discard = "on"
|
||||||
|
size = 30
|
||||||
|
speed {
|
||||||
|
iops_read = 100
|
||||||
|
iops_read_burstable = 1000
|
||||||
|
iops_write = 400
|
||||||
|
iops_write_burstable = 800
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
ResourceAttributes("proxmox_virtual_environment_vm.clone", map[string]string{
|
||||||
|
"disk.0.iothread": "true",
|
||||||
|
"disk.0.discard": "on",
|
||||||
|
"disk.0.size": "30",
|
||||||
|
"disk.0.speed.0.iops_read": "100",
|
||||||
|
"disk.0.speed.0.iops_read_burstable": "1000",
|
||||||
|
"disk.0.speed.0.iops_write": "400",
|
||||||
|
"disk.0.speed.0.iops_write_burstable": "800",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}}},
|
||||||
|
{"clone with moving disk", []resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_vm" "template" {
|
||||||
|
node_name = "{{.NodeName}}"
|
||||||
|
started = false
|
||||||
|
efi_disk {
|
||||||
|
datastore_id = "local-lvm"
|
||||||
|
type = "4m"
|
||||||
|
}
|
||||||
|
disk {
|
||||||
|
datastore_id = "local-lvm"
|
||||||
|
interface = "virtio0"
|
||||||
|
file_format = "raw"
|
||||||
|
size = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "proxmox_virtual_environment_vm" "clone" {
|
||||||
|
node_name = "{{.NodeName}}"
|
||||||
|
started = false
|
||||||
|
clone {
|
||||||
|
vm_id = proxmox_virtual_environment_vm.template.vm_id
|
||||||
|
}
|
||||||
|
disk {
|
||||||
|
datastore_id = "tank"
|
||||||
|
interface = "virtio0"
|
||||||
|
file_format = "raw"
|
||||||
|
size = 20
|
||||||
|
}
|
||||||
|
}`),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
ResourceAttributes("proxmox_virtual_environment_vm.clone", map[string]string{
|
||||||
|
"disk.0.datastore_id": "tank",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -704,50 +704,6 @@ func TestAccResourceVMClone(t *testing.T) {
|
|||||||
}`),
|
}`),
|
||||||
ExpectError: regexp.MustCompile(`storage 'doesnotexist' does not exist`),
|
ExpectError: regexp.MustCompile(`storage 'doesnotexist' does not exist`),
|
||||||
}}},
|
}}},
|
||||||
{"update disk speed and resize in a clone", []resource.TestStep{{
|
|
||||||
Config: te.RenderConfig(`
|
|
||||||
resource "proxmox_virtual_environment_vm" "template" {
|
|
||||||
node_name = "{{.NodeName}}"
|
|
||||||
started = false
|
|
||||||
disk {
|
|
||||||
datastore_id = "local-lvm"
|
|
||||||
interface = "virtio0"
|
|
||||||
file_format = "raw"
|
|
||||||
size = 20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resource "proxmox_virtual_environment_vm" "clone" {
|
|
||||||
node_name = "{{.NodeName}}"
|
|
||||||
started = false
|
|
||||||
clone {
|
|
||||||
vm_id = proxmox_virtual_environment_vm.template.vm_id
|
|
||||||
}
|
|
||||||
disk {
|
|
||||||
datastore_id = "local-lvm"
|
|
||||||
interface = "virtio0"
|
|
||||||
iothread = true
|
|
||||||
discard = "on"
|
|
||||||
size = 30
|
|
||||||
speed {
|
|
||||||
iops_read = 100
|
|
||||||
iops_read_burstable = 1000
|
|
||||||
iops_write = 400
|
|
||||||
iops_write_burstable = 800
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`),
|
|
||||||
Check: resource.ComposeTestCheckFunc(
|
|
||||||
ResourceAttributes("proxmox_virtual_environment_vm.clone", map[string]string{
|
|
||||||
"disk.0.iothread": "true",
|
|
||||||
"disk.0.discard": "on",
|
|
||||||
"disk.0.size": "30",
|
|
||||||
"disk.0.speed.0.iops_read": "100",
|
|
||||||
"disk.0.speed.0.iops_read_burstable": "1000",
|
|
||||||
"disk.0.speed.0.iops_write": "400",
|
|
||||||
"disk.0.speed.0.iops_write_burstable": "800",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}}},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -25,13 +25,17 @@ var StorageInterfaces = []string{"ide", "sata", "scsi", "virtio"}
|
|||||||
|
|
||||||
// CustomStorageDevice handles QEMU SATA device parameters.
|
// CustomStorageDevice handles QEMU SATA device parameters.
|
||||||
type CustomStorageDevice struct {
|
type CustomStorageDevice struct {
|
||||||
|
// FileVolume is the path to the storage device in format
|
||||||
|
// "STORAGE_ID:SIZE_IN_GiB" or "STORAGE_ID:PATH_TO_FILE".
|
||||||
|
// This is a required field.
|
||||||
|
FileVolume string `json:"file" url:"file"`
|
||||||
|
|
||||||
AIO *string `json:"aio,omitempty" url:"aio,omitempty"`
|
AIO *string `json:"aio,omitempty" url:"aio,omitempty"`
|
||||||
Backup *types.CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"`
|
Backup *types.CustomBool `json:"backup,omitempty" url:"backup,omitempty,int"`
|
||||||
BurstableReadSpeedMbps *int `json:"mbps_rd_max,omitempty" url:"mbps_rd_max,omitempty"`
|
BurstableReadSpeedMbps *int `json:"mbps_rd_max,omitempty" url:"mbps_rd_max,omitempty"`
|
||||||
BurstableWriteSpeedMbps *int `json:"mbps_wr_max,omitempty" url:"mbps_wr_max,omitempty"`
|
BurstableWriteSpeedMbps *int `json:"mbps_wr_max,omitempty" url:"mbps_wr_max,omitempty"`
|
||||||
Cache *string `json:"cache,omitempty" url:"cache,omitempty"`
|
Cache *string `json:"cache,omitempty" url:"cache,omitempty"`
|
||||||
Discard *string `json:"discard,omitempty" url:"discard,omitempty"`
|
Discard *string `json:"discard,omitempty" url:"discard,omitempty"`
|
||||||
FileVolume string `json:"file" url:"file"`
|
|
||||||
Format *string `json:"format,omitempty" url:"format,omitempty"`
|
Format *string `json:"format,omitempty" url:"format,omitempty"`
|
||||||
IopsRead *int `json:"iops_rd,omitempty" url:"iops_rd,omitempty"`
|
IopsRead *int `json:"iops_rd,omitempty" url:"iops_rd,omitempty"`
|
||||||
IopsWrite *int `json:"iops_wr,omitempty" url:"iops_wr,omitempty"`
|
IopsWrite *int `json:"iops_wr,omitempty" url:"iops_wr,omitempty"`
|
||||||
|
@ -80,7 +80,7 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}
|
|||||||
csrfPreventionToken = v.(string)
|
csrfPreventionToken = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:staticcheck //using GetOkExists to check if the value is set, as it can be an empty string in tests
|
//nolint:staticcheck
|
||||||
if v, ok := d.GetOkExists(mkProviderAPIToken); ok {
|
if v, ok := d.GetOkExists(mkProviderAPIToken); ok {
|
||||||
apiToken = v.(string)
|
apiToken = v.(string)
|
||||||
}
|
}
|
||||||
@ -89,12 +89,12 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}
|
|||||||
otp = v.(string)
|
otp = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
///nolint:staticcheck //using GetOkExists to check if the value is set, as it can be an empty string in tests
|
///nolint:staticcheck
|
||||||
if v, ok := d.GetOkExists(mkProviderUsername); ok {
|
if v, ok := d.GetOkExists(mkProviderUsername); ok {
|
||||||
username = v.(string)
|
username = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:staticcheck //using GetOkExists to check if the value is set, as it can be an empty string in tests
|
//nolint:staticcheck
|
||||||
if v, ok := d.GetOkExists(mkProviderPassword); ok {
|
if v, ok := d.GetOkExists(mkProviderPassword); ok {
|
||||||
password = v.(string)
|
password = v.(string)
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,17 @@ func UpdateClone(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update other disk parameters
|
||||||
|
// we have to do it before moving the disk, because the disk volume and location may change
|
||||||
|
if currentDisk.MergeWith(*planDisk) {
|
||||||
|
diskUpdateBody := &vms.UpdateRequestBody{}
|
||||||
|
diskUpdateBody.AddCustomStorageDevice(diskInterface, *currentDisk)
|
||||||
|
|
||||||
|
if err := vmAPI.UpdateVM(ctx, diskUpdateBody); err != nil {
|
||||||
|
return fmt.Errorf("disk update fails: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
moveDisk := false
|
moveDisk := false
|
||||||
|
|
||||||
if *planDisk.DatastoreID != "" {
|
if *planDisk.DatastoreID != "" {
|
||||||
@ -98,6 +109,8 @@ func UpdateClone(
|
|||||||
TargetStorage: *planDisk.DatastoreID,
|
TargetStorage: *planDisk.DatastoreID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: after disk move, the actual disk volume ID will be different: both datastore id *and*
|
||||||
|
// path in datastore will change.
|
||||||
err := vmAPI.MoveVMDisk(ctx, diskMoveBody)
|
err := vmAPI.MoveVMDisk(ctx, diskMoveBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("disk move fails: %w", err)
|
return fmt.Errorf("disk move fails: %w", err)
|
||||||
@ -115,16 +128,6 @@ func UpdateClone(
|
|||||||
return fmt.Errorf("disk resize fails: %w", err)
|
return fmt.Errorf("disk resize fails: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update other disk parameters
|
|
||||||
if currentDisk.MergeWith(*planDisk) {
|
|
||||||
diskUpdateBody := &vms.UpdateRequestBody{}
|
|
||||||
diskUpdateBody.AddCustomStorageDevice(diskInterface, *currentDisk)
|
|
||||||
|
|
||||||
if err := vmAPI.UpdateVM(ctx, diskUpdateBody); err != nil {
|
|
||||||
return fmt.Errorf("disk update fails: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
Loading…
Reference in New Issue
Block a user