Skip to content

Commit 995d445

Browse files
authored
fix(vm): prevent over-creation of vmmac on additional interface updates (#2184)
Fix two issues in VM additional network handling: 1. Prevent over-creation of VirtualMachineMACAddress resources when adding additional interfaces. 2. Fix network interface ID assignment when some IDs are already reserved later in the spec list. Signed-off-by: Dmitry Lopatin <dmitry.lopatin@flant.com>
1 parent aeb5859 commit 995d445

4 files changed

Lines changed: 134 additions & 26 deletions

File tree

images/virtualization-artifact/pkg/common/network/ids.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ func EnsureNetworkInterfaceIDs(networks []v1alpha2.NetworksSpec) bool {
3939
changed := false
4040
used := make(map[int]struct{}, len(networks))
4141

42+
for i := range networks {
43+
if networks[i].ID != nil && *networks[i].ID > 0 {
44+
used[*networks[i].ID] = struct{}{}
45+
}
46+
}
47+
4248
nextID := StartGenericID
4349
for i := range networks {
4450
if networks[i].ID != nil {
45-
if *networks[i].ID > 0 {
46-
used[*networks[i].ID] = struct{}{}
47-
}
4851
continue
4952
}
5053

images/virtualization-artifact/pkg/common/network/ids_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,114 @@ var _ = Describe("Network IDs", func() {
6161
Expect(*networks[2].ID).To(Equal(2))
6262
})
6363

64+
It("should skip already reserved IDs even if they appear later in list", func() {
65+
idMain := 1
66+
idMax := 16383
67+
idNet := 2
68+
networks := []v1alpha2.NetworksSpec{
69+
{Type: v1alpha2.NetworksTypeMain, ID: &idMain},
70+
{Type: v1alpha2.NetworksTypeClusterNetwork, Name: "cnet-521"},
71+
{Type: v1alpha2.NetworksTypeNetwork, Name: "net-501", ID: &idMax},
72+
{Type: v1alpha2.NetworksTypeNetwork, Name: "net-502", ID: &idNet},
73+
}
74+
75+
changed := EnsureNetworkInterfaceIDs(networks)
76+
77+
Expect(changed).To(BeTrue())
78+
Expect(*networks[0].ID).To(Equal(1))
79+
Expect(*networks[1].ID).To(Equal(3))
80+
Expect(*networks[2].ID).To(Equal(16383))
81+
Expect(*networks[3].ID).To(Equal(2))
82+
})
83+
84+
It("should assign minimal free IDs for multiple missing networks", func() {
85+
idMain := 1
86+
id4 := 4
87+
id7 := 7
88+
networks := []v1alpha2.NetworksSpec{
89+
{Type: v1alpha2.NetworksTypeMain, ID: &idMain},
90+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n-missing-1"},
91+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n-4", ID: &id4},
92+
{Type: v1alpha2.NetworksTypeClusterNetwork, Name: "cn-missing-1"},
93+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n-7", ID: &id7},
94+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n-missing-2"},
95+
}
96+
97+
changed := EnsureNetworkInterfaceIDs(networks)
98+
99+
Expect(changed).To(BeTrue())
100+
Expect(*networks[1].ID).To(Equal(2))
101+
Expect(*networks[3].ID).To(Equal(3))
102+
Expect(*networks[5].ID).To(Equal(5))
103+
})
104+
105+
It("should assign generic IDs starting from 2 when there is no main", func() {
106+
id4 := 4
107+
networks := []v1alpha2.NetworksSpec{
108+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n1"},
109+
{Type: v1alpha2.NetworksTypeClusterNetwork, Name: "cn1"},
110+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n4", ID: &id4},
111+
}
112+
113+
changed := EnsureNetworkInterfaceIDs(networks)
114+
115+
Expect(changed).To(BeTrue())
116+
Expect(*networks[0].ID).To(Equal(2))
117+
Expect(*networks[1].ID).To(Equal(3))
118+
Expect(*networks[2].ID).To(Equal(4))
119+
})
120+
121+
It("should stop assignment when generic ID space is exhausted", func() {
122+
idMain := 1
123+
networks := []v1alpha2.NetworksSpec{{Type: v1alpha2.NetworksTypeMain, ID: &idMain}}
124+
for id := StartGenericID; id <= MaxID; id++ {
125+
v := id
126+
networks = append(networks, v1alpha2.NetworksSpec{Type: v1alpha2.NetworksTypeNetwork, ID: &v})
127+
}
128+
networks = append(networks, v1alpha2.NetworksSpec{Type: v1alpha2.NetworksTypeClusterNetwork, Name: "overflow"})
129+
130+
changed := EnsureNetworkInterfaceIDs(networks)
131+
132+
Expect(changed).To(BeFalse())
133+
Expect(networks[len(networks)-1].ID).To(BeNil())
134+
})
135+
136+
It("should keep duplicate id=1 when main has no ID but id=1 is already used", func() {
137+
id1 := 1
138+
networks := []v1alpha2.NetworksSpec{
139+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n-with-1", ID: &id1},
140+
{Type: v1alpha2.NetworksTypeMain, Name: "main"},
141+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n-missing"},
142+
}
143+
144+
changed := EnsureNetworkInterfaceIDs(networks)
145+
146+
Expect(changed).To(BeTrue())
147+
Expect(*networks[0].ID).To(Equal(1))
148+
Expect(*networks[1].ID).To(Equal(1))
149+
Expect(*networks[2].ID).To(Equal(2))
150+
})
151+
152+
It("should preserve explicit invalid IDs and assign only missing ones", func() {
153+
idZero := 0
154+
idNegative := -5
155+
idTooHigh := 20000
156+
networks := []v1alpha2.NetworksSpec{
157+
{Type: v1alpha2.NetworksTypeMain, Name: "main", ID: &idZero},
158+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n-neg", ID: &idNegative},
159+
{Type: v1alpha2.NetworksTypeNetwork, Name: "n-high", ID: &idTooHigh},
160+
{Type: v1alpha2.NetworksTypeClusterNetwork, Name: "cn-missing"},
161+
}
162+
163+
changed := EnsureNetworkInterfaceIDs(networks)
164+
165+
Expect(changed).To(BeTrue())
166+
Expect(*networks[0].ID).To(Equal(0))
167+
Expect(*networks[1].ID).To(Equal(-5))
168+
Expect(*networks[2].ID).To(Equal(20000))
169+
Expect(*networks[3].ID).To(Equal(2))
170+
})
171+
64172
It("should return false when nothing to assign", func() {
65173
idMain := 1
66174
idNet := 2

images/virtualization-artifact/pkg/controller/vm/internal/mac.go

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,10 @@ func (h *MACHandler) Handle(ctx context.Context, s state.VirtualMachineState) (r
115115
}
116116
}
117117
if createdCount < expectedMACAddresses {
118-
macsToCreate := countNetworksWithMACRequest(vm.Spec.Networks, vmmacs) - createdCount
118+
macsToCreate := expectedMACAddresses - len(vmmacs) - createdCount
119+
if macsToCreate < 0 {
120+
macsToCreate = 0
121+
}
119122
for i := 0; i < macsToCreate; i++ {
120123
err = h.macManager.CreateMACAddress(ctx, vm, h.client, "")
121124
if err != nil {
@@ -177,25 +180,3 @@ func (h *MACHandler) Handle(ctx context.Context, s state.VirtualMachineState) (r
177180
func (h *MACHandler) Name() string {
178181
return nameMACHandler
179182
}
180-
181-
func countNetworksWithMACRequest(networkSpec []v1alpha2.NetworksSpec, vmmacs []*v1alpha2.VirtualMachineMACAddress) int {
182-
existingMACNames := make(map[string]bool)
183-
for _, vmmac := range vmmacs {
184-
existingMACNames[vmmac.Name] = true
185-
}
186-
187-
count := 0
188-
for _, ns := range networkSpec {
189-
if ns.Type == v1alpha2.NetworksTypeMain {
190-
continue
191-
}
192-
193-
if ns.VirtualMachineMACAddressName == "" {
194-
count++
195-
} else if !existingMACNames[ns.VirtualMachineMACAddressName] {
196-
count++
197-
}
198-
}
199-
200-
return count
201-
}

images/virtualization-artifact/pkg/controller/vm/internal/mac_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,22 @@ var _ = Describe("MACHandler", func() {
112112
}
113113

114114
Describe("Condition presence and absence scenarios", func() {
115+
It("should create only one additional VMMAC when one additional network is added", func() {
116+
vm.Spec.Networks = []v1alpha2.NetworksSpec{
117+
{Type: v1alpha2.NetworksTypeMain},
118+
{Type: v1alpha2.NetworksTypeNetwork, Name: "test-network1"},
119+
{Type: v1alpha2.NetworksTypeNetwork, Name: "test-network2"},
120+
}
121+
122+
macAddress1 := newMACAddress("test-mac-address1", "aa:bb:cc:dd:ee:ff", "", "")
123+
fakeClient, resource, vmState = setupEnvironment(vm, macAddress1)
124+
reconcile()
125+
126+
vmmacList := &v1alpha2.VirtualMachineMACAddressList{}
127+
err := fakeClient.List(ctx, vmmacList, client.InNamespace(namespace))
128+
Expect(err).NotTo(HaveOccurred())
129+
Expect(vmmacList.Items).To(HaveLen(2))
130+
})
115131
Describe("NetworkSpec is nil", func() {
116132
It("Condition 'MACAddressReady' should have status 'True'", func() {
117133
fakeClient, resource, vmState = setupEnvironment(vm)

0 commit comments

Comments
 (0)