github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/container/lxd/network_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd_test 5 6 import ( 7 "errors" 8 9 lxdapi "github.com/canonical/lxd/shared/api" 10 jc "github.com/juju/testing/checkers" 11 "go.uber.org/mock/gomock" 12 gc "gopkg.in/check.v1" 13 14 "github.com/juju/juju/container/lxd" 15 lxdtesting "github.com/juju/juju/container/lxd/testing" 16 corenetwork "github.com/juju/juju/core/network" 17 "github.com/juju/juju/network" 18 ) 19 20 type networkSuite struct { 21 lxdtesting.BaseSuite 22 } 23 24 var _ = gc.Suite(&networkSuite{}) 25 26 func (s *networkSuite) patch() { 27 lxd.PatchGenerateVirtualMACAddress(s) 28 } 29 30 func defaultProfileWithNIC() *lxdapi.Profile { 31 return &lxdapi.Profile{ 32 Name: "default", 33 ProfilePut: lxdapi.ProfilePut{ 34 Devices: map[string]map[string]string{ 35 "eth0": { 36 "network": network.DefaultLXDBridge, 37 "type": "nic", 38 }, 39 }, 40 }, 41 } 42 } 43 44 func defaultLegacyProfileWithNIC() *lxdapi.Profile { 45 return &lxdapi.Profile{ 46 Name: "default", 47 ProfilePut: lxdapi.ProfilePut{ 48 Devices: map[string]map[string]string{ 49 "eth0": { 50 "parent": network.DefaultLXDBridge, 51 "type": "nic", 52 "nictype": "bridged", 53 }, 54 }, 55 }, 56 } 57 } 58 59 func (s *networkSuite) TestEnsureIPv4NoChange(c *gc.C) { 60 ctrl := gomock.NewController(c) 61 defer ctrl.Finish() 62 cSvr := s.NewMockServerWithExtensions(ctrl, "network") 63 64 net := &lxdapi.Network{ 65 NetworkPut: lxdapi.NetworkPut{ 66 Config: map[string]string{ 67 "ipv4.address": "10.5.3.1", 68 }, 69 }, 70 } 71 cSvr.EXPECT().GetNetwork("some-net-name").Return(net, lxdtesting.ETag, nil) 72 73 jujuSvr, err := lxd.NewServer(cSvr) 74 c.Assert(err, jc.ErrorIsNil) 75 76 mod, err := jujuSvr.EnsureIPv4("some-net-name") 77 c.Assert(err, jc.ErrorIsNil) 78 c.Check(mod, jc.IsFalse) 79 } 80 81 func (s *networkSuite) TestEnsureIPv4Modified(c *gc.C) { 82 ctrl := gomock.NewController(c) 83 defer ctrl.Finish() 84 cSvr := s.NewMockServerWithExtensions(ctrl, "network") 85 86 req := lxdapi.NetworkPut{ 87 Config: map[string]string{ 88 "ipv4.address": "auto", 89 "ipv4.nat": "true", 90 }, 91 } 92 gomock.InOrder( 93 cSvr.EXPECT().GetNetwork(network.DefaultLXDBridge).Return(&lxdapi.Network{}, lxdtesting.ETag, nil), 94 cSvr.EXPECT().UpdateNetwork(network.DefaultLXDBridge, req, lxdtesting.ETag).Return(nil), 95 ) 96 97 jujuSvr, err := lxd.NewServer(cSvr) 98 c.Assert(err, jc.ErrorIsNil) 99 100 mod, err := jujuSvr.EnsureIPv4(network.DefaultLXDBridge) 101 c.Assert(err, jc.ErrorIsNil) 102 c.Check(mod, jc.IsTrue) 103 } 104 105 func (s *networkSuite) TestGetNICsFromProfile(c *gc.C) { 106 lxd.PatchGenerateVirtualMACAddress(s) 107 108 ctrl := gomock.NewController(c) 109 defer ctrl.Finish() 110 cSvr := s.NewMockServer(ctrl) 111 112 cSvr.EXPECT().GetProfile("default").Return(defaultLegacyProfileWithNIC(), lxdtesting.ETag, nil) 113 114 jujuSvr, err := lxd.NewServer(cSvr) 115 c.Assert(err, jc.ErrorIsNil) 116 117 nics, err := jujuSvr.GetNICsFromProfile("default") 118 c.Assert(err, jc.ErrorIsNil) 119 120 exp := map[string]map[string]string{ 121 "eth0": { 122 "parent": network.DefaultLXDBridge, 123 "type": "nic", 124 "nictype": "bridged", 125 "hwaddr": "00:16:3e:00:00:00", 126 }, 127 } 128 129 c.Check(nics, gc.DeepEquals, exp) 130 } 131 132 func (s *networkSuite) TestVerifyNetworkDevicePresentValid(c *gc.C) { 133 ctrl := gomock.NewController(c) 134 defer ctrl.Finish() 135 cSvr := s.NewMockServerWithExtensions(ctrl, "network") 136 137 net := &lxdapi.Network{ 138 Name: network.DefaultLXDBridge, 139 Managed: true, 140 Type: "bridge", 141 } 142 cSvr.EXPECT().GetNetwork(network.DefaultLXDBridge).Return(net, "", nil) 143 144 jujuSvr, err := lxd.NewServer(cSvr) 145 c.Assert(err, jc.ErrorIsNil) 146 147 err = jujuSvr.VerifyNetworkDevice(defaultProfileWithNIC(), "") 148 c.Assert(err, jc.ErrorIsNil) 149 } 150 151 func (s *networkSuite) TestVerifyNetworkDevicePresentValidLegacy(c *gc.C) { 152 ctrl := gomock.NewController(c) 153 defer ctrl.Finish() 154 cSvr := s.NewMockServerWithExtensions(ctrl, "network") 155 156 cSvr.EXPECT().GetNetwork(network.DefaultLXDBridge).Return(&lxdapi.Network{}, "", nil) 157 158 jujuSvr, err := lxd.NewServer(cSvr) 159 c.Assert(err, jc.ErrorIsNil) 160 161 err = jujuSvr.VerifyNetworkDevice(defaultLegacyProfileWithNIC(), "") 162 c.Assert(err, jc.ErrorIsNil) 163 } 164 165 func (s *networkSuite) TestVerifyNetworkDeviceMultipleNICsOneValid(c *gc.C) { 166 ctrl := gomock.NewController(c) 167 defer ctrl.Finish() 168 cSvr := s.NewMockServerClustered(ctrl, "cluster-1") 169 170 profile := defaultLegacyProfileWithNIC() 171 profile.Devices["eno1"] = profile.Devices["eth0"] 172 profile.Devices["eno1"]["parent"] = "valid-net" 173 174 net := &lxdapi.Network{ 175 Name: network.DefaultLXDBridge, 176 Managed: true, 177 NetworkPut: lxdapi.NetworkPut{ 178 Config: map[string]string{ 179 "ipv6.address": "something-not-nothing", 180 }, 181 }, 182 } 183 184 // Random map iteration may or may not cause this call to be made. 185 cSvr.EXPECT().GetNetwork(network.DefaultLXDBridge).Return(net, "", nil).MaxTimes(1) 186 cSvr.EXPECT().GetNetwork("valid-net").Return(&lxdapi.Network{}, "", nil) 187 188 jujuSvr, err := lxd.NewServer(cSvr) 189 c.Assert(err, jc.ErrorIsNil) 190 191 err = jujuSvr.VerifyNetworkDevice(profile, "") 192 c.Assert(err, jc.ErrorIsNil) 193 194 c.Check(jujuSvr.LocalBridgeName(), gc.Equals, "valid-net") 195 } 196 197 func (s *networkSuite) TestVerifyNetworkDevicePresentBadNicType(c *gc.C) { 198 ctrl := gomock.NewController(c) 199 defer ctrl.Finish() 200 cSvr := s.NewMockServerWithExtensions(ctrl, "network") 201 202 profile := defaultLegacyProfileWithNIC() 203 profile.Devices["eth0"]["nictype"] = "not-bridge-or-macvlan" 204 205 net := &lxdapi.Network{ 206 Name: network.DefaultLXDBridge, 207 Managed: true, 208 Type: "not-bridge-or-macvlan", 209 } 210 cSvr.EXPECT().GetNetwork(network.DefaultLXDBridge).Return(net, "", nil) 211 212 jujuSvr, err := lxd.NewServer(cSvr) 213 c.Assert(err, jc.ErrorIsNil) 214 215 err = jujuSvr.VerifyNetworkDevice(profile, "") 216 c.Assert(err, gc.ErrorMatches, 217 `profile "default": no network device found with nictype "bridged" or "macvlan"\n`+ 218 `\tthe following devices were checked: eth0\n`+ 219 `Reconfigure lxd to use a network of type "bridged" or "macvlan".`) 220 } 221 222 // Juju used to fail when IPv6 was enabled on the lxd network. This test now 223 // checks regression to make sure that we know longer fail. 224 func (s *networkSuite) TestVerifyNetworkDeviceIPv6PresentNoFail(c *gc.C) { 225 ctrl := gomock.NewController(c) 226 defer ctrl.Finish() 227 cSvr := s.NewMockServerWithExtensions(ctrl, "network") 228 229 net := &lxdapi.Network{ 230 Name: network.DefaultLXDBridge, 231 Managed: true, 232 NetworkPut: lxdapi.NetworkPut{ 233 Config: map[string]string{ 234 "ipv6.address": "2001:DB8::1", 235 }, 236 }, 237 } 238 cSvr.EXPECT().GetNetwork(network.DefaultLXDBridge).Return(net, "", nil) 239 240 jujuSvr, err := lxd.NewServer(cSvr) 241 c.Assert(err, jc.ErrorIsNil) 242 243 err = jujuSvr.VerifyNetworkDevice(defaultLegacyProfileWithNIC(), "") 244 c.Assert(err, jc.ErrorIsNil) 245 } 246 247 func (s *networkSuite) TestVerifyNetworkDeviceNotPresentCreated(c *gc.C) { 248 s.patch() 249 250 ctrl := gomock.NewController(c) 251 defer ctrl.Finish() 252 cSvr := s.NewMockServerWithExtensions(ctrl, "network") 253 254 netConf := map[string]string{ 255 "ipv4.address": "auto", 256 "ipv4.nat": "true", 257 "ipv6.address": "none", 258 "ipv6.nat": "false", 259 } 260 netCreateReq := lxdapi.NetworksPost{ 261 Name: network.DefaultLXDBridge, 262 Type: "bridge", 263 NetworkPut: lxdapi.NetworkPut{Config: netConf}, 264 } 265 newNet := &lxdapi.Network{ 266 Name: network.DefaultLXDBridge, 267 Type: "bridge", 268 Managed: true, 269 NetworkPut: lxdapi.NetworkPut{Config: netConf}, 270 } 271 gomock.InOrder( 272 cSvr.EXPECT().GetNetwork(network.DefaultLXDBridge).Return(nil, "", errors.New("network not found")), 273 cSvr.EXPECT().CreateNetwork(netCreateReq).Return(nil), 274 cSvr.EXPECT().GetNetwork(network.DefaultLXDBridge).Return(newNet, "", nil), 275 cSvr.EXPECT().UpdateProfile("default", defaultLegacyProfileWithNIC().Writable(), lxdtesting.ETag).Return(nil), 276 ) 277 278 profile := defaultLegacyProfileWithNIC() 279 delete(profile.Devices, "eth0") 280 281 jujuSvr, err := lxd.NewServer(cSvr) 282 c.Assert(err, jc.ErrorIsNil) 283 284 err = jujuSvr.VerifyNetworkDevice(profile, lxdtesting.ETag) 285 c.Assert(err, jc.ErrorIsNil) 286 } 287 288 func (s *networkSuite) TestVerifyNetworkDeviceNotPresentNoNetAPIError(c *gc.C) { 289 s.patch() 290 291 ctrl := gomock.NewController(c) 292 defer ctrl.Finish() 293 cSvr := s.NewMockServer(ctrl) 294 295 jujuSvr, err := lxd.NewServer(cSvr) 296 c.Assert(err, jc.ErrorIsNil) 297 298 profile := defaultLegacyProfileWithNIC() 299 delete(profile.Devices, "eth0") 300 301 err = jujuSvr.VerifyNetworkDevice(profile, lxdtesting.ETag) 302 c.Assert(err, gc.ErrorMatches, `profile "default" does not have any devices configured with type "nic"`) 303 } 304 305 func (s *networkSuite) TestVerifyNetworkDevicePresentNoNetAPIError(c *gc.C) { 306 s.patch() 307 308 ctrl := gomock.NewController(c) 309 defer ctrl.Finish() 310 cSvr := s.NewMockServer(ctrl) 311 312 jujuSvr, err := lxd.NewServer(cSvr) 313 c.Assert(err, jc.ErrorIsNil) 314 315 profile := defaultLegacyProfileWithNIC() 316 317 err = jujuSvr.VerifyNetworkDevice(profile, lxdtesting.ETag) 318 c.Assert(err, gc.ErrorMatches, "versions of LXD without network API not supported") 319 } 320 321 func (s *networkSuite) TestVerifyNetworkDeviceNotPresentCreatedWithUnusedName(c *gc.C) { 322 s.patch() 323 324 ctrl := gomock.NewController(c) 325 defer ctrl.Finish() 326 cSvr := s.NewMockServerWithExtensions(ctrl, "network") 327 328 defaultBridge := &lxdapi.Network{ 329 Name: network.DefaultLXDBridge, 330 Type: "bridge", 331 Managed: true, 332 NetworkPut: lxdapi.NetworkPut{ 333 Config: map[string]string{ 334 "ipv4.address": "auto", 335 "ipv4.nat": "true", 336 "ipv6.address": "none", 337 "ipv6.nat": "false", 338 }, 339 }, 340 } 341 devReq := lxdapi.ProfilePut{ 342 Devices: map[string]map[string]string{ 343 "eth0": {}, 344 "eth1": {}, 345 // eth2 will be generated as the first unused device name. 346 "eth2": { 347 "parent": network.DefaultLXDBridge, 348 "type": "nic", 349 "nictype": "bridged", 350 }, 351 }, 352 } 353 gomock.InOrder( 354 cSvr.EXPECT().GetNetwork(network.DefaultLXDBridge).Return(defaultBridge, "", nil), 355 cSvr.EXPECT().UpdateProfile("default", devReq, lxdtesting.ETag).Return(nil), 356 ) 357 358 profile := defaultLegacyProfileWithNIC() 359 profile.Devices["eth0"] = map[string]string{} 360 profile.Devices["eth1"] = map[string]string{} 361 362 jujuSvr, err := lxd.NewServer(cSvr) 363 c.Assert(err, jc.ErrorIsNil) 364 365 err = jujuSvr.VerifyNetworkDevice(profile, lxdtesting.ETag) 366 c.Assert(err, jc.ErrorIsNil) 367 } 368 369 func (s *networkSuite) TestVerifyNetworkDeviceNotPresentErrorForCluster(c *gc.C) { 370 ctrl := gomock.NewController(c) 371 defer ctrl.Finish() 372 cSvr := s.NewMockServerClustered(ctrl, "cluster-1") 373 374 profile := defaultLegacyProfileWithNIC() 375 delete(profile.Devices, "eth0") 376 377 jujuSvr, err := lxd.NewServer(cSvr) 378 c.Assert(err, jc.ErrorIsNil) 379 380 err = jujuSvr.VerifyNetworkDevice(profile, lxdtesting.ETag) 381 c.Assert(err, gc.ErrorMatches, `profile "default" does not have any devices configured with type "nic"`) 382 } 383 384 func (s *networkSuite) TestInterfaceInfoFromDevices(c *gc.C) { 385 nics := map[string]map[string]string{ 386 "eth0": { 387 "parent": network.DefaultLXDBridge, 388 "type": "nic", 389 "nictype": "bridged", 390 "hwaddr": "00:16:3e:00:00:00", 391 }, 392 "eno9": { 393 "parent": "br1", 394 "type": "nic", 395 "nictype": "macvlan", 396 "hwaddr": "00:16:3e:00:00:3e", 397 }, 398 } 399 400 info, err := lxd.InterfaceInfoFromDevices(nics) 401 c.Assert(err, jc.ErrorIsNil) 402 403 exp := corenetwork.InterfaceInfos{ 404 { 405 InterfaceName: "eno9", 406 MACAddress: "00:16:3e:00:00:3e", 407 ConfigType: corenetwork.ConfigDHCP, 408 ParentInterfaceName: "br1", 409 Origin: corenetwork.OriginProvider, 410 }, 411 { 412 InterfaceName: "eth0", 413 MACAddress: "00:16:3e:00:00:00", 414 ConfigType: corenetwork.ConfigDHCP, 415 ParentInterfaceName: network.DefaultLXDBridge, 416 Origin: corenetwork.OriginProvider, 417 }, 418 } 419 c.Check(info, jc.DeepEquals, exp) 420 } 421 422 func (s *networkSuite) TestEnableHTTPSListener(c *gc.C) { 423 ctrl := gomock.NewController(c) 424 defer ctrl.Finish() 425 426 cfg := &lxdapi.Server{} 427 cSvr := lxdtesting.NewMockInstanceServer(ctrl) 428 429 gomock.InOrder( 430 cSvr.EXPECT().GetServer().Return(cfg, lxdtesting.ETag, nil).Times(2), 431 cSvr.EXPECT().UpdateServer(lxdapi.ServerPut{ 432 Config: map[string]interface{}{ 433 "core.https_address": "[::]", 434 }, 435 }, lxdtesting.ETag).Return(nil), 436 ) 437 438 jujuSvr, err := lxd.NewServer(cSvr) 439 c.Assert(err, jc.ErrorIsNil) 440 441 err = jujuSvr.EnableHTTPSListener() 442 c.Assert(err, jc.ErrorIsNil) 443 } 444 445 func (s *networkSuite) TestEnableHTTPSListenerWithFallbackToIPv4(c *gc.C) { 446 ctrl := gomock.NewController(c) 447 defer ctrl.Finish() 448 449 cfg := &lxdapi.Server{} 450 cSvr := lxdtesting.NewMockInstanceServer(ctrl) 451 452 gomock.InOrder( 453 cSvr.EXPECT().GetServer().Return(cfg, lxdtesting.ETag, nil).Times(2), 454 cSvr.EXPECT().UpdateServer(lxdapi.ServerPut{ 455 Config: map[string]interface{}{ 456 "core.https_address": "[::]", 457 }, 458 }, lxdtesting.ETag).Return(errors.New(lxd.ErrIPV6NotSupported)), 459 cSvr.EXPECT().GetServer().Return(cfg, lxdtesting.ETag, nil), 460 cSvr.EXPECT().UpdateServer(lxdapi.ServerPut{ 461 Config: map[string]interface{}{ 462 "core.https_address": "0.0.0.0", 463 }, 464 }, lxdtesting.ETag).Return(nil), 465 ) 466 467 jujuSvr, err := lxd.NewServer(cSvr) 468 c.Assert(err, jc.ErrorIsNil) 469 470 err = jujuSvr.EnableHTTPSListener() 471 c.Assert(err, jc.ErrorIsNil) 472 } 473 474 func (s *networkSuite) TestEnableHTTPSListenerWithErrors(c *gc.C) { 475 ctrl := gomock.NewController(c) 476 defer ctrl.Finish() 477 478 cfg := &lxdapi.Server{} 479 cSvr := lxdtesting.NewMockInstanceServer(ctrl) 480 481 cSvr.EXPECT().GetServer().Return(cfg, lxdtesting.ETag, nil) 482 483 jujuSvr, err := lxd.NewServer(cSvr) 484 c.Assert(err, jc.ErrorIsNil) 485 486 // check on the first request 487 cSvr.EXPECT().GetServer().Return(cfg, lxdtesting.ETag, errors.New("bad")) 488 489 err = jujuSvr.EnableHTTPSListener() 490 c.Assert(err, gc.ErrorMatches, "bad") 491 492 // check on the second request 493 gomock.InOrder( 494 cSvr.EXPECT().GetServer().Return(cfg, lxdtesting.ETag, nil), 495 cSvr.EXPECT().UpdateServer(lxdapi.ServerPut{ 496 Config: map[string]interface{}{ 497 "core.https_address": "[::]", 498 }, 499 }, lxdtesting.ETag).Return(errors.New(lxd.ErrIPV6NotSupported)), 500 cSvr.EXPECT().GetServer().Return(cfg, lxdtesting.ETag, errors.New("bad")), 501 ) 502 503 err = jujuSvr.EnableHTTPSListener() 504 c.Assert(err, gc.ErrorMatches, "bad") 505 506 // check on the third request 507 gomock.InOrder( 508 cSvr.EXPECT().GetServer().Return(cfg, lxdtesting.ETag, nil), 509 cSvr.EXPECT().UpdateServer(lxdapi.ServerPut{ 510 Config: map[string]interface{}{ 511 "core.https_address": "[::]", 512 }, 513 }, lxdtesting.ETag).Return(errors.New(lxd.ErrIPV6NotSupported)), 514 cSvr.EXPECT().GetServer().Return(cfg, lxdtesting.ETag, nil), 515 cSvr.EXPECT().UpdateServer(lxdapi.ServerPut{ 516 Config: map[string]interface{}{ 517 "core.https_address": "0.0.0.0", 518 }, 519 }, lxdtesting.ETag).Return(errors.New("bad")), 520 ) 521 522 err = jujuSvr.EnableHTTPSListener() 523 c.Assert(err, gc.ErrorMatches, "bad") 524 } 525 526 func (s *networkSuite) TestNewNICDeviceWithoutMACAddressOrMTUGreaterThanZero(c *gc.C) { 527 device := lxd.NewNICDevice("eth1", "br-eth1", "", 0) 528 expected := map[string]string{ 529 "name": "eth1", 530 "nictype": "bridged", 531 "parent": "br-eth1", 532 "type": "nic", 533 } 534 c.Assert(device, gc.DeepEquals, expected) 535 } 536 537 func (s *networkSuite) TestNewNICDeviceWithMACAddressButNoMTU(c *gc.C) { 538 device := lxd.NewNICDevice("eth1", "br-eth1", "aa:bb:cc:dd:ee:f0", 0) 539 expected := map[string]string{ 540 "hwaddr": "aa:bb:cc:dd:ee:f0", 541 "name": "eth1", 542 "nictype": "bridged", 543 "parent": "br-eth1", 544 "type": "nic", 545 } 546 c.Assert(device, gc.DeepEquals, expected) 547 } 548 549 func (s *networkSuite) TestNewNICDeviceWithoutMACAddressButMTUGreaterThanZero(c *gc.C) { 550 device := lxd.NewNICDevice("eth1", "br-eth1", "", 1492) 551 expected := map[string]string{ 552 "mtu": "1492", 553 "name": "eth1", 554 "nictype": "bridged", 555 "parent": "br-eth1", 556 "type": "nic", 557 } 558 c.Assert(device, gc.DeepEquals, expected) 559 } 560 561 func (s *networkSuite) TestNewNICDeviceWithMACAddressAndMTUGreaterThanZero(c *gc.C) { 562 device := lxd.NewNICDevice("eth1", "br-eth1", "aa:bb:cc:dd:ee:f0", 9000) 563 expected := map[string]string{ 564 "hwaddr": "aa:bb:cc:dd:ee:f0", 565 "mtu": "9000", 566 "name": "eth1", 567 "nictype": "bridged", 568 "parent": "br-eth1", 569 "type": "nic", 570 } 571 c.Assert(device, gc.DeepEquals, expected) 572 }