Skip to content

Commit e5b2771

Browse files
authored
fix(vm): replace enumerated network and IP config fields with dynamic unmarshaling (#2679)
Signed-off-by: Pavel Boldyrev <pavel@bpg.sh>
1 parent 65b809d commit e5b2771

File tree

8 files changed

+79
-176
lines changed

8 files changed

+79
-176
lines changed

fwprovider/nodes/clonedvm/resource.go

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"context"
1111
"errors"
1212
"fmt"
13-
"reflect"
1413
"strconv"
1514
"strings"
1615
"time"
@@ -976,27 +975,7 @@ func readDiskSlot(config *vms.GetResponseData, slot string, current DiskModel) D
976975
}
977976

978977
func networkDeviceBySlot(config *vms.GetResponseData, slot string) *vms.CustomNetworkDevice {
979-
idx, ok := slotIndex(slot, "net")
980-
if !ok {
981-
return nil
982-
}
983-
984-
val := reflect.ValueOf(config)
985-
if val.Kind() == reflect.Pointer {
986-
val = val.Elem()
987-
}
988-
989-
field := val.FieldByName(fmt.Sprintf("NetworkDevice%d", idx))
990-
if !field.IsValid() || field.IsNil() {
991-
return nil
992-
}
993-
994-
device, ok := field.Interface().(*vms.CustomNetworkDevice)
995-
if !ok {
996-
return nil
997-
}
998-
999-
return device
978+
return config.NetworkDevices[slot]
1000979
}
1001980

