Skip to content

Commit e0d486b

Browse files
author
Huy Mai
committed
Fix sub-ports not deleted with trunks
Signed-off-by: Huy Mai <[email protected]>
1 parent 550d519 commit e0d486b

File tree

5 files changed

+219
-11
lines changed

5 files changed

+219
-11
lines changed

controllers/openstackmachine_controller_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ func Test_reconcileDelete(t *testing.T) {
265265
trunkExtension.Alias = "trunk"
266266
r.network.ListExtensions().Return([]extensions.Extension{trunkExtension}, nil)
267267
r.network.ListTrunk(trunks.ListOpts{PortID: portUUID}).Return([]trunks.Trunk{{ID: trunkUUID}}, nil)
268+
r.network.ListTrunkSubports(trunkUUID).Return([]trunks.Subport{}, nil)
268269
r.network.DeleteTrunk(trunkUUID).Return(nil)
269270
r.network.DeletePort(portUUID).Return(nil)
270271
}

pkg/clients/mock/network.go

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/clients/networking.go

Lines changed: 21 additions & 0 deletions
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/networking/trunk.go

Lines changed: 55 additions & 2 deletions
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) getOrCreateTrunkForPort(eventObject runtime.Object, port *port
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

Lines changed: 113 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828
"strings"
2929
"time"
3030

31+
"github.com/gophercloud/gophercloud"
32+
"github.com/gophercloud/gophercloud/openstack"
3133
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
3234
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
3335
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
@@ -424,9 +426,10 @@ var _ = Describe("e2e tests [PR-Blocking]", func() {
424426

425427
// Note that as the bootstrap config does not have cloud.conf, the node will not be added to the cluster.
426428
// We still expect the port for the machine to be created.
429+
machineDeployment := makeMachineDeployment(namespace.Name, md3Name, clusterName, "", 1)
427430
framework.CreateMachineDeployment(ctx, framework.CreateMachineDeploymentInput{
428431
Creator: e2eCtx.Environment.BootstrapClusterProxy.GetClient(),
429-
MachineDeployment: makeMachineDeployment(namespace.Name, md3Name, clusterName, "", 1),
432+
MachineDeployment: machineDeployment,
430433
BootstrapConfigTemplate: makeJoinBootstrapConfigTemplate(namespace.Name, md3Name),
431434
InfraMachineTemplate: makeOpenStackMachineTemplateWithPortOptions(namespace.Name, clusterName, md3Name, customPortOptions, machineTags),
432435
})
@@ -440,33 +443,134 @@ var _ = Describe("e2e tests [PR-Blocking]", func() {
440443
return len(plist)
441444
}, e2eCtx.E2EConfig.GetIntervals(specName, "wait-worker-nodes")...).Should(Equal(1))
442445

443-
port := plist[0]
444-
Expect(port.Description).To(Equal("primary"))
445-
Expect(port.Tags).To(ContainElement(testTag))
446+
primaryPort := plist[0]
447+
Expect(primaryPort.Description).To(Equal("primary"))
448+
Expect(primaryPort.Tags).To(ContainElement(testTag))
446449

447450
// assert trunked port is created.
448451
Eventually(func() int {
449452
plist, err = shared.DumpOpenStackPorts(e2eCtx, ports.ListOpts{Description: "trunked", Tags: testTag})
450453
Expect(err).To(BeNil())
451454
return len(plist)
452455
}, e2eCtx.E2EConfig.GetIntervals(specName, "wait-worker-nodes")...).Should(Equal(1))
453-
port = plist[0]
454-
Expect(port.Description).To(Equal("trunked"))
455-
Expect(port.Tags).To(ContainElement(testTag))
456+
trunkedPort := plist[0]
457+
Expect(trunkedPort.Description).To(Equal("trunked"))
458+
Expect(trunkedPort.Tags).To(ContainElement(testTag))
456459

457460
// assert trunk data.
458461
var trunk *trunks.Trunk
459462
Eventually(func() int {
460-
trunk, err = shared.DumpOpenStackTrunks(e2eCtx, port.ID)
463+
trunk, err = shared.DumpOpenStackTrunks(e2eCtx, trunkedPort.ID)
461464
Expect(err).To(BeNil())
462465
Expect(trunk).NotTo(BeNil())
463466
return 1
464467
}, e2eCtx.E2EConfig.GetIntervals(specName, "wait-worker-nodes")...).Should(Equal(1))
465-
Expect(trunk.PortID).To(Equal(port.ID))
468+
Expect(trunk.PortID).To(Equal(trunkedPort.ID))
469+
466470
// assert port level security group is created by name using SecurityGroupFilters
471+
467472
securityGroupsList, err := shared.DumpOpenStackSecurityGroups(e2eCtx, groups.ListOpts{Name: testSecurityGroupName})
468473
Expect(err).NotTo(HaveOccurred())
469474
Expect(securityGroupsList).To(HaveLen(1))
475+
476+
// Testing subports
477+
shared.Logf("Create a new port and add it as a subport of the trunk")
478+
479+
providerClient, clientOpts, _, err := shared.GetTenantProviderClient(e2eCtx)
480+
Expect(err).To(BeNil(), "Cannot create providerClient")
481+
482+
networkClient, err := openstack.NewNetworkV2(providerClient, gophercloud.EndpointOpts{
483+
Region: clientOpts.RegionName,
484+
})
485+
Expect(err).To(BeNil(), "Cannot create network client")
486+
487+
networksList, err := shared.DumpOpenStackNetworks(
488+
e2eCtx,
489+
networks.ListOpts{
490+
TenantID: securityGroupsList[0].TenantID,
491+
},
492+
)
493+
Expect(err).To(BeNil(), "Cannot get network List")
494+
495+
createOpts := ports.CreateOpts{
496+
Name: "subPort",
497+
NetworkID: networksList[0].ID,
498+
}
499+
500+
subPort, err := ports.Create(networkClient, createOpts).Extract()
501+
Expect(err).To(BeNil(), "Cannot create subPort")
502+
503+
addSubportsOpts := trunks.AddSubportsOpts{
504+
Subports: []trunks.Subport{
505+
{
506+
SegmentationID: 1,
507+
SegmentationType: "vlan",
508+
PortID: subPort.ID,
509+
},
510+
},
511+
}
512+
shared.Logf("Add subport to trunk")
513+
_, err = trunks.AddSubports(networkClient, trunk.ID, addSubportsOpts).Extract()
514+
Expect(err).To(BeNil(), "Cannot add subports")
515+
516+
subports, err := trunks.GetSubports(networkClient, trunk.ID).Extract()
517+
Expect(err).To(BeNil())
518+
Expect(subports).To(HaveLen(1))
519+
520+
shared.Logf("Get machine object from MachineDeployments")
521+
c := e2eCtx.Environment.BootstrapClusterProxy.GetClient()
522+
523+
machines := framework.GetMachinesByMachineDeployments(ctx, framework.GetMachinesByMachineDeploymentsInput{
524+
Lister: c,
525+
ClusterName: clusterName,
526+
Namespace: namespace.Name,
527+
MachineDeployment: *machineDeployment,
528+
})
529+
530+
Expect(machines).To(HaveLen(1))
531+
532+
machine := machines[0]
533+
534+
shared.Logf("Fetching serverID")
535+
allServers, err := shared.DumpOpenStackServers(e2eCtx, servers.ListOpts{Name: machine.Spec.InfrastructureRef.Name})
536+
Expect(err).To(BeNil())
537+
538+
Expect(allServers).To(HaveLen(1))
539+
serverID := allServers[0].ID
540+
Expect(err).To(BeNil())
541+
542+
shared.Logf("Deleting the machine deployment, which should trigger trunk deletion")
543+
544+
err = c.Delete(ctx, machineDeployment)
545+
Expect(err).To(BeNil())
546+
547+
shared.Logf("Waiting for the server to be cleaned")
548+
549+
computeClient, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{
550+
Region: clientOpts.RegionName,
551+
})
552+
Expect(err).To(BeNil(), "Cannot create compute client")
553+
554+
Eventually(
555+
func() bool {
556+
_, err := servers.Get(computeClient, serverID).Extract()
557+
_, ok := err.(gophercloud.ErrDefault404)
558+
return ok
559+
}, e2eCtx.E2EConfig.GetIntervals(specName, "wait-delete-cluster")...,
560+
).Should(BeTrue())
561+
562+
// Wait here for some time, to make sure the reconciler fully cleans everything
563+
time.Sleep(10 * time.Second)
564+
565+
// Verify that the trunk is deleted
566+
_, err = trunks.Get(networkClient, trunk.ID).Extract()
567+
_, ok := err.(gophercloud.ErrDefault404)
568+
Expect(ok).To(BeTrue())
569+
570+
// Verify that subPort is deleted
571+
_, err = ports.Get(networkClient, subPort.ID).Extract()
572+
_, ok = err.(gophercloud.ErrDefault404)
573+
Expect(ok).To(BeTrue())
470574
})
471575
})
472576

0 commit comments

Comments
 (0)