|
| 1 | +/* |
| 2 | +Copyright 2021 The Kubernetes Authors. |
| 3 | +
|
| 4 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +you may not use this file except in compliance with the License. |
| 6 | +You may obtain a copy of the License at |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 10 | +Unless required by applicable law or agreed to in writing, software |
| 11 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +See the License for the specific language governing permissions and |
| 14 | +limitations under the License. |
| 15 | +*/ |
| 16 | + |
| 17 | +package networking |
| 18 | + |
| 19 | +import ( |
| 20 | + "testing" |
| 21 | + |
| 22 | + "github.com/go-logr/logr" |
| 23 | + "github.com/golang/mock/gomock" |
| 24 | + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/attributestags" |
| 25 | + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding" |
| 26 | + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsecurity" |
| 27 | + "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/trunks" |
| 28 | + "github.com/gophercloud/gophercloud/openstack/networking/v2/ports" |
| 29 | + . "github.com/onsi/gomega" |
| 30 | + |
| 31 | + infrav1 "sigs.k8s.io/cluster-api-provider-openstack/api/v1alpha4" |
| 32 | + "sigs.k8s.io/cluster-api-provider-openstack/pkg/cloud/services/networking/mock_networking" |
| 33 | +) |
| 34 | + |
| 35 | +func Test_GetOrCreatePort(t *testing.T) { |
| 36 | + mockCtrl := gomock.NewController(t) |
| 37 | + defer mockCtrl.Finish() |
| 38 | + |
| 39 | + // Arbitrary GUIDs used in the tests |
| 40 | + netID := "7fd24ceb-788a-441f-ad0a-d8e2f5d31a1d" |
| 41 | + subnetID := "d9c88a6d-0b8c-48ff-8f0e-8d85a078c194" |
| 42 | + portID1 := "50214c48-c09e-4a54-914f-97b40fd22802" |
| 43 | + portID2 := "4c096384-f0a5-466d-9534-06a7ed281a79" |
| 44 | + hostID := "825c1b11-3dca-4bfe-a2d8-a3cc1964c8d5" |
| 45 | + tenantID := "62b523a7-f838-45fd-904f-d2db2bb58e04" |
| 46 | + projectID := "063171b1-0595-4882-98cd-3ee79676ff87" |
| 47 | + trunkID := "eb7541fa-5e2a-4cca-b2c3-dfa409b917ce" |
| 48 | + |
| 49 | + // Other arbitrary variables passed in to the tests |
| 50 | + instanceSecurityGroups := []string{"instance-secgroup"} |
| 51 | + portSecurityGroups := []string{"port-secgroup"} |
| 52 | + |
| 53 | + pointerToTrue := pointerTo(true) |
| 54 | + pointerToFalse := pointerTo(false) |
| 55 | + |
| 56 | + tests := []struct { |
| 57 | + name string |
| 58 | + portName string |
| 59 | + net infrav1.Network |
| 60 | + instanceSecurityGroups *[]string |
| 61 | + tags []string |
| 62 | + expect func(m *mock_networking.MockNetworkClientMockRecorder) |
| 63 | + // Note the 'wanted' port isn't so important, since it will be whatever we tell ListPort or CreatePort to return. |
| 64 | + // Mostly in this test suite, we're checking that ListPort/CreatePort is called with the expected port opts. |
| 65 | + want *ports.Port |
| 66 | + wantErr bool |
| 67 | + }{ |
| 68 | + { |
| 69 | + "gets and returns existing port if name matches", |
| 70 | + "foo-port-1", |
| 71 | + infrav1.Network{ |
| 72 | + ID: netID, |
| 73 | + Subnet: &infrav1.Subnet{}, |
| 74 | + }, |
| 75 | + nil, |
| 76 | + []string{}, |
| 77 | + func(m *mock_networking.MockNetworkClientMockRecorder) { |
| 78 | + m. |
| 79 | + ListPort(ports.ListOpts{ |
| 80 | + Name: "foo-port-1", |
| 81 | + NetworkID: netID, |
| 82 | + }).Return([]ports.Port{{ |
| 83 | + ID: portID1, |
| 84 | + }}, nil) |
| 85 | + }, |
| 86 | + &ports.Port{ |
| 87 | + ID: portID1, |
| 88 | + }, |
| 89 | + false, |
| 90 | + }, |
| 91 | + { |
| 92 | + "errors if multiple matching ports are found", |
| 93 | + "foo-port-1", |
| 94 | + infrav1.Network{ |
| 95 | + ID: netID, |
| 96 | + Subnet: &infrav1.Subnet{}, |
| 97 | + }, |
| 98 | + nil, |
| 99 | + []string{}, |
| 100 | + func(m *mock_networking.MockNetworkClientMockRecorder) { |
| 101 | + m. |
| 102 | + ListPort(ports.ListOpts{ |
| 103 | + Name: "foo-port-1", |
| 104 | + NetworkID: netID, |
| 105 | + }).Return([]ports.Port{ |
| 106 | + { |
| 107 | + ID: portID1, |
| 108 | + NetworkID: netID, |
| 109 | + Name: "foo-port-1", |
| 110 | + }, |
| 111 | + { |
| 112 | + ID: portID2, |
| 113 | + NetworkID: netID, |
| 114 | + Name: "foo-port-2", |
| 115 | + }, |
| 116 | + }, nil) |
| 117 | + }, |
| 118 | + nil, |
| 119 | + true, |
| 120 | + }, |
| 121 | + { |
| 122 | + "creates port with defaults (description and secgroups) if not specified in portOpts", |
| 123 | + "foo-port-1", |
| 124 | + infrav1.Network{ |
| 125 | + ID: netID, |
| 126 | + PortOpts: &infrav1.PortOpts{}, |
| 127 | + }, |
| 128 | + &instanceSecurityGroups, |
| 129 | + []string{}, |
| 130 | + func(m *mock_networking.MockNetworkClientMockRecorder) { |
| 131 | + // No ports found |
| 132 | + m. |
| 133 | + ListPort(ports.ListOpts{ |
| 134 | + Name: "foo-port-1", |
| 135 | + NetworkID: netID, |
| 136 | + }).Return([]ports.Port{}, nil) |
| 137 | + m. |
| 138 | + CreatePort(portsbinding.CreateOptsExt{ |
| 139 | + CreateOptsBuilder: ports.CreateOpts{ |
| 140 | + Name: "foo-port-1", |
| 141 | + Description: "Created by cluster-api-provider-openstack cluster test-cluster", |
| 142 | + SecurityGroups: &instanceSecurityGroups, |
| 143 | + NetworkID: netID, |
| 144 | + AllowedAddressPairs: []ports.AddressPair{}, |
| 145 | + }, |
| 146 | + }).Return(&ports.Port{ID: portID1}, nil) |
| 147 | + }, |
| 148 | + &ports.Port{ID: portID1}, |
| 149 | + false, |
| 150 | + }, |
| 151 | + { |
| 152 | + "creates port with specified portOpts if no matching port exists", |
| 153 | + "foo-port-bar", |
| 154 | + infrav1.Network{ |
| 155 | + ID: netID, |
| 156 | + Subnet: &infrav1.Subnet{}, |
| 157 | + PortOpts: &infrav1.PortOpts{ |
| 158 | + NameSuffix: "bar", |
| 159 | + Description: "this is a test port", |
| 160 | + MACAddress: "fe:fe:fe:fe:fe:fe", |
| 161 | + AdminStateUp: pointerToTrue, |
| 162 | + FixedIPs: []infrav1.FixedIP{{ |
| 163 | + SubnetID: subnetID, |
| 164 | + IPAddress: "192.168.0.50", |
| 165 | + }, {IPAddress: "192.168.1.50"}}, |
| 166 | + TenantID: tenantID, |
| 167 | + ProjectID: projectID, |
| 168 | + SecurityGroups: &portSecurityGroups, |
| 169 | + AllowedAddressPairs: []infrav1.AddressPair{{ |
| 170 | + IPAddress: "10.10.10.10", |
| 171 | + MACAddress: "f1:f1:f1:f1:f1:f1", |
| 172 | + }}, |
| 173 | + HostID: hostID, |
| 174 | + VNICType: "direct", |
| 175 | + Profile: map[string]string{"interface_name": "eno1"}, |
| 176 | + DisablePortSecurity: pointerToFalse, |
| 177 | + }, |
| 178 | + }, |
| 179 | + nil, |
| 180 | + nil, |
| 181 | + func(m *mock_networking.MockNetworkClientMockRecorder) { |
| 182 | + portCreateOpts := ports.CreateOpts{ |
| 183 | + NetworkID: netID, |
| 184 | + Name: "foo-port-bar", |
| 185 | + Description: "this is a test port", |
| 186 | + AdminStateUp: pointerToTrue, |
| 187 | + MACAddress: "fe:fe:fe:fe:fe:fe", |
| 188 | + FixedIPs: []ports.IP{ |
| 189 | + { |
| 190 | + SubnetID: subnetID, |
| 191 | + IPAddress: "192.168.0.50", |
| 192 | + }, { |
| 193 | + IPAddress: "192.168.1.50", |
| 194 | + }, |
| 195 | + }, |
| 196 | + TenantID: tenantID, |
| 197 | + ProjectID: projectID, |
| 198 | + SecurityGroups: &portSecurityGroups, |
| 199 | + AllowedAddressPairs: []ports.AddressPair{{ |
| 200 | + IPAddress: "10.10.10.10", |
| 201 | + MACAddress: "f1:f1:f1:f1:f1:f1", |
| 202 | + }}, |
| 203 | + } |
| 204 | + portsecurityCreateOptsExt := portsecurity.PortCreateOptsExt{ |
| 205 | + CreateOptsBuilder: portCreateOpts, |
| 206 | + PortSecurityEnabled: pointerToTrue, |
| 207 | + } |
| 208 | + portbindingCreateOptsExt := portsbinding.CreateOptsExt{ |
| 209 | + // Note for the test matching, the order in which the builders are composed |
| 210 | + // must be the same as in the function we are testing. |
| 211 | + CreateOptsBuilder: portsecurityCreateOptsExt, |
| 212 | + HostID: hostID, |
| 213 | + VNICType: "direct", |
| 214 | + Profile: map[string]interface{}{"interface_name": "eno1"}, |
| 215 | + } |
| 216 | + m. |
| 217 | + ListPort(ports.ListOpts{ |
| 218 | + Name: "foo-port-bar", |
| 219 | + NetworkID: netID, |
| 220 | + }).Return([]ports.Port{}, nil) |
| 221 | + m. |
| 222 | + CreatePort(portbindingCreateOptsExt). |
| 223 | + Return(&ports.Port{ |
| 224 | + ID: portID1, |
| 225 | + }, nil) |
| 226 | + }, |
| 227 | + &ports.Port{ |
| 228 | + ID: portID1, |
| 229 | + }, |
| 230 | + false, |
| 231 | + }, |
| 232 | + { |
| 233 | + "overrides default (instance) security groups if port security groups are specified", |
| 234 | + "foo-port-1", |
| 235 | + infrav1.Network{ |
| 236 | + ID: netID, |
| 237 | + PortOpts: &infrav1.PortOpts{ |
| 238 | + SecurityGroups: &portSecurityGroups, |
| 239 | + }, |
| 240 | + }, |
| 241 | + &instanceSecurityGroups, |
| 242 | + []string{}, |
| 243 | + func(m *mock_networking.MockNetworkClientMockRecorder) { |
| 244 | + // No ports found |
| 245 | + m. |
| 246 | + ListPort(ports.ListOpts{ |
| 247 | + Name: "foo-port-1", |
| 248 | + NetworkID: netID, |
| 249 | + }).Return([]ports.Port{}, nil) |
| 250 | + m. |
| 251 | + CreatePort(portsbinding.CreateOptsExt{ |
| 252 | + CreateOptsBuilder: ports.CreateOpts{ |
| 253 | + Name: "foo-port-1", |
| 254 | + Description: "Created by cluster-api-provider-openstack cluster test-cluster", |
| 255 | + SecurityGroups: &portSecurityGroups, |
| 256 | + NetworkID: netID, |
| 257 | + AllowedAddressPairs: []ports.AddressPair{}, |
| 258 | + }, |
| 259 | + }, |
| 260 | + ).Return(&ports.Port{ID: portID1}, nil) |
| 261 | + }, |
| 262 | + &ports.Port{ID: portID1}, |
| 263 | + false, |
| 264 | + }, |
| 265 | + { |
| 266 | + "creates port with tags passed to function", |
| 267 | + "foo-port-1", |
| 268 | + infrav1.Network{ |
| 269 | + ID: netID, |
| 270 | + PortOpts: &infrav1.PortOpts{}, |
| 271 | + }, |
| 272 | + nil, |
| 273 | + []string{"my-tag"}, |
| 274 | + func(m *mock_networking.MockNetworkClientMockRecorder) { |
| 275 | + // No ports found |
| 276 | + m. |
| 277 | + ListPort(ports.ListOpts{ |
| 278 | + Name: "foo-port-1", |
| 279 | + NetworkID: netID, |
| 280 | + }).Return([]ports.Port{}, nil) |
| 281 | + m.CreatePort(portsbinding.CreateOptsExt{ |
| 282 | + CreateOptsBuilder: ports.CreateOpts{ |
| 283 | + Name: "foo-port-1", |
| 284 | + Description: "Created by cluster-api-provider-openstack cluster test-cluster", |
| 285 | + NetworkID: netID, |
| 286 | + AllowedAddressPairs: []ports.AddressPair{}, |
| 287 | + }, |
| 288 | + }).Return(&ports.Port{ID: portID1}, nil) |
| 289 | + m.ReplaceAllAttributesTags("ports", portID1, attributestags.ReplaceAllOpts{Tags: []string{"my-tag"}}).Return([]string{"my-tag"}, nil) |
| 290 | + }, |
| 291 | + &ports.Port{ID: portID1}, |
| 292 | + false, |
| 293 | + }, |
| 294 | + { |
| 295 | + "creates port and trunk (with tags) if they aren't found", |
| 296 | + "foo-port-1", |
| 297 | + infrav1.Network{ |
| 298 | + ID: netID, |
| 299 | + PortOpts: &infrav1.PortOpts{ |
| 300 | + Trunk: pointerToTrue, |
| 301 | + }, |
| 302 | + }, |
| 303 | + nil, |
| 304 | + []string{"my-tag"}, |
| 305 | + func(m *mock_networking.MockNetworkClientMockRecorder) { |
| 306 | + // No ports found |
| 307 | + m. |
| 308 | + ListPort(ports.ListOpts{ |
| 309 | + Name: "foo-port-1", |
| 310 | + NetworkID: netID, |
| 311 | + }).Return([]ports.Port{}, nil) |
| 312 | + m. |
| 313 | + CreatePort(portsbinding.CreateOptsExt{ |
| 314 | + CreateOptsBuilder: ports.CreateOpts{ |
| 315 | + Name: "foo-port-1", |
| 316 | + Description: "Created by cluster-api-provider-openstack cluster test-cluster", |
| 317 | + NetworkID: netID, |
| 318 | + AllowedAddressPairs: []ports.AddressPair{}, |
| 319 | + }, |
| 320 | + }).Return(&ports.Port{Name: "foo-port-1", ID: portID1}, nil) |
| 321 | + m. |
| 322 | + ListTrunk(trunks.ListOpts{ |
| 323 | + Name: "foo-port-1", |
| 324 | + PortID: portID1, |
| 325 | + }).Return([]trunks.Trunk{}, nil) |
| 326 | + m. |
| 327 | + CreateTrunk(trunks.CreateOpts{ |
| 328 | + Name: "foo-port-1", |
| 329 | + PortID: portID1, |
| 330 | + Description: "Created by cluster-api-provider-openstack cluster test-cluster", |
| 331 | + }).Return(&trunks.Trunk{ID: trunkID}, nil) |
| 332 | + |
| 333 | + m.ReplaceAllAttributesTags("ports", portID1, attributestags.ReplaceAllOpts{Tags: []string{"my-tag"}}).Return([]string{"my-tag"}, nil) |
| 334 | + m.ReplaceAllAttributesTags("trunks", trunkID, attributestags.ReplaceAllOpts{Tags: []string{"my-tag"}}).Return([]string{"my-tag"}, nil) |
| 335 | + }, |
| 336 | + &ports.Port{Name: "foo-port-1", ID: portID1}, |
| 337 | + false, |
| 338 | + }, |
| 339 | + } |
| 340 | + |
| 341 | + eventObject := &infrav1.OpenStackMachine{} |
| 342 | + for _, tt := range tests { |
| 343 | + t.Run(tt.name, func(t *testing.T) { |
| 344 | + g := NewWithT(t) |
| 345 | + mockClient := mock_networking.NewMockNetworkClient(mockCtrl) |
| 346 | + tt.expect(mockClient.EXPECT()) |
| 347 | + s := Service{ |
| 348 | + client: mockClient, |
| 349 | + logger: logr.DiscardLogger{}, |
| 350 | + } |
| 351 | + got, err := s.GetOrCreatePort( |
| 352 | + eventObject, |
| 353 | + "test-cluster", |
| 354 | + tt.portName, |
| 355 | + tt.net, |
| 356 | + tt.instanceSecurityGroups, |
| 357 | + tt.tags, |
| 358 | + ) |
| 359 | + if tt.wantErr { |
| 360 | + g.Expect(err).To(HaveOccurred()) |
| 361 | + } else { |
| 362 | + g.Expect(err).NotTo(HaveOccurred()) |
| 363 | + } |
| 364 | + g.Expect(got).To(Equal(tt.want)) |
| 365 | + }) |
| 366 | + } |
| 367 | +} |
| 368 | + |
| 369 | +func pointerTo(b bool) *bool { |
| 370 | + return &b |
| 371 | +} |
0 commit comments