github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/lxd/provider_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/http" 8 "net/http/httptest" 9 10 "github.com/golang/mock/gomock" 11 "github.com/juju/errors" 12 gitjujutesting "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/yaml.v2" 17 18 "github.com/juju/juju/cloud" 19 "github.com/juju/juju/environs" 20 "github.com/juju/juju/environs/context" 21 "github.com/juju/juju/environs/testing" 22 "github.com/juju/juju/provider/lxd" 23 "github.com/juju/juju/provider/lxd/lxdnames" 24 jujutesting "github.com/juju/juju/testing" 25 ) 26 27 var ( 28 _ = gc.Suite(&providerSuite{}) 29 _ = gc.Suite(&ProviderFunctionalSuite{}) 30 ) 31 32 type providerSuite struct { 33 lxd.BaseSuite 34 35 provider environs.EnvironProvider 36 } 37 38 func (s *providerSuite) SetUpTest(c *gc.C) { 39 s.BaseSuite.SetUpTest(c) 40 } 41 42 type providerSuiteDeps struct { 43 provider environs.EnvironProvider 44 creds *testing.MockProviderCredentials 45 credsRegister *testing.MockProviderCredentialsRegister 46 factory *lxd.MockServerFactory 47 configReader *lxd.MockLXCConfigReader 48 } 49 50 func (s *providerSuite) createProvider(ctrl *gomock.Controller) providerSuiteDeps { 51 creds := testing.NewMockProviderCredentials(ctrl) 52 credsRegister := testing.NewMockProviderCredentialsRegister(ctrl) 53 factory := lxd.NewMockServerFactory(ctrl) 54 configReader := lxd.NewMockLXCConfigReader(ctrl) 55 56 provider := lxd.NewProviderWithMocks(creds, credsRegister, factory, configReader) 57 return providerSuiteDeps{ 58 provider: provider, 59 creds: creds, 60 credsRegister: credsRegister, 61 factory: factory, 62 configReader: configReader, 63 } 64 } 65 66 func (s *providerSuite) TestDetectClouds(c *gc.C) { 67 ctrl := gomock.NewController(c) 68 defer ctrl.Finish() 69 70 deps := s.createProvider(ctrl) 71 deps.configReader.EXPECT().ReadConfig(".config/lxc/config.yml").Return(lxd.LXCConfig{}, nil) 72 73 cloudDetector := deps.provider.(environs.CloudDetector) 74 75 clouds, err := cloudDetector.DetectClouds() 76 c.Assert(err, jc.ErrorIsNil) 77 c.Assert(clouds, gc.HasLen, 1) 78 s.assertLocalhostCloud(c, clouds[0]) 79 } 80 81 func (s *providerSuite) TestRemoteDetectClouds(c *gc.C) { 82 ctrl := gomock.NewController(c) 83 defer ctrl.Finish() 84 85 deps := s.createProvider(ctrl) 86 deps.configReader.EXPECT().ReadConfig(".config/lxc/config.yml").Return(lxd.LXCConfig{ 87 DefaultRemote: "localhost", 88 Remotes: map[string]lxd.LXCRemoteConfig{ 89 "nuc1": { 90 Addr: "https://10.0.0.1:8443", 91 AuthType: "certificate", 92 Protocol: "lxd", 93 Public: false, 94 }, 95 }, 96 }, nil) 97 98 cloudDetector := deps.provider.(environs.CloudDetector) 99 100 clouds, err := cloudDetector.DetectClouds() 101 c.Assert(err, jc.ErrorIsNil) 102 c.Assert(clouds, gc.HasLen, 2) 103 c.Assert(clouds, jc.DeepEquals, []cloud.Cloud{ 104 { 105 Name: "localhost", 106 Type: "lxd", 107 AuthTypes: []cloud.AuthType{ 108 cloud.CertificateAuthType, 109 }, 110 Regions: []cloud.Region{{ 111 Name: "localhost", 112 }}, 113 Description: "LXD Container Hypervisor", 114 }, 115 { 116 Name: "nuc1", 117 Type: "lxd", 118 Endpoint: "https://10.0.0.1:8443", 119 AuthTypes: []cloud.AuthType{ 120 cloud.CertificateAuthType, 121 }, 122 Regions: []cloud.Region{{ 123 Name: "default", 124 Endpoint: "https://10.0.0.1:8443", 125 }}, 126 Description: "LXD Cluster", 127 }, 128 }) 129 } 130 131 func (s *providerSuite) TestRemoteDetectCloudsWithConfigError(c *gc.C) { 132 ctrl := gomock.NewController(c) 133 defer ctrl.Finish() 134 135 deps := s.createProvider(ctrl) 136 deps.configReader.EXPECT().ReadConfig(".config/lxc/config.yml").Return(lxd.LXCConfig{}, errors.New("bad")) 137 138 cloudDetector := deps.provider.(environs.CloudDetector) 139 140 clouds, err := cloudDetector.DetectClouds() 141 c.Assert(err, jc.ErrorIsNil) 142 c.Assert(clouds, gc.HasLen, 1) 143 s.assertLocalhostCloud(c, clouds[0]) 144 } 145 146 func (s *providerSuite) TestDetectCloud(c *gc.C) { 147 ctrl := gomock.NewController(c) 148 defer ctrl.Finish() 149 150 deps := s.createProvider(ctrl) 151 cloudDetector := deps.provider.(environs.CloudDetector) 152 153 cloud, err := cloudDetector.DetectCloud("localhost") 154 c.Assert(err, jc.ErrorIsNil) 155 s.assertLocalhostCloud(c, cloud) 156 cloud, err = cloudDetector.DetectCloud("lxd") 157 c.Assert(err, jc.ErrorIsNil) 158 s.assertLocalhostCloud(c, cloud) 159 } 160 161 func (s *providerSuite) TestRemoteDetectCloud(c *gc.C) { 162 ctrl := gomock.NewController(c) 163 defer ctrl.Finish() 164 165 deps := s.createProvider(ctrl) 166 cloudDetector := deps.provider.(environs.CloudDetector) 167 168 deps.configReader.EXPECT().ReadConfig(".config/lxc/config.yml").Return(lxd.LXCConfig{ 169 DefaultRemote: "localhost", 170 Remotes: map[string]lxd.LXCRemoteConfig{ 171 "nuc1": { 172 Addr: "https://10.0.0.1:8443", 173 AuthType: "certificate", 174 Protocol: "lxd", 175 Public: false, 176 }, 177 }, 178 }, nil) 179 180 got, err := cloudDetector.DetectCloud("nuc1") 181 c.Assert(err, jc.ErrorIsNil) 182 c.Assert(got, jc.DeepEquals, cloud.Cloud{ 183 Name: "nuc1", 184 Type: "lxd", 185 Endpoint: "https://10.0.0.1:8443", 186 AuthTypes: []cloud.AuthType{ 187 cloud.CertificateAuthType, 188 }, 189 Regions: []cloud.Region{{ 190 Name: "default", 191 Endpoint: "https://10.0.0.1:8443", 192 }}, 193 Description: "LXD Cluster", 194 }) 195 } 196 197 func (s *providerSuite) TestRemoteDetectCloudWithConfigError(c *gc.C) { 198 ctrl := gomock.NewController(c) 199 defer ctrl.Finish() 200 201 deps := s.createProvider(ctrl) 202 cloudDetector := deps.provider.(environs.CloudDetector) 203 204 deps.configReader.EXPECT().ReadConfig(".config/lxc/config.yml").Return(lxd.LXCConfig{}, errors.New("bad")) 205 206 _, err := cloudDetector.DetectCloud("nuc1") 207 c.Assert(err, gc.ErrorMatches, `cloud nuc1 not found`) 208 } 209 210 func (s *providerSuite) TestDetectCloudError(c *gc.C) { 211 ctrl := gomock.NewController(c) 212 defer ctrl.Finish() 213 214 deps := s.createProvider(ctrl) 215 deps.configReader.EXPECT().ReadConfig(".config/lxc/config.yml").Return(lxd.LXCConfig{}, errors.New("bad")) 216 217 cloudDetector := deps.provider.(environs.CloudDetector) 218 219 _, err := cloudDetector.DetectCloud("foo") 220 c.Assert(err, gc.ErrorMatches, `cloud foo not found`) 221 } 222 223 func (s *providerSuite) assertLocalhostCloud(c *gc.C, found cloud.Cloud) { 224 c.Assert(found, jc.DeepEquals, cloud.Cloud{ 225 Name: "localhost", 226 Type: "lxd", 227 AuthTypes: []cloud.AuthType{ 228 cloud.CertificateAuthType, 229 }, 230 Regions: []cloud.Region{{ 231 Name: "localhost", 232 }}, 233 Description: "LXD Container Hypervisor", 234 }) 235 } 236 237 func (s *providerSuite) TestFinalizeCloud(c *gc.C) { 238 ctrl := gomock.NewController(c) 239 defer ctrl.Finish() 240 241 deps := s.createProvider(ctrl) 242 server := lxd.NewMockServer(ctrl) 243 finalizer := deps.provider.(environs.CloudFinalizer) 244 245 deps.factory.EXPECT().LocalServer().Return(server, nil) 246 server.EXPECT().LocalBridgeName().Return("lxdbr0") 247 deps.factory.EXPECT().LocalServerAddress().Return("1.2.3.4:1234", nil) 248 249 var ctx mockContext 250 out, err := finalizer.FinalizeCloud(&ctx, cloud.Cloud{ 251 Name: "localhost", 252 Type: "lxd", 253 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 254 Regions: []cloud.Region{{ 255 Name: "localhost", 256 }}, 257 }) 258 c.Assert(err, jc.ErrorIsNil) 259 c.Assert(out, jc.DeepEquals, cloud.Cloud{ 260 Name: "localhost", 261 Type: "lxd", 262 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 263 Endpoint: "1.2.3.4:1234", 264 Regions: []cloud.Region{{ 265 Name: "localhost", 266 Endpoint: "1.2.3.4:1234", 267 }}, 268 }) 269 ctx.CheckCallNames(c, "Verbosef") 270 ctx.CheckCall( 271 c, 0, "Verbosef", "Resolved LXD host address on bridge %s: %s", 272 []interface{}{"lxdbr0", "1.2.3.4:1234"}, 273 ) 274 } 275 276 func (s *providerSuite) TestFinalizeCloudWithRemoteProvider(c *gc.C) { 277 ctrl := gomock.NewController(c) 278 defer ctrl.Finish() 279 280 deps := s.createProvider(ctrl) 281 finalizer := deps.provider.(environs.CloudFinalizer) 282 283 var ctx mockContext 284 out, err := finalizer.FinalizeCloud(&ctx, cloud.Cloud{ 285 Name: "nuc8", 286 Type: "lxd", 287 Endpoint: "http://10.0.0.1:8443", 288 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 289 Regions: []cloud.Region{}, 290 }) 291 c.Assert(err, jc.ErrorIsNil) 292 c.Assert(out, jc.DeepEquals, cloud.Cloud{ 293 Name: "nuc8", 294 Type: "lxd", 295 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 296 Endpoint: "http://10.0.0.1:8443", 297 Regions: []cloud.Region{{ 298 Name: "default", 299 Endpoint: "http://10.0.0.1:8443", 300 }}, 301 }) 302 } 303 304 func (s *providerSuite) TestFinalizeCloudWithRemoteProviderWithOnlyRegionEndpoint(c *gc.C) { 305 ctrl := gomock.NewController(c) 306 defer ctrl.Finish() 307 308 deps := s.createProvider(ctrl) 309 cloudFinalizer := deps.provider.(environs.CloudFinalizer) 310 311 cloudSpec := cloud.Cloud{ 312 Name: "foo", 313 Type: "lxd", 314 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 315 Regions: []cloud.Region{{ 316 Name: "bar", 317 Endpoint: "https://321.321.12.12", 318 }}, 319 } 320 321 ctx := testing.NewMockFinalizeCloudContext(ctrl) 322 got, err := cloudFinalizer.FinalizeCloud(ctx, cloudSpec) 323 c.Assert(err, jc.ErrorIsNil) 324 c.Assert(got, gc.DeepEquals, cloudSpec) 325 } 326 327 func (s *providerSuite) TestFinalizeCloudWithRemoteProviderWithMixedRegions(c *gc.C) { 328 ctrl := gomock.NewController(c) 329 defer ctrl.Finish() 330 331 deps := s.createProvider(ctrl) 332 cloudFinalizer := deps.provider.(environs.CloudFinalizer) 333 334 server := lxd.NewMockServer(ctrl) 335 336 deps.factory.EXPECT().LocalServer().Return(server, nil) 337 server.EXPECT().LocalBridgeName().Return("lxdbr0") 338 deps.factory.EXPECT().LocalServerAddress().Return("https://192.0.0.1:8443", nil) 339 340 cloudSpec := cloud.Cloud{ 341 Name: "localhost", 342 Type: "lxd", 343 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 344 Regions: []cloud.Region{{ 345 Name: "bar", 346 Endpoint: "https://321.321.12.12", 347 }}, 348 } 349 350 ctx := testing.NewMockFinalizeCloudContext(ctrl) 351 ctx.EXPECT().Verbosef("Resolved LXD host address on bridge %s: %s", "lxdbr0", "https://192.0.0.1:8443") 352 353 got, err := cloudFinalizer.FinalizeCloud(ctx, cloudSpec) 354 c.Assert(err, jc.ErrorIsNil) 355 c.Assert(got, gc.DeepEquals, cloud.Cloud{ 356 Name: "localhost", 357 Type: "lxd", 358 Endpoint: "https://192.0.0.1:8443", 359 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 360 Regions: []cloud.Region{{ 361 Name: "bar", 362 Endpoint: "https://321.321.12.12", 363 }}, 364 }) 365 } 366 367 func (s *providerSuite) TestFinalizeCloudWithRemoteProviderWithNoRegion(c *gc.C) { 368 ctrl := gomock.NewController(c) 369 defer ctrl.Finish() 370 371 deps := s.createProvider(ctrl) 372 cloudFinalizer := deps.provider.(environs.CloudFinalizer) 373 374 cloudSpec := cloud.Cloud{ 375 Name: "test", 376 Type: "lxd", 377 Endpoint: "https://192.0.0.1:8443", 378 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 379 Regions: []cloud.Region{}, 380 } 381 382 ctx := testing.NewMockFinalizeCloudContext(ctrl) 383 384 got, err := cloudFinalizer.FinalizeCloud(ctx, cloudSpec) 385 c.Assert(err, jc.ErrorIsNil) 386 c.Assert(got, gc.DeepEquals, cloud.Cloud{ 387 Name: "test", 388 Type: "lxd", 389 Endpoint: "https://192.0.0.1:8443", 390 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 391 Regions: []cloud.Region{{ 392 Name: "default", 393 Endpoint: "https://192.0.0.1:8443", 394 }}, 395 }) 396 } 397 398 func (s *providerSuite) TestFinalizeCloudNotListening(c *gc.C) { 399 ctrl := gomock.NewController(c) 400 defer ctrl.Finish() 401 402 deps := s.createProvider(ctrl) 403 cloudFinalizer := deps.provider.(environs.CloudFinalizer) 404 405 deps.factory.EXPECT().LocalServer().Return(nil, errors.New("bad")) 406 407 ctx := testing.NewMockFinalizeCloudContext(ctrl) 408 _, err := cloudFinalizer.FinalizeCloud(ctx, cloud.Cloud{ 409 Name: "localhost", 410 Type: "lxd", 411 AuthTypes: []cloud.AuthType{cloud.CertificateAuthType}, 412 Regions: []cloud.Region{{ 413 Name: "bar", 414 }}, 415 }) 416 c.Assert(err, gc.NotNil) 417 c.Assert(err, gc.ErrorMatches, "bad") 418 } 419 420 func (s *providerSuite) TestDetectRegions(c *gc.C) { 421 ctrl := gomock.NewController(c) 422 defer ctrl.Finish() 423 424 deps := s.createProvider(ctrl) 425 cloudDetector := deps.provider.(environs.CloudRegionDetector) 426 427 regions, err := cloudDetector.DetectRegions() 428 c.Assert(err, jc.ErrorIsNil) 429 c.Assert(regions, jc.DeepEquals, []cloud.Region{{Name: lxdnames.DefaultLocalRegion}}) 430 } 431 432 func (s *providerSuite) TestValidate(c *gc.C) { 433 ctrl := gomock.NewController(c) 434 defer ctrl.Finish() 435 436 deps := s.createProvider(ctrl) 437 438 validCfg, err := deps.provider.Validate(s.Config, nil) 439 c.Assert(err, jc.ErrorIsNil) 440 validAttrs := validCfg.AllAttrs() 441 442 c.Check(s.Config.AllAttrs(), gc.DeepEquals, validAttrs) 443 } 444 445 func (s *providerSuite) TestValidateWithInvalidConfig(c *gc.C) { 446 ctrl := gomock.NewController(c) 447 defer ctrl.Finish() 448 449 deps := s.createProvider(ctrl) 450 451 config, err := jujutesting.ModelConfig(c).Apply(map[string]interface{}{ 452 "value": int64(1), 453 }) 454 c.Assert(err, gc.IsNil) 455 456 _, err = deps.provider.Validate(config, nil) 457 c.Assert(err, gc.NotNil) 458 } 459 460 func (s *providerSuite) TestCloudSchema(c *gc.C) { 461 ctrl := gomock.NewController(c) 462 defer ctrl.Finish() 463 464 deps := s.createProvider(ctrl) 465 466 config := ` 467 auth-types: [certificate] 468 endpoint: http://foo.com/lxd 469 `[1:] 470 var v interface{} 471 err := yaml.Unmarshal([]byte(config), &v) 472 c.Assert(err, jc.ErrorIsNil) 473 v, err = utils.ConformYAML(v) 474 c.Assert(err, jc.ErrorIsNil) 475 476 err = deps.provider.CloudSchema().Validate(v) 477 c.Assert(err, jc.ErrorIsNil) 478 } 479 480 func (s *providerSuite) TestPingFailWithNoEndpoint(c *gc.C) { 481 server := httptest.NewTLSServer(http.HandlerFunc(http.NotFound)) 482 defer server.Close() 483 484 p, err := environs.Provider("lxd") 485 c.Assert(err, jc.ErrorIsNil) 486 err = p.Ping(context.NewCloudCallContext(), server.URL) 487 c.Assert(err, gc.ErrorMatches, "no lxd server running at "+server.URL) 488 } 489 490 func (s *providerSuite) TestPingFailWithHTTP(c *gc.C) { 491 server := httptest.NewServer(http.HandlerFunc(http.NotFound)) 492 defer server.Close() 493 494 p, err := environs.Provider("lxd") 495 c.Assert(err, jc.ErrorIsNil) 496 err = p.Ping(context.NewCloudCallContext(), server.URL) 497 c.Assert(err, gc.ErrorMatches, "invalid URL \""+server.URL+"\": only HTTPS is supported") 498 } 499 500 type ProviderFunctionalSuite struct { 501 lxd.BaseSuite 502 503 provider environs.EnvironProvider 504 } 505 506 func (s *ProviderFunctionalSuite) SetUpTest(c *gc.C) { 507 if !s.IsRunningLocally(c) { 508 c.Skip("LXD not running locally") 509 } 510 511 s.BaseSuite.SetUpTest(c) 512 513 provider, err := environs.Provider("lxd") 514 c.Assert(err, jc.ErrorIsNil) 515 516 s.provider = provider 517 } 518 519 func (s *ProviderFunctionalSuite) TestOpen(c *gc.C) { 520 env, err := environs.Open(s.provider, environs.OpenParams{ 521 Cloud: lxdCloudSpec(), 522 Config: s.Config, 523 }) 524 c.Assert(err, jc.ErrorIsNil) 525 envConfig := env.Config() 526 527 c.Check(envConfig.Name(), gc.Equals, "testmodel") 528 } 529 530 func (s *ProviderFunctionalSuite) TestPrepareConfig(c *gc.C) { 531 cfg, err := s.provider.PrepareConfig(environs.PrepareConfigParams{ 532 Cloud: lxdCloudSpec(), 533 Config: s.Config, 534 }) 535 c.Assert(err, jc.ErrorIsNil) 536 c.Check(cfg, gc.NotNil) 537 } 538 539 func (s *ProviderFunctionalSuite) TestPrepareConfigUnsupportedEndpointScheme(c *gc.C) { 540 cloudSpec := lxdCloudSpec() 541 cloudSpec.Endpoint = "unix://foo" 542 _, err := s.provider.PrepareConfig(environs.PrepareConfigParams{ 543 Cloud: cloudSpec, 544 Config: s.Config, 545 }) 546 c.Assert(err, gc.ErrorMatches, `validating cloud spec: invalid URL "unix://foo": only HTTPS is supported`) 547 } 548 549 func (s *ProviderFunctionalSuite) TestPrepareConfigUnsupportedAuthType(c *gc.C) { 550 cred := cloud.NewCredential("foo", nil) 551 _, err := s.provider.PrepareConfig(environs.PrepareConfigParams{ 552 Cloud: environs.CloudSpec{ 553 Type: "lxd", 554 Name: "remotehost", 555 Credential: &cred, 556 }, 557 }) 558 c.Assert(err, gc.ErrorMatches, `validating cloud spec: "foo" auth-type not supported`) 559 } 560 561 func (s *ProviderFunctionalSuite) TestPrepareConfigInvalidCertificateAttrs(c *gc.C) { 562 cred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{}) 563 _, err := s.provider.PrepareConfig(environs.PrepareConfigParams{ 564 Cloud: environs.CloudSpec{ 565 Type: "lxd", 566 Name: "remotehost", 567 Credential: &cred, 568 }, 569 }) 570 c.Assert(err, gc.ErrorMatches, `validating cloud spec: certificate credentials not valid`) 571 } 572 573 func (s *ProviderFunctionalSuite) TestPrepareConfigEmptyAuthNonLocal(c *gc.C) { 574 cred := cloud.NewEmptyCredential() 575 _, err := s.provider.PrepareConfig(environs.PrepareConfigParams{ 576 Cloud: environs.CloudSpec{ 577 Type: "lxd", 578 Name: "remotehost", 579 Endpoint: "8.8.8.8", 580 Credential: &cred, 581 }, 582 }) 583 c.Assert(err, gc.ErrorMatches, `validating cloud spec: "empty" auth-type not supported`) 584 } 585 586 type mockContext struct { 587 gitjujutesting.Stub 588 } 589 590 func (c *mockContext) Verbosef(f string, args ...interface{}) { 591 c.MethodCall(c, "Verbosef", f, args) 592 }