Skip to content

Commit 13d3d5c

Browse files
committed
Fix Port Leaks
When an instance fails to come up, there's a bug where the port doesn't get cleaned up, and eventually you will exhaust your quota. The code does attempt to garbage collect, but I suspect at some point in the past ports were suffixed with an index, but the garbage collection code wasn't made aware of this, so lists nothing. Replace the APi "filtering" that doesn't work with a manual filter that does prefix matching. As I'm a good boy, regression tests have been added to protect the functionality.
1 parent f938384 commit 13d3d5c

File tree

2 files changed

+72
-3
lines changed

2 files changed

+72
-3
lines changed

pkg/cloud/services/networking/port.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,13 +290,20 @@ func (s *Service) DeletePorts(openStackCluster *infrav1.OpenStackCluster) error
290290
}
291291

292292
func (s *Service) GarbageCollectErrorInstancesPort(eventObject runtime.Object, instanceName string) error {
293-
portList, err := s.client.ListPort(ports.ListOpts{
294-
Name: instanceName,
295-
})
293+
// NOTE: when creating an instance, ports are named using getPortName(), which
294+
// features an index, so we must list all ports and delete those that start with
295+
// the instance name. Specifying the name in the list options will only return
296+
// a direct match.
297+
portList, err := s.client.ListPort(ports.ListOpts{})
296298
if err != nil {
297299
return err
298300
}
301+
299302
for _, p := range portList {
303+
if !strings.HasPrefix(p.Name, instanceName) {
304+
continue
305+
}
306+
300307
if err := s.DeletePort(eventObject, p.ID); err != nil {
301308
return err
302309
}

pkg/cloud/services/networking/port_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,68 @@ func Test_GetOrCreatePort(t *testing.T) {
535535
}
536536
}
537537

538+
func Test_GarbageCollectErrorInstancesPort(t *testing.T) {
539+
mockCtrl := gomock.NewController(t)
540+
defer mockCtrl.Finish()
541+
542+
instanceName := "foo"
543+
portID1 := "dc6e0ae3-dad6-4240-a9cb-e541916f20d3"
544+
portID2 := "a38ab1cb-c2cc-4c1b-9d1d-696ec73356d2"
545+
portName1 := "foo-0"
546+
portName2 := "foo-1"
547+
548+
tests := []struct {
549+
name string
550+
expect func(m *mock.MockNetworkClientMockRecorder)
551+
wantErr bool
552+
}{
553+
{
554+
name: "garbage collects all ports for an instance",
555+
expect: func(m *mock.MockNetworkClientMockRecorder) {
556+
p := []ports.Port{
557+
{
558+
ID: "9278e096-5c63-4ffb-9289-2bacf948dc51",
559+
Name: "bar-0",
560+
},
561+
{
562+
ID: portID1,
563+
Name: portName1,
564+
},
565+
{
566+
ID: portID2,
567+
Name: portName2,
568+
},
569+
}
570+
m.ListPort(ports.ListOpts{}).Return(p, nil)
571+
m.DeletePort(portID1)
572+
m.DeletePort(portID2)
573+
},
574+
wantErr: false,
575+
},
576+
}
577+
578+
eventObject := &infrav1.OpenStackMachine{}
579+
for _, tt := range tests {
580+
t.Run(tt.name, func(t *testing.T) {
581+
g := NewWithT(t)
582+
mockClient := mock.NewMockNetworkClient(mockCtrl)
583+
tt.expect(mockClient.EXPECT())
584+
s := Service{
585+
client: mockClient,
586+
}
587+
err := s.GarbageCollectErrorInstancesPort(
588+
eventObject,
589+
instanceName,
590+
)
591+
if tt.wantErr {
592+
g.Expect(err).To(HaveOccurred())
593+
} else {
594+
g.Expect(err).NotTo(HaveOccurred())
595+
}
596+
})
597+
}
598+
}
599+
538600
func pointerTo(b bool) *bool {
539601
return &b
540602
}

0 commit comments

Comments
 (0)