github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/openstack/networking_test.go (about) 1 // Copyright 2021 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package openstack 5 6 import ( 7 "github.com/go-goose/goose/v5/neutron" 8 "github.com/go-goose/goose/v5/nova" 9 "github.com/juju/errors" 10 jujutesting "github.com/juju/testing" 11 jc "github.com/juju/testing/checkers" 12 "go.uber.org/mock/gomock" 13 gc "gopkg.in/check.v1" 14 15 "github.com/juju/juju/core/instance" 16 "github.com/juju/juju/core/network" 17 "github.com/juju/juju/environs" 18 ) 19 20 type networkingSuite struct { 21 jujutesting.IsolationSuite 22 23 base *MockNetworkingBase 24 neutron *MockNetworkingNeutron 25 nova *MockNetworkingNova 26 client *MockNetworkingAuthenticatingClient 27 ecfg *MockNetworkingEnvironConfig 28 29 serverAZ string 30 externalNetwork string 31 ip string 32 ip2 string 33 ip3 string 34 } 35 36 var _ = gc.Suite(&networkingSuite{}) 37 38 func (s *networkingSuite) SetUpTest(c *gc.C) { 39 s.serverAZ = "test-me" 40 s.externalNetwork = "ext-net" 41 s.ip = "10.4.5.6" 42 s.ip2 = "10.4.5.42" 43 s.ip3 = "10.4.5.75" 44 } 45 46 func (s *networkingSuite) TestAllocatePublicIPConfiguredExternalNetwork(c *gc.C) { 47 // Get a FIP for an instance with a configured external-network, 48 // which has available FIPs. Other external networks do exist - 49 // at last 1 in the same AZ as the instance. Should get the FIP 50 // on the configured external-network. 51 defer s.setupMocks(c).Finish() 52 s.expectExternalNetwork() 53 s.expectListFloatingIPsV2FromConfig() 54 s.expectListExternalNetworksV2() // resolveNeutronNetwork() 55 s.expectListInternalNetworksV2() 56 s.expectListExternalNetworksV2() // getExternalNeutronNetworksByAZ() 57 58 fip, err := s.runAllocatePublicIP() 59 c.Assert(err, jc.ErrorIsNil) 60 c.Assert(fip, gc.NotNil) 61 c.Assert(*fip, gc.Equals, s.ip) 62 } 63 64 func (s *networkingSuite) TestAllocatePublicIPUnconfiguredExternalNetwork(c *gc.C) { 65 // Get a FIP for an instance with an external network in the same AZ 66 // having an available FIP. The first external network in the list 67 // does not have an available FIP. No configured external-networks. 68 defer s.setupMocks(c).Finish() 69 s.externalNetwork = "" 70 s.expectExternalNetwork() 71 s.expectListFloatingIPsV2NotFromConfig() 72 s.expectListInternalNetworksV2() 73 s.expectListExternalNetworksV2() // getExternalNeutronNetworksByAZ() 74 75 fip, err := s.runAllocatePublicIP() 76 c.Assert(err, jc.ErrorIsNil) 77 c.Assert(fip, gc.NotNil) 78 c.Assert(*fip, gc.Equals, s.ip2) 79 } 80 81 func (s *networkingSuite) TestAllocatePublicIPUnconfiguredExternalNetworkMultiAZ(c *gc.C) { 82 // Get a FIP for an instance with an external network in the same AZ 83 // having an available FIP. This external network exists in multiple 84 // AZ, the one we want is not first in the list. The first external 85 // network in the list does not have an available FIP. No configured 86 // external-networks. 87 defer s.setupMocks(c).Finish() 88 s.externalNetwork = "" 89 s.expectExternalNetwork() 90 s.expectListFloatingIPsV2NotFromConfig() 91 s.expectListInternalNetworksV2() 92 s.expectListExternalNetworksV2MultiAZ() // getExternalNeutronNetworksByAZ() 93 94 fip, err := s.runAllocatePublicIP() 95 c.Assert(err, jc.ErrorIsNil) 96 c.Assert(fip, gc.NotNil) 97 c.Assert(*fip, gc.Equals, s.ip2) 98 } 99 100 func (s *networkingSuite) TestAllocatePublicIPFail(c *gc.C) { 101 // Find external-networks, but none have an available FIP, nor 102 // are they able to create one. 103 defer s.setupMocks(c).Finish() 104 s.expectExternalNetwork() 105 s.expectListFloatingIPsV2Empty() 106 s.expectListExternalNetworksV2() // resolveNeutronNetwork() 107 s.expectListInternalNetworksV2() 108 s.expectListExternalNetworksV2() // getExternalNeutronNetworksByAZ() 109 s.expectAllocateFloatingIPV2FailAll() 110 111 fip, err := s.runAllocatePublicIP() 112 c.Assert(err, jc.Satisfies, errors.IsNotFound) 113 c.Assert(fip, gc.IsNil) 114 } 115 116 func (s *networkingSuite) TestAllocatePublicIPEmtpyAZEqualEmptyString(c *gc.C) { 117 // Test for lp: 1891227 fix. An empty slice for AZ should be 118 // treated as an empty string AZ. 119 s.serverAZ = "" 120 defer s.setupMocks(c).Finish() 121 s.externalNetwork = "" 122 s.expectListExternalNetworksV2() // resolveNeutronNetwork() 123 s.expectExternalNetwork() 124 s.neutron.EXPECT().ListNetworksV2(gomock.Any()).Return([]neutron.NetworkV2{ 125 { 126 Id: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 127 Name: "test-me", 128 }, 129 }, nil).AnyTimes() 130 s.expectListFloatingIPsV2NotFromConfig() 131 132 fip, err := s.runAllocatePublicIP() 133 c.Assert(err, jc.ErrorIsNil) 134 c.Assert(fip, gc.NotNil) 135 c.Assert(*fip, gc.Equals, s.ip2) 136 } 137 138 func (s *networkingSuite) TestAllocatePublicIPNoneAvailable(c *gc.C) { 139 // Get a FIP for an instance with an external network in the same AZ 140 // having an available FIP. No FIPs are available in the configured 141 // external network, so allocate one. The first network fails to 142 // allocate, the 2nd succeeds. 143 defer s.setupMocks(c).Finish() 144 s.expectExternalNetwork() 145 s.expectListFloatingIPsV2FromConfigInUse() 146 s.expectListExternalNetworksV2() // resolveNeutronNetwork() 147 s.expectListInternalNetworksV2() // findNetworkAZForHostAddrs() 148 s.expectListExternalNetworksV2() // getExternalNeutronNetworksByAZ() 149 s.expectAllocateFloatingIPV2() 150 151 fip, err := s.runAllocatePublicIP() 152 c.Assert(err, jc.ErrorIsNil) 153 c.Assert(fip, gc.NotNil) 154 c.Assert(*fip, gc.Equals, s.ip3) 155 } 156 157 func (s *networkingSuite) TestAllocatePublicIPFailNoNetworkInAZ(c *gc.C) { 158 // No external network in same AZ as the instance is found, no 159 // external network is configured. 160 defer s.setupMocks(c).Finish() 161 s.externalNetwork = "" 162 s.expectExternalNetwork() 163 s.expectListInternalNetworksV2() // findNetworkAZForHostAddrs() 164 s.expectListExternalNetworksV2NotInAZ() // getExternalNeutronNetworksByAZ() 165 166 fip, err := s.runAllocatePublicIP() 167 c.Assert(err, jc.Satisfies, errors.IsNotFound) 168 c.Assert(fip, gc.IsNil) 169 } 170 171 func (s *networkingSuite) TestNetworkInterfaces(c *gc.C) { 172 defer s.expectNeutronCalls(c).Finish() 173 s.externalNetwork = "" 174 s.expectListSubnets() 175 176 s.neutron.EXPECT().ListFloatingIPsV2(gomock.Any()).Return([]neutron.FloatingIPV2{ 177 { 178 FixedIP: "10.0.0.2", 179 IP: "10.245.164.31", 180 }, 181 }, nil) 182 183 s.neutron.EXPECT().ListPortsV2(gomock.Any()).Return([]neutron.PortV2{ 184 { 185 DeviceId: "another-instance", 186 DeviceOwner: "compute:nova", 187 }, 188 { 189 Id: "nic-0", 190 DeviceId: "inst-0", 191 NetworkId: "deadbeef-0bad-400d-8000-4b1ddbeefbeef", 192 DeviceOwner: "compute:nova", 193 MACAddress: "aa:bb:cc:dd:ee:ff", 194 Status: "ACTIVE", 195 FixedIPs: []neutron.PortFixedIPsV2{ 196 { 197 IPAddress: "192.168.0.2", 198 SubnetID: "sub-42", 199 }, 200 { 201 IPAddress: "10.0.0.2", 202 SubnetID: "sub-665", 203 }, 204 }, 205 }, 206 { 207 Id: "nic-1", 208 DeviceId: "inst-0", 209 NetworkId: "deadbeef-0bad-400d-8000-4b1ddbeefbeef", 210 DeviceOwner: "compute:nova", 211 MACAddress: "10:20:30:40:50:60", 212 Status: "N/A", 213 FixedIPs: []neutron.PortFixedIPsV2{ 214 { 215 IPAddress: "192.168.0.42", 216 SubnetID: "sub-42", 217 }, 218 }, 219 }, 220 }, nil) 221 222 nn := &NeutronNetworking{NetworkingBase: s.base} 223 224 res, err := nn.NetworkInterfaces([]instance.Id{"inst-0"}) 225 c.Assert(err, jc.ErrorIsNil) 226 227 c.Assert(res, gc.HasLen, 1) 228 c.Assert(res[0], gc.HasLen, 2, gc.Commentf("expected to get 2 NICs for machine-0")) 229 230 nic0 := res[0][0] 231 c.Assert(nic0.InterfaceType, gc.Equals, network.EthernetDevice) 232 c.Assert(nic0.Origin, gc.Equals, network.OriginProvider) 233 c.Assert(nic0.Disabled, jc.IsFalse) 234 c.Assert(nic0.MACAddress, gc.Equals, "aa:bb:cc:dd:ee:ff") 235 c.Assert(nic0.Addresses, gc.DeepEquals, network.ProviderAddresses{ 236 network.NewMachineAddress( 237 "192.168.0.2", 238 network.WithCIDR("192.168.0.0/24"), 239 network.WithScope(network.ScopeCloudLocal), 240 network.WithConfigType(network.ConfigStatic), 241 ).AsProviderAddress(), 242 network.NewMachineAddress( 243 "10.0.0.2", 244 network.WithCIDR("10.0.0.0/24"), 245 network.WithScope(network.ScopeCloudLocal), 246 network.WithConfigType(network.ConfigStatic), 247 ).AsProviderAddress(), 248 }) 249 c.Assert(nic0.ShadowAddresses, gc.DeepEquals, network.ProviderAddresses{ 250 network.NewMachineAddress( 251 "10.245.164.31", 252 network.WithScope(network.ScopePublic), 253 ).AsProviderAddress(), 254 }) 255 c.Assert(nic0.ProviderId, gc.Equals, network.Id("nic-0")) 256 c.Assert(nic0.ProviderNetworkId, gc.Equals, network.Id("deadbeef-0bad-400d-8000-4b1ddbeefbeef")) 257 c.Assert(nic0.ProviderSubnetId, gc.Equals, network.Id("sub-42"), gc.Commentf("expected NIC to use the provider subnet ID for the primary NIC address")) 258 259 nic1 := res[0][1] 260 c.Assert(nic1.InterfaceType, gc.Equals, network.EthernetDevice) 261 c.Assert(nic1.Origin, gc.Equals, network.OriginProvider) 262 c.Assert(nic1.Disabled, jc.IsTrue, gc.Commentf("expected device to be listed as disabled")) 263 c.Assert(nic1.MACAddress, gc.Equals, "10:20:30:40:50:60") 264 c.Assert(nic1.Addresses, gc.DeepEquals, network.ProviderAddresses{ 265 network.NewMachineAddress( 266 "192.168.0.42", 267 network.WithCIDR("192.168.0.0/24"), 268 network.WithScope(network.ScopeCloudLocal), 269 network.WithConfigType(network.ConfigStatic), 270 ).AsProviderAddress(), 271 }) 272 c.Assert(nic1.ProviderId, gc.Equals, network.Id("nic-1")) 273 c.Assert(nic1.ProviderNetworkId, gc.Equals, network.Id("deadbeef-0bad-400d-8000-4b1ddbeefbeef")) 274 c.Assert(nic1.ProviderSubnetId, gc.Equals, network.Id("sub-42"), gc.Commentf("expected NIC to use the provider subnet ID for the primary NIC address")) 275 } 276 277 func (s *networkingSuite) TestNetworkInterfacesPartialMatch(c *gc.C) { 278 defer s.expectNeutronCalls(c).Finish() 279 s.externalNetwork = "" 280 s.expectListSubnets() 281 282 s.neutron.EXPECT().ListFloatingIPsV2(gomock.Any()).Return(nil, nil) 283 284 s.neutron.EXPECT().ListPortsV2(gomock.Any()).Return([]neutron.PortV2{ 285 { 286 Id: "nic-0", 287 DeviceId: "inst-0", 288 NetworkId: "deadbeef-0bad-400d-8000-4b1ddbeefbeef", 289 DeviceOwner: "compute:nova", 290 MACAddress: "aa:bb:cc:dd:ee:ff", 291 Status: "ACTIVE", 292 }, 293 }, nil) 294 295 nn := &NeutronNetworking{NetworkingBase: s.base} 296 297 res, err := nn.NetworkInterfaces([]instance.Id{"inst-0", "bogus-0"}) 298 c.Assert(err, gc.Equals, environs.ErrPartialInstances) 299 300 c.Assert(res, gc.HasLen, 2) 301 c.Assert(res[0], gc.HasLen, 1, gc.Commentf("expected to get 1 NIC for inst-0")) 302 c.Assert(res[1], gc.IsNil, gc.Commentf("expected a nil slice for non-matched machines")) 303 } 304 305 func (s *networkingSuite) expectNeutronCalls(c *gc.C) *gomock.Controller { 306 ctrl := gomock.NewController(c) 307 308 s.client = NewMockNetworkingAuthenticatingClient(ctrl) 309 s.client.EXPECT().TenantId().Return("TenantId").AnyTimes() 310 311 s.neutron = NewMockNetworkingNeutron(ctrl) 312 313 s.ecfg = NewMockNetworkingEnvironConfig(ctrl) 314 315 s.base = NewMockNetworkingBase(ctrl) 316 bExp := s.base.EXPECT() 317 bExp.neutron().Return(s.neutron).AnyTimes() 318 bExp.client().Return(s.client).AnyTimes() 319 bExp.ecfg().Return(s.ecfg).AnyTimes() 320 321 return ctrl 322 } 323 324 func (s *networkingSuite) expectListSubnets() { 325 s.ecfg.EXPECT().networks().Return([]string{"int-net"}) 326 327 s.expectExternalNetwork() 328 s.neutron.EXPECT().ListNetworksV2(gomock.Any()).Return([]neutron.NetworkV2{ 329 { 330 Id: "deadbeef-0bad-400d-8000-4b1ddbeefbeef", 331 Name: "int-net", 332 AvailabilityZones: []string{s.serverAZ}, 333 }, 334 }, nil) 335 s.neutron.EXPECT().ListSubnetsV2().Return([]neutron.SubnetV2{ 336 { 337 Id: "sub-42", 338 NetworkId: "deadbeef-0bad-400d-8000-4b1ddbeefbeef", 339 Cidr: "192.168.0.0/24", 340 }, 341 { 342 Id: "sub-665", 343 NetworkId: "deadbeef-0bad-400d-8000-4b1ddbeefbeef", 344 Cidr: "10.0.0.0/24", 345 }, 346 }, nil) 347 s.neutron.EXPECT().GetNetworkV2("deadbeef-0bad-400d-8000-4b1ddbeefbeef").Return(&neutron.NetworkV2{ 348 AvailabilityZones: []string{"mars"}, 349 }, nil).AnyTimes() 350 } 351 352 func (s *networkingSuite) setupMocks(c *gc.C) *gomock.Controller { 353 ctrl := gomock.NewController(c) 354 355 s.neutron = NewMockNetworkingNeutron(ctrl) 356 s.nova = NewMockNetworkingNova(ctrl) 357 s.client = NewMockNetworkingAuthenticatingClient(ctrl) 358 s.ecfg = NewMockNetworkingEnvironConfig(ctrl) 359 360 s.base = NewMockNetworkingBase(ctrl) 361 bExp := s.base.EXPECT() 362 bExp.client().Return(s.client).AnyTimes() 363 bExp.neutron().Return(s.neutron).AnyTimes() 364 bExp.nova().Return(s.nova) 365 bExp.ecfg().Return(s.ecfg) 366 367 s.client.EXPECT().TenantId().Return("TenantId").AnyTimes() 368 s.nova.EXPECT().GetServer(gomock.Any()).Return(&nova.ServerDetail{ 369 Addresses: map[string][]nova.IPAddress{ 370 "int-net": {}, 371 }, 372 AvailabilityZone: s.serverAZ, 373 }, nil) 374 375 return ctrl 376 } 377 378 func (s *networkingSuite) runAllocatePublicIP() (*string, error) { 379 networking := &NeutronNetworking{NetworkingBase: s.base} 380 return networking.AllocatePublicIP(instance.Id("32")) 381 } 382 383 func (s *networkingSuite) expectListFloatingIPsV2FromConfig() { 384 s.neutron.EXPECT().ListFloatingIPsV2(gomock.Any()).Return([]neutron.FloatingIPV2{ 385 {FloatingNetworkId: "deadbeef-0bad-400d-8000-4b1ddeadbeef", IP: s.ip}, 386 }, nil) 387 } 388 389 func (s *networkingSuite) expectListFloatingIPsV2FromConfigInUse() { 390 s.neutron.EXPECT().ListFloatingIPsV2(gomock.Any()).Return([]neutron.FloatingIPV2{{ 391 FloatingNetworkId: "deadbeef-0bad-400d-8000-4b1ddeadbeef", 392 FixedIP: "10.7.8.9", 393 IP: s.ip, 394 }}, nil) 395 } 396 397 func (s *networkingSuite) expectListFloatingIPsV2NotFromConfig() { 398 s.neutron.EXPECT().ListFloatingIPsV2(gomock.Any()).Return([]neutron.FloatingIPV2{ 399 {FloatingNetworkId: "deadbeef-0bad-400d-8000-4b1d0d06f00d", IP: s.ip2}, 400 }, nil) 401 } 402 403 func (s *networkingSuite) expectListFloatingIPsV2Empty() { 404 s.neutron.EXPECT().ListFloatingIPsV2(gomock.Any()).Return([]neutron.FloatingIPV2{}, nil) 405 } 406 407 func (s *networkingSuite) expectExternalNetwork() { 408 s.ecfg.EXPECT().externalNetwork().Return(s.externalNetwork) 409 } 410 411 func (s *networkingSuite) expectListExternalNetworksV2() { 412 s.neutron.EXPECT().ListNetworksV2(gomock.Any()).Return([]neutron.NetworkV2{ 413 { 414 Id: "deadbeef-0bad-400d-8000-4b1ddeadbeef", 415 Name: s.externalNetwork, 416 External: true, 417 AvailabilityZones: []string{s.serverAZ}, 418 }, { 419 Name: "do-not-pick-me", 420 External: true, 421 AvailabilityZones: []string{"failme"}, 422 }, { 423 Id: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 424 Name: "unconfigured-ext-net", 425 External: true, 426 AvailabilityZones: []string{s.serverAZ}, 427 }, 428 }, nil) 429 } 430 431 func (s *networkingSuite) expectListInternalNetworksV2() { 432 s.neutron.EXPECT().ListNetworksV2(gomock.Any()).Return([]neutron.NetworkV2{ 433 { 434 Id: "deadbeef-0bad-400d-8000-4b1ddbeefbeef", 435 Name: "int-net", 436 AvailabilityZones: []string{s.serverAZ}, 437 }, { 438 Name: "internal-do-not-pick-me", 439 AvailabilityZones: []string{"failme"}, 440 }, { 441 Id: "deadbeef-0bad-400d-8000-4b1d8273450d", 442 Name: "unconfigured-int-net", 443 AvailabilityZones: []string{s.serverAZ}, 444 }, 445 }, nil) 446 } 447 448 func (s *networkingSuite) expectListExternalNetworksV2MultiAZ() { 449 s.neutron.EXPECT().ListNetworksV2(gomock.Any()).Return([]neutron.NetworkV2{ 450 { 451 Name: "do-not-pick-me", 452 AvailabilityZones: []string{"failme"}, 453 }, { 454 Id: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 455 Name: "unconfigured-ext-net", 456 AvailabilityZones: []string{"other", s.serverAZ}, 457 }, 458 }, nil).AnyTimes() 459 } 460 461 func (s *networkingSuite) expectListExternalNetworksV2NotInAZ() { 462 s.neutron.EXPECT().ListNetworksV2(gomock.Any()).Return([]neutron.NetworkV2{ 463 { 464 Name: "do-not-pick-me", 465 AvailabilityZones: []string{"failme"}, 466 }, { 467 Id: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 468 Name: "unconfigured-ext-net", 469 AvailabilityZones: []string{"other"}, 470 }, 471 }, nil).AnyTimes() 472 } 473 474 func (s *networkingSuite) expectAllocateFloatingIPV2() { 475 s.neutron.EXPECT().AllocateFloatingIPV2("deadbeef-0bad-400d-8000-4b1ddeadbeef").Return(nil, errors.NotFoundf("fip")) 476 s.neutron.EXPECT().AllocateFloatingIPV2("deadbeef-0bad-400d-8000-4b1d0d06f00d").Return(&neutron.FloatingIPV2{ 477 FloatingNetworkId: "deadbeef-0bad-400d-8000-4b1d0d06f00d", 478 IP: s.ip3, 479 }, nil) 480 } 481 482 func (s *networkingSuite) expectAllocateFloatingIPV2FailAll() { 483 s.neutron.EXPECT().AllocateFloatingIPV2(gomock.Any()).Return(nil, errors.NotFoundf("fip")).AnyTimes() 484 }