Skip to content

Commit 0dbf13b

Browse files
committed
Only replace tags when needed
Signed-off-by: Lennart Jern <lennart.jern@est.tech>
1 parent 92bd149 commit 0dbf13b

File tree

5 files changed

+153
-35
lines changed

5 files changed

+153
-35
lines changed

controllers/openstackserver_controller_test.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,18 @@ var listDefaultPorts = func(r *recorders) {
114114
}, nil)
115115
}
116116

117+
var listDefaultPortsWithID = func(r *recorders) {
118+
r.network.ListPort(ports.ListOpts{
119+
Name: openStackServerName + "-0",
120+
ID: portUUID,
121+
NetworkID: networkUUID,
122+
}).Return([]ports.Port{
123+
{
124+
ID: portUUID,
125+
},
126+
}, nil)
127+
}
128+
117129
var listDefaultPortsNotFound = func(r *recorders) {
118130
r.network.ListPort(ports.ListOpts{
119131
Name: openStackServerName + "-0",
@@ -501,7 +513,7 @@ func Test_OpenStackServerReconcileCreate(t *testing.T) {
501513
},
502514
expect: func(r *recorders) {
503515
listDefaultPorts(r)
504-
listDefaultPorts(r)
516+
listDefaultPortsWithID(r)
505517
listDefaultServerFound(r)
506518
},
507519
},

pkg/cloud/services/networking/port.go

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,12 @@ func (s *Service) GetPortForExternalNetwork(instanceID string, externalNetworkID
126126

127127
// ensurePortTagsAndTrunk ensures that the provided port has the tags and trunk defined in portSpec.
128128
func (s *Service) ensurePortTagsAndTrunk(port *ports.Port, eventObject runtime.Object, portSpec *infrav1.ResolvedPortSpec) error {
129-
if len(portSpec.Tags) > 0 {
130-
if err := s.replaceAllAttributesTags(eventObject, portResource, port.ID, portSpec.Tags); err != nil {
131-
record.Warnf(eventObject, "FailedReplaceTags", "Failed to replace port tags %s: %v", portSpec.Name, err)
129+
wantedTags := uniqueSortedTags(portSpec.Tags)
130+
actualTags := uniqueSortedTags(port.Tags)
131+
// Only replace tags if there is a difference
132+
if !slices.Equal(wantedTags, actualTags) && len(wantedTags) > 0 {
133+
if err := s.replaceAllAttributesTags(eventObject, portResource, port.ID, wantedTags); err != nil {
134+
record.Warnf(eventObject, "FailedReplaceTags", "Failed to replace port tags %s: %v", port.Name, err)
132135
return err
133136
}
134137
}
@@ -138,21 +141,30 @@ func (s *Service) ensurePortTagsAndTrunk(port *ports.Port, eventObject runtime.O
138141
record.Warnf(eventObject, "FailedCreateTrunk", "Failed to create trunk for port %s: %v", port.Name, err)
139142
return err
140143
}
141-
if err = s.replaceAllAttributesTags(eventObject, trunkResource, trunk.ID, portSpec.Tags); err != nil {
142-
record.Warnf(eventObject, "FailedReplaceTags", "Failed to replace trunk tags %s: %v", port.Name, err)
143-
return err
144+
145+
if !slices.Equal(wantedTags, trunk.Tags) {
146+
if err = s.replaceAllAttributesTags(eventObject, trunkResource, trunk.ID, wantedTags); err != nil {
147+
record.Warnf(eventObject, "FailedReplaceTags", "Failed to replace trunk tags %s: %v", port.Name, err)
148+
return err
149+
}
144150
}
145151
}
146152
return nil
147153
}
148154

149155
// EnsurePort ensure that a port defined with portSpec Name and NetworkID exists,
150-
// and that the port has suitable tags and trunk.
151-
func (s *Service) EnsurePort(eventObject runtime.Object, portSpec *infrav1.ResolvedPortSpec) (*ports.Port, error) {
152-
existingPorts, err := s.client.ListPort(ports.ListOpts{
156+
// and that the port has suitable tags and trunk. If the PortStatus is already known,
157+
// use the ID when filtering for existing ports.
158+
func (s *Service) EnsurePort(eventObject runtime.Object, portSpec *infrav1.ResolvedPortSpec, portStatus infrav1.PortStatus) (*ports.Port, error) {
159+
opts := ports.ListOpts{
153160
Name: portSpec.Name,
154161
NetworkID: portSpec.NetworkID,
155-
})
162+
}
163+
if portStatus.ID != "" {
164+
opts.ID = portStatus.ID
165+
}
166+
167+
existingPorts, err := s.client.ListPort(opts)
156168
if err != nil {
157169
return nil, fmt.Errorf("searching for existing port for server: %v", err)
158170
}
@@ -359,16 +371,27 @@ func getPortName(baseName string, portSpec *infrav1.PortOpts, netIndex int) stri
359371
// EnsurePorts ensures that every one of desiredPorts is created and has
360372
// expected trunk and tags.
361373
func (s *Service) EnsurePorts(eventObject runtime.Object, desiredPorts []infrav1.ResolvedPortSpec, resources *infrav1alpha1.ServerResources) error {
362-
for _, portSpec := range desiredPorts {
374+
for i := range desiredPorts {
375+
// If we already created the port, make use of the status
376+
portStatus := infrav1.PortStatus{}
377+
if i < len(resources.Ports) {
378+
portStatus = resources.Ports[i]
379+
}
363380
// Events are recorded in EnsurePort
364-
port, err := s.EnsurePort(eventObject, &portSpec)
381+
port, err := s.EnsurePort(eventObject, &desiredPorts[i], portStatus)
365382
if err != nil {
366383
return err
367384
}
368385

369-
resources.Ports = append(resources.Ports, infrav1.PortStatus{
370-
ID: port.ID,
371-
})
386+
// If we already have the status, replace it,
387+
// otherwise append it.
388+
if i < len(resources.Ports) {
389+
resources.Ports[i] = portStatus
390+
} else {
391+
resources.Ports = append(resources.Ports, infrav1.PortStatus{
392+
ID: port.ID,
393+
})
394+
}
372395
}
373396

374397
return nil
@@ -628,3 +651,19 @@ func (s *Service) AdoptPortsServer(scope *scope.WithLogger, desiredPorts []infra
628651

629652
return nil
630653
}
654+
655+
// uniqueSortedTags returns a new, sorted slice where any duplicates have been removed.
656+
func uniqueSortedTags(tags []string) []string {
657+
// remove duplicate values from tags
658+
tagsMap := make(map[string]string)
659+
for _, t := range tags {
660+
tagsMap[t] = t
661+
}
662+
663+
uniqueTags := []string{}
664+
for k := range tagsMap {
665+
uniqueTags = append(uniqueTags, k)
666+
}
667+
slices.Sort(uniqueTags)
668+
return uniqueTags
669+
}

pkg/cloud/services/networking/port_test.go

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ func Test_EnsurePort(t *testing.T) {
306306
want: &ports.Port{ID: portID},
307307
},
308308
{
309-
name: "tags and trunk",
309+
name: "create port with tags and trunk",
310310
port: infrav1.ResolvedPortSpec{
311311
Name: "test-port",
312312
NetworkID: netID,
@@ -358,6 +358,87 @@ func Test_EnsurePort(t *testing.T) {
358358
},
359359
want: &ports.Port{ID: portID, Name: "test-port"},
360360
},
361+
{
362+
name: "port with tags and trunk already exists",
363+
port: infrav1.ResolvedPortSpec{
364+
Name: "test-port",
365+
NetworkID: netID,
366+
Tags: []string{"tag1", "tag2"},
367+
Trunk: ptr.To(true),
368+
},
369+
expect: func(m *mock.MockNetworkClientMockRecorder, _ types.Gomega) {
370+
m.ListPort(ports.ListOpts{
371+
Name: "test-port",
372+
NetworkID: netID,
373+
}).Return([]ports.Port{{
374+
ID: portID,
375+
Name: "test-port",
376+
NetworkID: netID,
377+
Tags: []string{"tag1", "tag2"},
378+
}}, nil)
379+
380+
// Look for existing trunk
381+
m.ListTrunk(trunks.ListOpts{
382+
PortID: portID,
383+
Name: "test-port",
384+
}).Return([]trunks.Trunk{{
385+
ID: trunkID,
386+
Tags: []string{"tag1", "tag2"},
387+
}}, nil)
388+
},
389+
want: &ports.Port{
390+
ID: portID,
391+
Name: "test-port",
392+
NetworkID: netID,
393+
Tags: []string{"tag1", "tag2"},
394+
},
395+
},
396+
{
397+
name: "partial port missing tags and trunk",
398+
port: infrav1.ResolvedPortSpec{
399+
Name: "test-port",
400+
NetworkID: netID,
401+
Tags: []string{"tag1", "tag2"},
402+
Trunk: ptr.To(true),
403+
},
404+
expect: func(m *mock.MockNetworkClientMockRecorder, _ types.Gomega) {
405+
m.ListPort(ports.ListOpts{
406+
Name: "test-port",
407+
NetworkID: netID,
408+
}).Return([]ports.Port{{
409+
ID: portID,
410+
Name: "test-port",
411+
NetworkID: netID,
412+
}}, nil)
413+
414+
// Tag the port
415+
m.ReplaceAllAttributesTags("ports", portID, attributestags.ReplaceAllOpts{
416+
Tags: []string{"tag1", "tag2"},
417+
})
418+
419+
// Look for existing trunk
420+
m.ListTrunk(trunks.ListOpts{
421+
PortID: portID,
422+
Name: "test-port",
423+
}).Return([]trunks.Trunk{}, nil)
424+
425+
// Create the trunk
426+
m.CreateTrunk(trunks.CreateOpts{
427+
PortID: portID,
428+
Name: "test-port",
429+
}).Return(&trunks.Trunk{ID: trunkID}, nil)
430+
431+
// Tag the trunk
432+
m.ReplaceAllAttributesTags("trunks", trunkID, attributestags.ReplaceAllOpts{
433+
Tags: []string{"tag1", "tag2"},
434+
})
435+
},
436+
want: &ports.Port{
437+
ID: portID,
438+
Name: "test-port",
439+
NetworkID: netID,
440+
},
441+
},
361442
}
362443

363444
eventObject := &infrav1.OpenStackMachine{}
@@ -376,6 +457,7 @@ func Test_EnsurePort(t *testing.T) {
376457
got, err := s.EnsurePort(
377458
eventObject,
378459
&tt.port,
460+
infrav1.PortStatus{},
379461
)
380462
if tt.wantErr {
381463
g.Expect(err).To(HaveOccurred())

pkg/cloud/services/networking/service.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package networking
1818

1919
import (
2020
"fmt"
21-
"sort"
2221

2322
"github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/attributestags"
2423
"k8s.io/apimachinery/pkg/runtime"
@@ -65,28 +64,15 @@ func (s *Service) replaceAllAttributesTags(eventObject runtime.Object, resourceT
6564
record.Warnf(eventObject, "FailedReplaceAllAttributesTags", "Invalid resourceType argument in function call")
6665
panic(fmt.Errorf("invalid argument: resourceType, %s, does not match allowed arguments: %s or %s", resourceType, trunkResource, portResource))
6766
}
68-
// remove duplicate values from tags
69-
tagsMap := make(map[string]string)
70-
for _, t := range tags {
71-
tagsMap[t] = t
72-
}
73-
74-
uniqueTags := []string{}
75-
for k := range tagsMap {
76-
uniqueTags = append(uniqueTags, k)
77-
}
78-
79-
// Sort the tags so that we always get fixed order of tags to make UT easier
80-
sort.Strings(uniqueTags)
8167

8268
_, err := s.client.ReplaceAllAttributesTags(resourceType, resourceID, attributestags.ReplaceAllOpts{
83-
Tags: uniqueTags,
69+
Tags: tags,
8470
})
8571
if err != nil {
8672
record.Warnf(eventObject, "FailedReplaceAllAttributesTags", "Failed to replace all attributestags, %s: %v", resourceID, err)
8773
return err
8874
}
8975

90-
record.Eventf(eventObject, "SuccessfulReplaceAllAttributeTags", "Replaced all attributestags for %s with tags %s", resourceID, uniqueTags)
76+
record.Eventf(eventObject, "SuccessfulReplaceAllAttributeTags", "Replaced all attributestags for %s with tags %s", resourceID, tags)
9177
return nil
9278
}

test/e2e/data/e2e_conf.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
---
21
# E2E test scenario using local dev images and manifests built from the source tree for following providers:
32
# - openstack
43

@@ -188,7 +187,7 @@ intervals:
188187
default/wait-cluster: ["20m", "10s"]
189188
default/wait-control-plane: ["30m", "10s"]
190189
default/wait-worker-nodes: ["30m", "10s"]
191-
default/wait-delete-cluster: ["10m", "10s"]
190+
default/wait-delete-cluster: ["5m", "10s"]
192191
default/wait-alt-az: ["20m", "30s"]
193192
default/wait-machine-upgrade: ["30m", "10s"]
194193
default/wait-nodes-ready: ["15m", "10s"]

0 commit comments

Comments
 (0)