Skip to content

Commit 512e572

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 512e572

File tree

4 files changed

+83
-13
lines changed

4 files changed

+83
-13
lines changed

pkg/cloud/services/compute/instance.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ func (s *Service) createInstanceImpl(eventObject runtime.Object, openStackCluste
283283
if len(instanceSpec.Tags) > 0 {
284284
iTags = instanceSpec.Tags
285285
}
286-
portName := getPortName(instanceSpec.Name, portOpts, i)
286+
portName := networking.GetPortName(instanceSpec.Name, portOpts, i)
287287
port, err := networkingService.GetOrCreatePort(eventObject, clusterName, portName, portOpts, securityGroups, iTags)
288288
if err != nil {
289289
return nil, err
@@ -380,14 +380,6 @@ func (s *Service) createInstanceImpl(eventObject runtime.Object, openStackCluste
380380
return createdInstance, nil
381381
}
382382

383-
// getPortName appends a suffix to an instance name in order to try and get a unique name per port.
384-
func getPortName(instanceName string, opts *infrav1.PortOpts, netIndex int) string {
385-
if opts != nil && opts.NameSuffix != "" {
386-
return fmt.Sprintf("%s-%s", instanceName, opts.NameSuffix)
387-
}
388-
return fmt.Sprintf("%s-%d", instanceName, netIndex)
389-
}
390-
391383
func rootVolumeName(instanceName string) string {
392384
return fmt.Sprintf("%s-root", instanceName)
393385
}

pkg/cloud/services/compute/instance_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import (
4848
infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha7"
4949
"sigs.k8s.io/cluster-api-provider-openstack/pkg/clients"
5050
"sigs.k8s.io/cluster-api-provider-openstack/pkg/clients/mock"
51+
"sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/networking"
5152
"sigs.k8s.io/cluster-api-provider-openstack/pkg/scope"
5253
)
5354

@@ -110,7 +111,7 @@ func Test_getPortName(t *testing.T) {
110111
}
111112
for _, tt := range tests {
112113
t.Run(tt.name, func(t *testing.T) {
113-
if got := getPortName(tt.args.instanceName, tt.args.opts, tt.args.netIndex); got != tt.want {
114+
if got := networking.GetPortName(tt.args.instanceName, tt.args.opts, tt.args.netIndex); got != tt.want {
114115
t.Errorf("getPortName() = %v, want %v", got, tt.want)
115116
}
116117
})

pkg/cloud/services/networking/port.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -290,17 +290,32 @@ 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
}
303310
}
304311

305312
return nil
306313
}
314+
315+
// GetPortName appends a suffix to an instance name in order to try and get a unique name per port.
316+
func GetPortName(instanceName string, opts *infrav1.PortOpts, netIndex int) string {
317+
if opts != nil && opts.NameSuffix != "" {
318+
return fmt.Sprintf("%s-%s", instanceName, opts.NameSuffix)
319+
}
320+
return fmt.Sprintf("%s-%d", instanceName, netIndex)
321+
}

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 := GetPortName(instanceName, nil, 0)
546+
portName2 := GetPortName(instanceName, nil, 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)