1002981
func slotIndex(slot string, prefix string) (int, bool) {

fwprovider/nodes/clonedvm/resource_test.go

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ package clonedvm_test
1111
import (
1212
"context"
1313
"fmt"
14-
"reflect"
1514
"strconv"
1615
"strings"
1716
"testing"
@@ -670,22 +669,7 @@ func checkNetworkSlot(te *test.Environment, resourceName, slot string, expected
670669
}
671670

672671
func networkSlotPresent(config *vms.GetResponseData, slot string) bool {
673-
idx, ok := slotIndex(slot, "net")
674-
if !ok {
675-
return false
676-
}
677-
678-
val := reflect.ValueOf(config)
679-
if val.Kind() == reflect.Pointer {
680-
val = val.Elem()
681-
}
682-
683-
field := val.FieldByName(fmt.Sprintf("NetworkDevice%d", idx))
684-
if !field.IsValid() || field.IsNil() {
685-
return false
686-
}
687-
688-
return true
672+
return config.NetworkDevices[slot] != nil
689673
}
690674

691675
func checkMemoryConfig(

proxmox/nodes/vms/custom_cloud_init.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type CustomCloudInitIPConfig struct {
4343
IPv6 *string `json:"ip6,omitempty" url:"ip6,omitempty"`
4444
}
4545

46+
// CustomCloudInitIPConfigMap is a map of CustomCloudInitIPConfig per IP config key (e.g. "ipconfig0").
47+
type CustomCloudInitIPConfigMap map[string]*CustomCloudInitIPConfig
48+
4649
// CustomCloudInitSSHKeys handles QEMU cloud-init SSH keys parameters.
4750
type CustomCloudInitSSHKeys []string
4851

proxmox/nodes/vms/custom_network_device.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ type CustomNetworkDevice struct {
3535
// CustomNetworkDevices handles QEMU network device parameters.
3636
type CustomNetworkDevices []CustomNetworkDevice
3737

38+
// CustomNetworkDeviceMap is a map of CustomNetworkDevice per network device key (e.g. "net0").
39+
type CustomNetworkDeviceMap map[string]*CustomNetworkDevice
40+
3841
// EncodeValues converts a CustomNetworkDevice struct to a URL value.
3942
func (r *CustomNetworkDevice) EncodeValues(key string, v *url.Values) error {
4043
values := []string{

proxmox/nodes/vms/vms_types.go

Lines changed: 26 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ var (
2727
regexPCIDevice = regexp.MustCompile(`^hostpci\d+$`)
2828
// regexVirtiofsShare is a regex pattern for matching virtiofs share names.
2929
regexVirtiofsShare = regexp.MustCompile(`^virtiofs\d+$`)
30+
// regexNetworkDevice is a regex pattern for matching network device names.
31+
regexNetworkDevice = regexp.MustCompile(`^net\d+$`)
32+
// regexIPConfig is a regex pattern for matching cloud-init IP config names.
33+
regexIPConfig = regexp.MustCompile(`^ipconfig\d+$`)
3034
)
3135

3236
// WaitForIPConfig specifies which IP address types to wait for when waiting for network interfaces.
@@ -229,38 +233,7 @@ type GetResponseData struct {
229233
HookScript *string `json:"hookscript,omitempty"`
230234
Hotplug *types.CustomCommaSeparatedList `json:"hotplug,omitempty"`
231235
Hugepages *string `json:"hugepages,omitempty"`
232-
IPConfig0 *CustomCloudInitIPConfig `json:"ipconfig0,omitempty"`
233-
IPConfig1 *CustomCloudInitIPConfig `json:"ipconfig1,omitempty"`
234-
IPConfig2 *CustomCloudInitIPConfig `json:"ipconfig2,omitempty"`
235-
IPConfig3 *CustomCloudInitIPConfig `json:"ipconfig3,omitempty"`
236-
IPConfig4 *CustomCloudInitIPConfig `json:"ipconfig4,omitempty"`
237-
IPConfig5 *CustomCloudInitIPConfig `json:"ipconfig5,omitempty"`
238-
IPConfig6 *CustomCloudInitIPConfig `json:"ipconfig6,omitempty"`
239-
IPConfig7 *CustomCloudInitIPConfig `json:"ipconfig7,omitempty"`
240-
IPConfig8 *CustomCloudInitIPConfig `json:"ipconfig8,omitempty"`
241-
IPConfig9 *CustomCloudInitIPConfig `json:"ipconfig9,omitempty"`
242-
IPConfig10 *CustomCloudInitIPConfig `json:"ipconfig10,omitempty"`
243-
IPConfig11 *CustomCloudInitIPConfig `json:"ipconfig11,omitempty"`
244-
IPConfig12 *CustomCloudInitIPConfig `json:"ipconfig12,omitempty"`
245-
IPConfig13 *CustomCloudInitIPConfig `json:"ipconfig13,omitempty"`
246-
IPConfig14 *CustomCloudInitIPConfig `json:"ipconfig14,omitempty"`
247-
IPConfig15 *CustomCloudInitIPConfig `json:"ipconfig15,omitempty"`
248-
IPConfig16 *CustomCloudInitIPConfig `json:"ipconfig16,omitempty"`
249-
IPConfig17 *CustomCloudInitIPConfig `json:"ipconfig17,omitempty"`
250-
IPConfig18 *CustomCloudInitIPConfig `json:"ipconfig18,omitempty"`
251-
IPConfig19 *CustomCloudInitIPConfig `json:"ipconfig19,omitempty"`
252-
IPConfig20 *CustomCloudInitIPConfig `json:"ipconfig20,omitempty"`
253-
IPConfig21 *CustomCloudInitIPConfig `json:"ipconfig21,omitempty"`
254-
IPConfig22 *CustomCloudInitIPConfig `json:"ipconfig22,omitempty"`
255-
IPConfig23 *CustomCloudInitIPConfig `json:"ipconfig23,omitempty"`
256-
IPConfig24 *CustomCloudInitIPConfig `json:"ipconfig24,omitempty"`
257-
IPConfig25 *CustomCloudInitIPConfig `json:"ipconfig25,omitempty"`
258-
IPConfig26 *CustomCloudInitIPConfig `json:"ipconfig26,omitempty"`
259-
IPConfig27 *CustomCloudInitIPConfig `json:"ipconfig27,omitempty"`
260-
IPConfig28 *CustomCloudInitIPConfig `json:"ipconfig28,omitempty"`
261-
IPConfig29 *CustomCloudInitIPConfig `json:"ipconfig29,omitempty"`
262-
IPConfig30 *CustomCloudInitIPConfig `json:"ipconfig30,omitempty"`
263-
IPConfig31 *CustomCloudInitIPConfig `json:"ipconfig31,omitempty"`
236+
IPConfigs CustomCloudInitIPConfigMap `json:"-"`
264237
KeepHugepages *types.CustomBool `json:"keephugepages,omitempty"`
265238
KeyboardLayout *string `json:"keyboard,omitempty"`
266239
KVMArguments *string `json:"args,omitempty"`
@@ -271,38 +244,7 @@ type GetResponseData struct {
271244
MigrateDowntime *float64 `json:"migrate_downtime,omitempty"`
272245
MigrateSpeed *int `json:"migrate_speed,omitempty"`
273246
Name *string `json:"name,omitempty"`
274-
NetworkDevice0 *CustomNetworkDevice `json:"net0,omitempty"`
275-
NetworkDevice1 *CustomNetworkDevice `json:"net1,omitempty"`
276-
NetworkDevice2 *CustomNetworkDevice `json:"net2,omitempty"`
277-
NetworkDevice3 *CustomNetworkDevice `json:"net3,omitempty"`
278-
NetworkDevice4 *CustomNetworkDevice `json:"net4,omitempty"`
279-
NetworkDevice5 *CustomNetworkDevice `json:"net5,omitempty"`
280-
NetworkDevice6 *CustomNetworkDevice `json:"net6,omitempty"`
281-
NetworkDevice7 *CustomNetworkDevice `json:"net7,omitempty"`
282-
NetworkDevice8 *CustomNetworkDevice `json:"net8,omitempty"`
283-
NetworkDevice9 *CustomNetworkDevice `json:"net9,omitempty"`
284-
NetworkDevice10 *CustomNetworkDevice `json:"net10,omitempty"`
285-
NetworkDevice11 *CustomNetworkDevice `json:"net11,omitempty"`
286-
NetworkDevice12 *CustomNetworkDevice `json:"net12,omitempty"`
287-
NetworkDevice13 *CustomNetworkDevice `json:"net13,omitempty"`
288-
NetworkDevice14 *CustomNetworkDevice `json:"net14,omitempty"`
289-
NetworkDevice15 *CustomNetworkDevice `json:"net15,omitempty"`
290-
NetworkDevice16 *CustomNetworkDevice `json:"net16,omitempty"`
291-
NetworkDevice17 *CustomNetworkDevice `json:"net17,omitempty"`
292-
NetworkDevice18 *CustomNetworkDevice `json:"net18,omitempty"`
293-
NetworkDevice19 *CustomNetworkDevice `json:"net19,omitempty"`
294-
NetworkDevice20 *CustomNetworkDevice `json:"net20,omitempty"`
295-
NetworkDevice21 *CustomNetworkDevice `json:"net21,omitempty"`
296-
NetworkDevice22 *CustomNetworkDevice `json:"net22,omitempty"`
297-
NetworkDevice23 *CustomNetworkDevice `json:"net23,omitempty"`
298-
NetworkDevice24 *CustomNetworkDevice `json:"net24,omitempty"`
299-
NetworkDevice25 *CustomNetworkDevice `json:"net25,omitempty"`
300-
NetworkDevice26 *CustomNetworkDevice `json:"net26,omitempty"`
301-
NetworkDevice27 *CustomNetworkDevice `json:"net27,omitempty"`
302-
NetworkDevice28 *CustomNetworkDevice `json:"net28,omitempty"`
303-
NetworkDevice29 *CustomNetworkDevice `json:"net29,omitempty"`
304-
NetworkDevice30 *CustomNetworkDevice `json:"net30,omitempty"`
305-
NetworkDevice31 *CustomNetworkDevice `json:"net31,omitempty"`
247+
NetworkDevices CustomNetworkDeviceMap `json:"-"`
306248
NUMAEnabled *types.CustomBool `json:"numa,omitempty"`
307249
NUMADevices0 *CustomNUMADevice `json:"numa0,omitempty"`
308250
NUMADevices1 *CustomNUMADevice `json:"numa1,omitempty"`
@@ -496,6 +438,8 @@ func (d *GetResponseData) UnmarshalJSON(b []byte) error {
496438
data.StorageDevices = make(CustomStorageDevices)
497439
data.PCIDevices = make(CustomPCIDevices)
498440
data.VirtiofsShares = make(CustomVirtiofsShares)
441+
data.NetworkDevices = make(CustomNetworkDeviceMap)
442+
data.IPConfigs = make(CustomCloudInitIPConfigMap)
499443

500444
for key, value := range byAttr {
501445
for _, prefix := range StorageInterfaces {
@@ -528,6 +472,24 @@ func (d *GetResponseData) UnmarshalJSON(b []byte) error {
528472

529473
data.VirtiofsShares[key] = &share
530474
}
475+
476+
if regexNetworkDevice.MatchString(key) {
477+
var device CustomNetworkDevice
478+
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &device); err != nil {
479+
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
480+
}
481+
482+
data.NetworkDevices[key] = &device
483+
}
484+
485+
if regexIPConfig.MatchString(key) {
486+
var ipConfig CustomCloudInitIPConfig
487+
if err := json.Unmarshal([]byte(`"`+value.(string)+`"`), &ipConfig); err != nil {
488+
return fmt.Errorf("failed to unmarshal %s: %w", key, err)
489+
}
490+
491+
data.IPConfigs[key] = &ipConfig
492+
}
531493
}
532494

533495
*d = GetResponseData(data)

proxmox/nodes/vms/vms_types_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ func TestUnmarshalGetResponseData(t *testing.T) {
2929
"hostpci0": "0000:81:00.2",
3030
"hostpci1": "host=81:00.4,pcie=0,rombar=1,x-vga=0",
3131
"hostpci12": "mapping=mappeddevice,pcie=0,rombar=1,x-vga=0",
32-
"virtiofs0":"test,cache=always,direct-io=1,expose-acl=1"
32+
"virtiofs0":"test,cache=always,direct-io=1,expose-acl=1",
33+
"net0": "model=virtio,bridge=vmbr0,firewall=1",
34+
"net3": "model=e1000,bridge=vmbr1",
35+
"ipconfig0": "ip=192.168.1.100/24,gw=192.168.1.1",
36+
"ipconfig1": "ip6=fd00::100/64,gw6=fd00::1"
3337
}`, "local-lvm:vm-100-disk-0,aio=io_uring,backup=1,cache=none,discard=ignore,replicate=1,size=8G,ssd=1")
3438

3539
var data GetResponseData
@@ -63,6 +67,25 @@ func TestUnmarshalGetResponseData(t *testing.T) {
6367
assert.NotNil(t, data.VirtiofsShares)
6468
assert.Len(t, data.VirtiofsShares, 1)
6569
assert.Equal(t, "always", *data.VirtiofsShares["virtiofs0"].Cache)
70+
71+
assert.NotNil(t, data.NetworkDevices)
72+
assert.Len(t, data.NetworkDevices, 2)
73+
assert.NotNil(t, data.NetworkDevices["net0"])
74+
assert.Equal(t, "virtio", data.NetworkDevices["net0"].Model)
75+
assert.Equal(t, "vmbr0", *data.NetworkDevices["net0"].Bridge)
76+
assert.NotNil(t, data.NetworkDevices["net3"])
77+
assert.Equal(t, "e1000", data.NetworkDevices["net3"].Model)
78+
assert.Equal(t, "vmbr1", *data.NetworkDevices["net3"].Bridge)
79+
assert.Nil(t, data.NetworkDevices["net1"])
80+
81+
assert.NotNil(t, data.IPConfigs)
82+
assert.Len(t, data.IPConfigs, 2)
83+
assert.NotNil(t, data.IPConfigs["ipconfig0"])
84+
assert.Equal(t, "192.168.1.100/24", *data.IPConfigs["ipconfig0"].IPv4)
85+
assert.Equal(t, "192.168.1.1", *data.IPConfigs["ipconfig0"].GatewayIPv4)
86+
assert.NotNil(t, data.IPConfigs["ipconfig1"])
87+
assert.Equal(t, "fd00::100/64", *data.IPConfigs["ipconfig1"].IPv6)
88+
assert.Equal(t, "fd00::1", *data.IPConfigs["ipconfig1"].GatewayIPv6)
6689
}
6790

6891
func assertDevice(t *testing.T, dev *CustomStorageDevice) {

proxmoxtf/resource/vm/network/network.go

Lines changed: 13 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -105,52 +105,26 @@ func valueOrDefault[T any](v *T, def T) T {
105105
return *v
106106
}
107107

108-
// getNetworkDeviceObjects extracts the ordered list of network device pointers from the API response.
108+
// getNetworkDeviceObjects builds an ordered slice of network device pointers from the API response map.
109109
func getNetworkDeviceObjects(vmConfig *vms.GetResponseData) []*vms.CustomNetworkDevice {
110-
return []*vms.CustomNetworkDevice{
111-
vmConfig.NetworkDevice0,
112-
vmConfig.NetworkDevice1,
113-
vmConfig.NetworkDevice2,
114-
vmConfig.NetworkDevice3,
115-
vmConfig.NetworkDevice4,
116-
vmConfig.NetworkDevice5,
117-
vmConfig.NetworkDevice6,
118-
vmConfig.NetworkDevice7,
119-
vmConfig.NetworkDevice8,
120-
vmConfig.NetworkDevice9,
121-
vmConfig.NetworkDevice10,
122-
vmConfig.NetworkDevice11,
123-
vmConfig.NetworkDevice12,
124-
vmConfig.NetworkDevice13,
125-
vmConfig.NetworkDevice14,
126-
vmConfig.NetworkDevice15,
127-
vmConfig.NetworkDevice16,
128-
vmConfig.NetworkDevice17,
129-
vmConfig.NetworkDevice18,
130-
vmConfig.NetworkDevice19,
131-
vmConfig.NetworkDevice20,
132-
vmConfig.NetworkDevice21,
133-
vmConfig.NetworkDevice22,
134-
vmConfig.NetworkDevice23,
135-
vmConfig.NetworkDevice24,
136-
vmConfig.NetworkDevice25,
137-
vmConfig.NetworkDevice26,
138-
vmConfig.NetworkDevice27,
139-
vmConfig.NetworkDevice28,
140-
vmConfig.NetworkDevice29,
141-
vmConfig.NetworkDevice30,
142-
vmConfig.NetworkDevice31,
110+
result := make([]*vms.CustomNetworkDevice, MaxNetworkDevices)
111+
112+
for key, device := range vmConfig.NetworkDevices {
113+
var idx int
114+
if _, err := fmt.Sscanf(key, "net%d", &idx); err == nil && idx >= 0 && idx < MaxNetworkDevices {
115+
result[idx] = device
116+
}
143117
}
118+
119+
return result
144120
}
145121

146122
// ExistingNetworkDeviceIndices returns the set of "net<i>" keys that currently exist on the VM.
147123
func ExistingNetworkDeviceIndices(vmConfig *vms.GetResponseData) map[string]struct{} {
148-
result := make(map[string]struct{})
124+
result := make(map[string]struct{}, len(vmConfig.NetworkDevices))
149125

150-
for i, nd := range getNetworkDeviceObjects(vmConfig) {
151-
if nd != nil {
152-
result[fmt.Sprintf("net%d", i)] = struct{}{}
153-
}
126+
for key := range vmConfig.NetworkDevices {
127+
result[key] = struct{}{}
154128
}
155129

156130
return result

proxmoxtf/resource/vm/vm.go

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4676,40 +4676,15 @@ func vmReadCustom(
46764676
}
46774677

46784678
ipConfigLast := -1
4679-
ipConfigObjects := []*vms.CustomCloudInitIPConfig{
4680-
vmConfig.IPConfig0,
4681-
vmConfig.IPConfig1,
4682-
vmConfig.IPConfig2,
4683-
vmConfig.IPConfig3,
4684-
vmConfig.IPConfig4,
4685-
vmConfig.IPConfig5,
4686-
vmConfig.IPConfig6,
4687-
vmConfig.IPConfig7,
4688-
vmConfig.IPConfig8,
4689-
vmConfig.IPConfig9,
4690-
vmConfig.IPConfig10,
4691-
vmConfig.IPConfig11,
4692-
vmConfig.IPConfig12,
4693-
vmConfig.IPConfig13,
4694-
vmConfig.IPConfig14,
4695-
vmConfig.IPConfig15,
4696-
vmConfig.IPConfig16,
4697-
vmConfig.IPConfig17,
4698-
vmConfig.IPConfig18,
4699-
vmConfig.IPConfig19,
4700-
vmConfig.IPConfig20,
4701-
vmConfig.IPConfig21,
4702-
vmConfig.IPConfig22,
4703-
vmConfig.IPConfig23,
4704-
vmConfig.IPConfig24,
4705-
vmConfig.IPConfig25,
4706-
vmConfig.IPConfig26,
4707-
vmConfig.IPConfig27,
4708-
vmConfig.IPConfig28,
4709-
vmConfig.IPConfig29,
4710-
vmConfig.IPConfig30,
4711-
vmConfig.IPConfig31,
4679+
ipConfigObjects := make([]*vms.CustomCloudInitIPConfig, network.MaxNetworkDevices)
4680+
4681+
for key, ipConfig := range vmConfig.IPConfigs {
4682+
var idx int
4683+
if _, err := fmt.Sscanf(key, "ipconfig%d", &idx); err == nil && idx >= 0 && idx < network.MaxNetworkDevices {
4684+
ipConfigObjects[idx] = ipConfig
4685+
}
47124686
}
4687+
47134688
ipConfigList := make([]any, len(ipConfigObjects))
47144689

47154690
for ipConfigIndex, ipConfig := range ipConfigObjects {

0 commit comments

Comments
 (0)