Skip to content

Commit 80473c6

Browse files
authored
Merge pull request #2164 from Nordix/mquhuy/fix-subport-deletion-release-0.9
🌱 Backport #2081 and #2141 to release-0.9
2 parents 038bb59 + 053a8b2 commit 80473c6

File tree

6 files changed

+217
-11
lines changed

6 files changed

+217
-11
lines changed

pkg/clients/mock/network.go

+29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/clients/networking.go

+21
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ type NetworkClient interface {
5353
CreateTrunk(opts trunks.CreateOptsBuilder) (*trunks.Trunk, error)
5454
DeleteTrunk(id string) error
5555

56+
ListTrunkSubports(trunkID string) ([]trunks.Subport, error)
57+
RemoveSubports(id string, opts trunks.RemoveSubportsOpts) error
58+
5659
ListRouter(opts routers.ListOpts) ([]routers.Router, error)
5760
CreateRouter(opts routers.CreateOptsBuilder) (*routers.Router, error)
5861
DeleteRouter(id string) error
@@ -238,6 +241,24 @@ func (c networkClient) DeleteTrunk(id string) error {
238241
return mc.ObserveRequestIgnoreNotFound(trunks.Delete(c.serviceClient, id).ExtractErr())
239242
}
240243

244+
func (c networkClient) ListTrunkSubports(trunkID string) ([]trunks.Subport, error) {
245+
mc := metrics.NewMetricPrometheusContext("trunk", "listsubports")
246+
subports, err := trunks.GetSubports(c.serviceClient, trunkID).Extract()
247+
if mc.ObserveRequest(err) != nil {
248+
return nil, err
249+
}
250+
return subports, nil
251+
}
252+
253+
func (c networkClient) RemoveSubports(id string, opts trunks.RemoveSubportsOpts) error {
254+
mc := metrics.NewMetricPrometheusContext("trunk", "deletesubports")
255+
_, err := trunks.RemoveSubports(c.serviceClient, id, opts).Extract()
256+
if mc.ObserveRequest(err) != nil {
257+
return err
258+
}
259+
return nil
260+
}
261+
241262
func (c networkClient) ListTrunk(opts trunks.ListOptsBuilder) ([]trunks.Trunk, error) {
242263
mc := metrics.NewMetricPrometheusContext("trunk", "list")
243264
allPages, err := trunks.List(c.serviceClient, opts).AllPages()

pkg/cloud/services/compute/instance_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ func TestService_ReconcileInstance(t *testing.T) {
953953

954954
// We should cleanup the first port and its trunk
955955
r.network.DeleteTrunk(trunkUUID).Return(nil)
956+
r.network.ListTrunkSubports(trunkUUID).Return([]trunks.Subport{}, nil)
956957
r.network.DeletePort(portUUID).Return(nil)
957958
},
958959
wantErr: true,

pkg/cloud/services/networking/port_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ func Test_GarbageCollectErrorInstancesPort(t *testing.T) {
625625
},
626626
}, nil)
627627
m.DeleteTrunk(trunkID1)
628+
m.ListTrunkSubports(trunkID1).Return([]trunks.Subport{}, nil)
628629
m.DeletePort(portID1)
629630
},
630631
portOpts: []infrav1.PortOpts{

pkg/cloud/services/networking/trunk.go

+55-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ import (
3131
)
3232

3333
const (
34-
timeoutTrunkDelete = 3 * time.Minute
35-
retryIntervalTrunkDelete = 5 * time.Second
34+
timeoutTrunkDelete = 3 * time.Minute
35+
retryIntervalTrunkDelete = 5 * time.Second
36+
retryIntervalSubportDelete = 30 * time.Second
3637
)
3738

3839
func (s *Service) GetTrunkSupport() (bool, error) {
@@ -77,6 +78,42 @@ func (s *Service) getOrCreateTrunk(eventObject runtime.Object, clusterName, trun
7778
return trunk, nil
7879
}
7980

81+
func (s *Service) RemoveTrunkSubports(trunkID string) error {
82+
subports, err := s.client.ListTrunkSubports(trunkID)
83+
if err != nil {
84+
return err
85+
}
86+
87+
if len(subports) == 0 {
88+
return nil
89+
}
90+
91+
portList := make([]trunks.RemoveSubport, len(subports))
92+
for i, subport := range subports {
93+
portList[i] = trunks.RemoveSubport{
94+
PortID: subport.PortID,
95+
}
96+
}
97+
98+
removeSubportsOpts := trunks.RemoveSubportsOpts{
99+
Subports: portList,
100+
}
101+
102+
err = s.client.RemoveSubports(trunkID, removeSubportsOpts)
103+
if err != nil {
104+
return err
105+
}
106+
107+
for _, subPort := range subports {
108+
err := s.client.DeletePort(subPort.PortID)
109+
if err != nil {
110+
return err
111+
}
112+
}
113+
114+
return nil
115+
}
116+
80117
func (s *Service) DeleteTrunk(eventObject runtime.Object, portID string) error {
81118
listOpts := trunks.ListOpts{
82119
PortID: portID,
@@ -88,6 +125,22 @@ func (s *Service) DeleteTrunk(eventObject runtime.Object, portID string) error {
88125
if len(trunkInfo) != 1 {
89126
return nil
90127
}
128+
// Delete sub-ports if trunk is associated with sub-ports
129+
err = wait.PollUntilContextTimeout(context.TODO(), retryIntervalSubportDelete, timeoutTrunkDelete, true, func(_ context.Context) (bool, error) {
130+
if err := s.RemoveTrunkSubports(trunkInfo[0].ID); err != nil {
131+
if capoerrors.IsNotFound(err) || capoerrors.IsConflict(err) || capoerrors.IsRetryable(err) {
132+
return false, nil
133+
}
134+
return false, err
135+
}
136+
return true, nil
137+
})
138+
if err != nil {
139+
record.Warnf(eventObject, "FailedRemoveTrunkSubports", "Failed to delete sub ports trunk %s with id %s: %v", trunkInfo[0].Name, trunkInfo[0].ID, err)
140+
return err
141+
}
142+
143+
record.Eventf(eventObject, "SuccessfulRemoveTrunkSubports", "Removed trunk sub ports %s with id %s", trunkInfo[0].Name, trunkInfo[0].ID)
91144

92145
err = wait.PollUntilContextTimeout(context.TODO(), retryIntervalTrunkDelete, timeoutTrunkDelete, true, func(_ context.Context) (bool, error) {
93146
if err := s.client.DeleteTrunk(trunkInfo[0].ID); err != nil {

test/e2e/suites/e2e/e2e_test.go

+110-9
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,10 @@ var _ = Describe("e2e tests [PR-Blocking]", func() {
258258

259259
// Note that as the bootstrap config does not have cloud.conf, the node will not be added to the cluster.
260260
// We still expect the port for the machine to be created.
261+
machineDeployment := makeMachineDeployment(namespace.Name, md3Name, clusterName, "", 1)
261262
framework.CreateMachineDeployment(ctx, framework.CreateMachineDeploymentInput{
262263
Creator: e2eCtx.Environment.BootstrapClusterProxy.GetClient(),
263-
MachineDeployment: makeMachineDeployment(namespace.Name, md3Name, clusterName, "", 1),
264+
MachineDeployment: machineDeployment,
264265
BootstrapConfigTemplate: makeJoinBootstrapConfigTemplate(namespace.Name, md3Name),
265266
InfraMachineTemplate: makeOpenStackMachineTemplateWithPortOptions(namespace.Name, clusterName, md3Name, customPortOptions, machineTags),
266267
})
@@ -274,33 +275,133 @@ var _ = Describe("e2e tests [PR-Blocking]", func() {
274275
return len(plist)
275276
}, e2eCtx.E2EConfig.GetIntervals(specName, "wait-worker-nodes")...).Should(Equal(1))
276277

277-
port := plist[0]
278-
Expect(port.Description).To(Equal("primary"))
279-
Expect(port.Tags).To(ContainElement(testTag))
278+
primaryPort := plist[0]
279+
Expect(primaryPort.Description).To(Equal("primary"))
280+
Expect(primaryPort.Tags).To(ContainElement(testTag))
280281

281282
// assert trunked port is created.
282283
Eventually(func() int {
283284
plist, err = shared.DumpOpenStackPorts(e2eCtx, ports.ListOpts{Description: "trunked", Tags: testTag})
284285
Expect(err).To(BeNil())
285286
return len(plist)
286287
}, e2eCtx.E2EConfig.GetIntervals(specName, "wait-worker-nodes")...).Should(Equal(1))
287-
port = plist[0]
288-
Expect(port.Description).To(Equal("trunked"))
289-
Expect(port.Tags).To(ContainElement(testTag))
288+
trunkedPort := plist[0]
289+
Expect(trunkedPort.Description).To(Equal("trunked"))
290+
Expect(trunkedPort.Tags).To(ContainElement(testTag))
290291

291292
// assert trunk data.
292293
var trunk *trunks.Trunk
293294
Eventually(func() int {
294-
trunk, err = shared.DumpOpenStackTrunks(e2eCtx, port.ID)
295+
trunk, err = shared.DumpOpenStackTrunks(e2eCtx, trunkedPort.ID)
295296
Expect(err).To(BeNil())
296297
Expect(trunk).NotTo(BeNil())
297298
return 1
298299
}, e2eCtx.E2EConfig.GetIntervals(specName, "wait-worker-nodes")...).Should(Equal(1))
299-
Expect(trunk.PortID).To(Equal(port.ID))
300+
Expect(trunk.PortID).To(Equal(trunkedPort.ID))
301+
300302
// assert port level security group is created by name using SecurityGroupFilters
303+
301304
securityGroupsList, err := shared.DumpOpenStackSecurityGroups(e2eCtx, groups.ListOpts{Name: testSecurityGroupName})
302305
Expect(err).NotTo(HaveOccurred())
303306
Expect(securityGroupsList).To(HaveLen(1))
307+
308+
// Testing subports
309+
shared.Logf("Create a new port and add it as a subport of the trunk")
310+
311+
providerClient, clientOpts, _, err := shared.GetTenantProviderClient(e2eCtx)
312+
Expect(err).To(BeNil(), "Cannot create providerClient")
313+
314+
networkClient, err := openstack.NewNetworkV2(providerClient, gophercloud.EndpointOpts{
315+
Region: clientOpts.RegionName,
316+
})
317+
Expect(err).To(BeNil(), "Cannot create network client")
318+
319+
networksList, err := shared.DumpOpenStackNetworks(
320+
e2eCtx,
321+
networks.ListOpts{
322+
TenantID: securityGroupsList[0].TenantID,
323+
},
324+
)
325+
Expect(err).To(BeNil(), "Cannot get network List")
326+
327+
createOpts := ports.CreateOpts{
328+
Name: "subPort",
329+
NetworkID: networksList[0].ID,
330+
}
331+
332+
subPort, err := ports.Create(networkClient, createOpts).Extract()
333+
Expect(err).To(BeNil(), "Cannot create subPort")
334+
335+
addSubportsOpts := trunks.AddSubportsOpts{
336+
Subports: []trunks.Subport{
337+
{
338+
SegmentationID: 1,
339+
SegmentationType: "vlan",
340+
PortID: subPort.ID,
341+
},
342+
},
343+
}
344+
shared.Logf("Add subport to trunk")
345+
_, err = trunks.AddSubports(networkClient, trunk.ID, addSubportsOpts).Extract()
346+
Expect(err).To(BeNil(), "Cannot add subports")
347+
348+
subports, err := trunks.GetSubports(networkClient, trunk.ID).Extract()
349+
Expect(err).To(BeNil())
350+
Expect(subports).To(HaveLen(1))
351+
352+
shared.Logf("Get machine object from MachineDeployments")
353+
c := e2eCtx.Environment.BootstrapClusterProxy.GetClient()
354+
355+
machines := framework.GetMachinesByMachineDeployments(ctx, framework.GetMachinesByMachineDeploymentsInput{
356+
Lister: c,
357+
ClusterName: clusterName,
358+
Namespace: namespace.Name,
359+
MachineDeployment: *machineDeployment,
360+
})
361+
362+
Expect(machines).To(HaveLen(1))
363+
364+
machine := machines[0]
365+
366+
shared.Logf("Fetching serverID")
367+
allServers, err := shared.DumpOpenStackServers(e2eCtx, servers.ListOpts{Name: machine.Spec.InfrastructureRef.Name})
368+
Expect(err).To(BeNil())
369+
Expect(allServers).To(HaveLen(1))
370+
serverID := allServers[0].ID
371+
Expect(err).To(BeNil())
372+
373+
shared.Logf("Deleting the machine deployment, which should trigger trunk deletion")
374+
375+
err = c.Delete(ctx, machineDeployment)
376+
Expect(err).To(BeNil())
377+
378+
shared.Logf("Waiting for the server to be cleaned")
379+
380+
computeClient, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{
381+
Region: clientOpts.RegionName,
382+
})
383+
Expect(err).To(BeNil(), "Cannot create compute client")
384+
385+
Eventually(
386+
func() bool {
387+
_, err := servers.Get(computeClient, serverID).Extract()
388+
_, ok := err.(gophercloud.ErrDefault404)
389+
return ok
390+
}, e2eCtx.E2EConfig.GetIntervals(specName, "wait-delete-cluster")...,
391+
).Should(BeTrue())
392+
393+
// Wait here for some time, to make sure the reconciler fully cleans everything
394+
time.Sleep(10 * time.Second)
395+
396+
// Verify that the trunk is deleted
397+
_, err = trunks.Get(networkClient, trunk.ID).Extract()
398+
_, ok := err.(gophercloud.ErrDefault404)
399+
Expect(ok).To(BeTrue())
400+
401+
// Verify that subPort is deleted
402+
_, err = ports.Get(networkClient, subPort.ID).Extract()
403+
_, ok = err.(gophercloud.ErrDefault404)
404+
Expect(ok).To(BeTrue())
304405
})
305406
})
306407

0 commit comments

Comments
 (0)