github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/lxd/server_integration_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd_test 5 6 import ( 7 "net" 8 "net/url" 9 "os" 10 "syscall" 11 12 client "github.com/canonical/lxd/client" 13 "github.com/canonical/lxd/shared/api" 14 "github.com/juju/errors" 15 "github.com/juju/testing" 16 jc "github.com/juju/testing/checkers" 17 "go.uber.org/mock/gomock" 18 gc "gopkg.in/check.v1" 19 20 "github.com/juju/juju/cloud" 21 environscloudspec "github.com/juju/juju/environs/cloudspec" 22 "github.com/juju/juju/provider/lxd" 23 ) 24 25 var ( 26 _ = gc.Suite(&serverIntegrationSuite{}) 27 ) 28 29 // serverIntegrationSuite tests server module functionality from outside the 30 // lxd package. See server_test.go for package-local unit tests. 31 type serverIntegrationSuite struct { 32 testing.IsolationSuite 33 } 34 35 func (s *serverIntegrationSuite) TestLocalServer(c *gc.C) { 36 ctrl := gomock.NewController(c) 37 defer ctrl.Finish() 38 39 profile := &api.Profile{} 40 etag := "etag" 41 bridgeName := "lxdbr0" 42 hostAddress := "192.168.0.1" 43 connectionInfo := &client.ConnectionInfo{ 44 Addresses: []string{ 45 "https://192.168.0.1:8443", 46 }, 47 } 48 49 factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl) 50 51 gomock.InOrder( 52 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 53 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 54 server.EXPECT().EnableHTTPSListener().Return(nil), 55 server.EXPECT().LocalBridgeName().Return(bridgeName), 56 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 57 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 58 server.EXPECT().StorageSupported().Return(true), 59 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 60 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 61 server.EXPECT().ServerVersion().Return("5.2"), 62 ) 63 64 svr, err := factory.LocalServer() 65 c.Assert(svr, gc.Not(gc.IsNil)) 66 c.Assert(svr, gc.Equals, server) 67 c.Assert(err, gc.IsNil) 68 } 69 70 func (s *serverIntegrationSuite) TestLocalServerRetrySemantics(c *gc.C) { 71 ctrl := gomock.NewController(c) 72 defer ctrl.Finish() 73 74 profile := &api.Profile{} 75 etag := "etag" 76 bridgeName := "lxdbr0" 77 hostAddress := "192.168.0.1" 78 emptyConnectionInfo := &client.ConnectionInfo{ 79 Addresses: []string{}, 80 } 81 connectionInfo := &client.ConnectionInfo{ 82 Addresses: []string{ 83 "https://192.168.0.1:8443", 84 }, 85 } 86 87 factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl) 88 89 gomock.InOrder( 90 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 91 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 92 server.EXPECT().EnableHTTPSListener().Return(nil), 93 server.EXPECT().LocalBridgeName().Return(bridgeName), 94 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 95 server.EXPECT().GetConnectionInfo().Return(emptyConnectionInfo, nil), 96 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 97 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 98 server.EXPECT().EnableHTTPSListener().Return(nil), 99 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 100 server.EXPECT().StorageSupported().Return(true), 101 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 102 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 103 server.EXPECT().ServerVersion().Return("5.2"), 104 ) 105 106 svr, err := factory.LocalServer() 107 c.Assert(svr, gc.Not(gc.IsNil)) 108 c.Assert(svr, gc.Equals, server) 109 c.Assert(err, gc.IsNil) 110 } 111 112 func (s *serverIntegrationSuite) TestLocalServerRetrySemanticsFailure(c *gc.C) { 113 ctrl := gomock.NewController(c) 114 defer ctrl.Finish() 115 116 profile := &api.Profile{} 117 etag := "etag" 118 bridgeName := "lxdbr0" 119 hostAddress := "192.168.0.1" 120 emptyConnectionInfo := &client.ConnectionInfo{ 121 Addresses: []string{}, 122 } 123 124 factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl) 125 126 server.EXPECT().GetProfile("default").Return(profile, etag, nil).Times(31) 127 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil).Times(31) 128 server.EXPECT().EnableHTTPSListener().Return(nil).Times(31) 129 server.EXPECT().LocalBridgeName().Return(bridgeName) 130 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil) 131 server.EXPECT().GetConnectionInfo().Return(emptyConnectionInfo, nil).Times(30) 132 133 svr, err := factory.LocalServer() 134 c.Assert(svr, gc.IsNil) 135 c.Assert(err, gc.NotNil) 136 c.Assert(err.Error(), gc.Equals, "LXD is not listening on address https://192.168.0.1 (reported addresses: [])") 137 } 138 139 func (s *serverIntegrationSuite) TestLocalServerWithInvalidAPIVersion(c *gc.C) { 140 ctrl := gomock.NewController(c) 141 defer ctrl.Finish() 142 143 profile := &api.Profile{} 144 etag := "etag" 145 bridgeName := "lxdbr0" 146 hostAddress := "192.168.0.1" 147 connectionInfo := &client.ConnectionInfo{ 148 Addresses: []string{ 149 "https://192.168.0.1:8443", 150 }, 151 } 152 153 factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl) 154 155 gomock.InOrder( 156 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 157 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 158 server.EXPECT().EnableHTTPSListener().Return(nil), 159 server.EXPECT().LocalBridgeName().Return(bridgeName), 160 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 161 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 162 server.EXPECT().StorageSupported().Return(true), 163 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 164 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 165 server.EXPECT().ServerVersion().Return("a.b"), 166 ) 167 168 svr, err := factory.LocalServer() 169 c.Assert(svr, gc.Not(gc.IsNil)) 170 c.Assert(svr, gc.Equals, server) 171 c.Assert(err, gc.IsNil) 172 } 173 174 func (s *serverIntegrationSuite) TestLocalServerErrorMessageShowsInstallMessage(c *gc.C) { 175 ctrl := gomock.NewController(c) 176 defer ctrl.Finish() 177 178 factory := lxd.NewServerFactoryWithMocks( 179 func() (lxd.Server, error) { 180 return nil, errors.New("bad") 181 }, 182 lxd.DefaultRemoteServerFunc(ctrl), 183 nil, 184 &lxd.MockClock{}, 185 ) 186 187 _, err := factory.LocalServer() 188 c.Assert(errors.Cause(err).Error(), gc.Equals, `bad 189 190 Please install LXD by running: 191 $ sudo snap install lxd 192 and then configure it with: 193 $ newgrp lxd 194 $ lxd init 195 `) 196 } 197 198 func (s *serverIntegrationSuite) TestLocalServerErrorMessageShowsConfigureMessage(c *gc.C) { 199 ctrl := gomock.NewController(c) 200 defer ctrl.Finish() 201 202 factory := lxd.NewServerFactoryWithMocks( 203 func() (lxd.Server, error) { 204 return nil, errors.Annotatef(&url.Error{ 205 Err: &net.OpError{ 206 Op: "dial", 207 Net: "unix", 208 Err: &os.SyscallError{ 209 Err: syscall.ECONNREFUSED, 210 }, 211 }, 212 }, "bad") 213 }, 214 lxd.DefaultRemoteServerFunc(ctrl), 215 nil, 216 &lxd.MockClock{}, 217 ) 218 219 _, err := factory.LocalServer() 220 c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD refused connections; is LXD running? 221 222 Please configure LXD by running: 223 $ newgrp lxd 224 $ lxd init 225 `) 226 } 227 228 func (s *serverIntegrationSuite) TestLocalServerErrorMessageShowsConfigureMessageWhenEACCES(c *gc.C) { 229 ctrl := gomock.NewController(c) 230 defer ctrl.Finish() 231 232 factory := lxd.NewServerFactoryWithMocks( 233 func() (lxd.Server, error) { 234 return nil, errors.Annotatef(&url.Error{ 235 Err: &net.OpError{ 236 Op: "dial", 237 Net: "unix", 238 Err: &os.SyscallError{ 239 Err: syscall.EACCES, 240 }, 241 }, 242 }, "bad") 243 }, 244 lxd.DefaultRemoteServerFunc(ctrl), 245 nil, 246 &lxd.MockClock{}, 247 ) 248 249 _, err := factory.LocalServer() 250 c.Assert(errors.Cause(err).Error(), gc.Equals, `Permission denied, are you in the lxd group? 251 252 Please configure LXD by running: 253 $ newgrp lxd 254 $ lxd init 255 `) 256 } 257 258 func (s *serverIntegrationSuite) TestLocalServerErrorMessageShowsInstallMessageWhenENOENT(c *gc.C) { 259 ctrl := gomock.NewController(c) 260 defer ctrl.Finish() 261 262 factory := lxd.NewServerFactoryWithMocks( 263 func() (lxd.Server, error) { 264 return nil, errors.Annotatef(&url.Error{ 265 Err: &net.OpError{ 266 Op: "dial", 267 Net: "unix", 268 Err: &os.SyscallError{ 269 Err: syscall.ENOENT, 270 }, 271 }, 272 }, "bad") 273 }, 274 lxd.DefaultRemoteServerFunc(ctrl), 275 nil, 276 &lxd.MockClock{}, 277 ) 278 279 _, err := factory.LocalServer() 280 c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD socket not found; is LXD installed & running? 281 282 Please install LXD by running: 283 $ sudo snap install lxd 284 and then configure it with: 285 $ newgrp lxd 286 $ lxd init 287 `) 288 } 289 290 func (s *serverIntegrationSuite) TestLocalServerWithStorageNotSupported(c *gc.C) { 291 ctrl := gomock.NewController(c) 292 defer ctrl.Finish() 293 294 profile := &api.Profile{} 295 etag := "etag" 296 bridgeName := "lxdbr0" 297 hostAddress := "192.168.0.1" 298 connectionInfo := &client.ConnectionInfo{ 299 Addresses: []string{ 300 "https://192.168.0.1:8443", 301 }, 302 } 303 304 factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl) 305 306 gomock.InOrder( 307 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 308 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 309 server.EXPECT().EnableHTTPSListener().Return(nil), 310 server.EXPECT().LocalBridgeName().Return(bridgeName), 311 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 312 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 313 server.EXPECT().StorageSupported().Return(false), 314 server.EXPECT().ServerVersion().Return("5.2"), 315 ) 316 317 svr, err := factory.RemoteServer(lxd.CloudSpec{}) 318 c.Assert(svr, gc.Not(gc.IsNil)) 319 c.Assert(err, gc.IsNil) 320 } 321 322 func (s *serverIntegrationSuite) TestRemoteServerWithEmptyEndpointYieldsLocalServer(c *gc.C) { 323 ctrl := gomock.NewController(c) 324 defer ctrl.Finish() 325 326 profile := &api.Profile{} 327 etag := "etag" 328 bridgeName := "lxdbr0" 329 hostAddress := "192.168.0.1" 330 connectionInfo := &client.ConnectionInfo{ 331 Addresses: []string{ 332 "https://192.168.0.1:8443", 333 }, 334 } 335 336 factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl) 337 338 gomock.InOrder( 339 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 340 server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil), 341 server.EXPECT().EnableHTTPSListener().Return(nil), 342 server.EXPECT().LocalBridgeName().Return(bridgeName), 343 interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil), 344 server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil), 345 server.EXPECT().StorageSupported().Return(true), 346 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 347 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 348 server.EXPECT().ServerVersion().Return("5.2"), 349 ) 350 351 svr, err := factory.RemoteServer(lxd.CloudSpec{}) 352 c.Assert(svr, gc.Not(gc.IsNil)) 353 c.Assert(err, gc.IsNil) 354 } 355 356 func (s *serverIntegrationSuite) TestRemoteServer(c *gc.C) { 357 ctrl := gomock.NewController(c) 358 defer ctrl.Finish() 359 360 profile := &api.Profile{} 361 etag := "etag" 362 363 factory, server := lxd.NewRemoteServerFactory(ctrl) 364 365 gomock.InOrder( 366 server.EXPECT().StorageSupported().Return(true), 367 server.EXPECT().GetProfile("default").Return(profile, etag, nil), 368 server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil), 369 server.EXPECT().ServerVersion().Return("5.2"), 370 ) 371 372 creds := cloud.NewCredential("any", map[string]string{ 373 "client-cert": "client-cert", 374 "client-key": "client-key", 375 "server-cert": "server-cert", 376 }) 377 svr, err := factory.RemoteServer( 378 lxd.CloudSpec{ 379 CloudSpec: environscloudspec.CloudSpec{ 380 Endpoint: "https://10.0.0.9:8443", 381 Credential: &creds, 382 }, 383 }) 384 c.Assert(svr, gc.Not(gc.IsNil)) 385 c.Assert(svr, gc.Equals, server) 386 c.Assert(err, gc.IsNil) 387 } 388 389 func (s *serverIntegrationSuite) TestRemoteServerWithNoStorage(c *gc.C) { 390 ctrl := gomock.NewController(c) 391 defer ctrl.Finish() 392 393 factory, server := lxd.NewRemoteServerFactory(ctrl) 394 395 gomock.InOrder( 396 server.EXPECT().StorageSupported().Return(false), 397 server.EXPECT().ServerVersion().Return("5.2"), 398 ) 399 400 creds := cloud.NewCredential("any", map[string]string{ 401 "client-cert": "client-cert", 402 "client-key": "client-key", 403 "server-cert": "server-cert", 404 }) 405 svr, err := factory.RemoteServer( 406 lxd.CloudSpec{ 407 CloudSpec: environscloudspec.CloudSpec{ 408 Endpoint: "https://10.0.0.9:8443", 409 Credential: &creds, 410 }, 411 }) 412 c.Assert(svr, gc.Not(gc.IsNil)) 413 c.Assert(svr, gc.Equals, server) 414 c.Assert(err, gc.IsNil) 415 } 416 417 func (s *serverIntegrationSuite) TestInsecureRemoteServerDoesNotCallGetServer(c *gc.C) { 418 ctrl := gomock.NewController(c) 419 defer ctrl.Finish() 420 421 factory, server := lxd.NewRemoteServerFactory(ctrl) 422 423 creds := cloud.NewCredential("any", map[string]string{ 424 "client-cert": "client-cert", 425 "client-key": "client-key", 426 "server-cert": "server-cert", 427 }) 428 svr, err := factory.InsecureRemoteServer( 429 lxd.CloudSpec{ 430 CloudSpec: environscloudspec.CloudSpec{ 431 Endpoint: "https://10.0.0.9:8443", 432 Credential: &creds, 433 }, 434 }) 435 c.Assert(svr, gc.Not(gc.IsNil)) 436 c.Assert(svr, gc.Equals, server) 437 c.Assert(err, gc.IsNil) 438 } 439 440 func (s *serverIntegrationSuite) TestRemoteServerMissingCertificates(c *gc.C) { 441 ctrl := gomock.NewController(c) 442 defer ctrl.Finish() 443 444 factory, _ := lxd.NewRemoteServerFactory(ctrl) 445 446 creds := cloud.NewCredential("any", map[string]string{}) 447 svr, err := factory.RemoteServer( 448 lxd.CloudSpec{ 449 CloudSpec: environscloudspec.CloudSpec{ 450 Endpoint: "https://10.0.0.9:8443", 451 Credential: &creds, 452 }, 453 }) 454 c.Assert(svr, gc.IsNil) 455 c.Assert(errors.Cause(err).Error(), gc.Equals, "credentials not valid") 456 } 457 458 func (s *serverIntegrationSuite) TestRemoteServerBadServerFuncError(c *gc.C) { 459 factory := lxd.NewServerFactoryWithError() 460 461 creds := cloud.NewCredential("any", map[string]string{ 462 "client-cert": "client-cert", 463 "client-key": "client-key", 464 "server-cert": "server-cert", 465 }) 466 svr, err := factory.RemoteServer( 467 lxd.CloudSpec{ 468 CloudSpec: environscloudspec.CloudSpec{ 469 Endpoint: "https://10.0.0.9:8443", 470 Credential: &creds, 471 }, 472 }) 473 c.Assert(svr, gc.IsNil) 474 c.Assert(errors.Cause(err).Error(), gc.Equals, "oops") 475 } 476 477 func (s *serverIntegrationSuite) TestRemoteServerWithUnSupportedAPIVersion(c *gc.C) { 478 ctrl := gomock.NewController(c) 479 defer ctrl.Finish() 480 481 factory, server := lxd.NewRemoteServerFactory(ctrl) 482 483 gomock.InOrder( 484 server.EXPECT().StorageSupported().Return(false), 485 server.EXPECT().ServerVersion().Return("4.0"), 486 ) 487 488 creds := cloud.NewCredential("any", map[string]string{ 489 "client-cert": "client-cert", 490 "client-key": "client-key", 491 "server-cert": "server-cert", 492 }) 493 _, err := factory.RemoteServer( 494 lxd.CloudSpec{ 495 CloudSpec: environscloudspec.CloudSpec{ 496 Endpoint: "https://10.0.0.9:8443", 497 Credential: &creds, 498 }, 499 }) 500 c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD version has to be at least "5.0.0", but current version is only "4.0.0"`) 501 } 502 503 func (s *serverIntegrationSuite) TestIsSupportedAPIVersion(c *gc.C) { 504 for _, t := range []struct { 505 input string 506 output string 507 }{ 508 { 509 input: "foo", 510 output: `LXD API version "foo": expected format <major>\.<minor>`, 511 }, 512 { 513 input: "a.b", 514 output: `major version number a not valid`, 515 }, 516 { 517 input: "4.0", 518 output: `LXD version has to be at least "5.0.0", but current version is only "4.0.0"`, 519 }, 520 { 521 input: "5.0", 522 output: "", 523 }, 524 } { 525 err := lxd.ValidateAPIVersion(t.input) 526 if t.output == "" { 527 c.Check(err, jc.ErrorIsNil) 528 } else { 529 c.Check(err, gc.ErrorMatches, t.output) 530 } 531 } 532 }