github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/bundle/bundle_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package bundle_test 5 6 import ( 7 "fmt" 8 9 "github.com/juju/description" 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/juju/names.v2" 13 14 "github.com/juju/juju/apiserver/facades/client/bundle" 15 "github.com/juju/juju/apiserver/params" 16 apiservertesting "github.com/juju/juju/apiserver/testing" 17 coretesting "github.com/juju/juju/testing" 18 ) 19 20 type bundleSuite struct { 21 coretesting.BaseSuite 22 auth *apiservertesting.FakeAuthorizer 23 facade *bundle.APIv2 24 apiv1 *bundle.APIv1 25 st *mockState 26 modelTag names.ModelTag 27 } 28 29 var _ = gc.Suite(&bundleSuite{}) 30 31 func (s *bundleSuite) SetUpTest(c *gc.C) { 32 s.BaseSuite.SetUpTest(c) 33 s.auth = &apiservertesting.FakeAuthorizer{ 34 Tag: names.NewUserTag("read"), 35 } 36 37 s.st = newMockState() 38 s.modelTag = names.NewModelTag("some-uuid") 39 40 s.apiv1 = s.makeAPIv1(c) 41 s.facade = s.makeAPI(c) 42 } 43 44 func (s *bundleSuite) makeAPI(c *gc.C) *bundle.APIv2 { 45 api, err := bundle.NewBundleAPI( 46 s.st, 47 s.auth, 48 s.modelTag, 49 ) 50 c.Assert(err, jc.ErrorIsNil) 51 return &bundle.APIv2{api} 52 } 53 54 func (s *bundleSuite) makeAPIv1(c *gc.C) *bundle.APIv1 { 55 api := s.makeAPI(c) 56 return &bundle.APIv1{api} 57 } 58 59 func (s *bundleSuite) TestGetChangesBundleContentError(c *gc.C) { 60 args := params.BundleChangesParams{ 61 BundleDataYAML: ":", 62 } 63 r, err := s.facade.GetChanges(args) 64 c.Assert(err, gc.ErrorMatches, `cannot read bundle YAML: cannot unmarshal bundle data: yaml: did not find expected key`) 65 c.Assert(r, gc.DeepEquals, params.BundleChangesResults{}) 66 } 67 68 func (s *bundleSuite) TestGetChangesBundleVerificationErrors(c *gc.C) { 69 args := params.BundleChangesParams{ 70 BundleDataYAML: ` 71 applications: 72 django: 73 charm: django 74 to: [1] 75 haproxy: 76 charm: 42 77 num_units: -1 78 `, 79 } 80 r, err := s.facade.GetChanges(args) 81 c.Assert(err, jc.ErrorIsNil) 82 c.Assert(r.Changes, gc.IsNil) 83 c.Assert(r.Errors, jc.SameContents, []string{ 84 `placement "1" refers to a machine not defined in this bundle`, 85 `too many units specified in unit placement for application "django"`, 86 `invalid charm URL in application "haproxy": cannot parse URL "42": name "42" not valid`, 87 `negative number of units specified on application "haproxy"`, 88 }) 89 } 90 91 func (s *bundleSuite) TestGetChangesBundleConstraintsError(c *gc.C) { 92 args := params.BundleChangesParams{ 93 BundleDataYAML: ` 94 applications: 95 django: 96 charm: django 97 num_units: 1 98 constraints: bad=wolf 99 `, 100 } 101 r, err := s.facade.GetChanges(args) 102 c.Assert(err, jc.ErrorIsNil) 103 c.Assert(r.Changes, gc.IsNil) 104 c.Assert(r.Errors, jc.SameContents, []string{ 105 `invalid constraints "bad=wolf" in application "django": unknown constraint "bad"`, 106 }) 107 } 108 109 func (s *bundleSuite) TestGetChangesBundleStorageError(c *gc.C) { 110 args := params.BundleChangesParams{ 111 BundleDataYAML: ` 112 applications: 113 django: 114 charm: django 115 num_units: 1 116 storage: 117 bad: 0,100M 118 `, 119 } 120 r, err := s.facade.GetChanges(args) 121 c.Assert(err, jc.ErrorIsNil) 122 c.Assert(r.Changes, gc.IsNil) 123 c.Assert(r.Errors, jc.SameContents, []string{ 124 `invalid storage "bad" in application "django": cannot parse count: count must be greater than zero, got "0"`, 125 }) 126 } 127 128 func (s *bundleSuite) TestGetChangesBundleDevicesError(c *gc.C) { 129 args := params.BundleChangesParams{ 130 BundleDataYAML: ` 131 applications: 132 django: 133 charm: django 134 num_units: 1 135 devices: 136 bad-gpu: -1,nvidia.com/gpu 137 `, 138 } 139 r, err := s.facade.GetChanges(args) 140 c.Assert(err, jc.ErrorIsNil) 141 c.Assert(r.Changes, gc.IsNil) 142 c.Assert(r.Errors, jc.SameContents, []string{ 143 `invalid device "bad-gpu" in application "django": count must be greater than zero, got "-1"`, 144 }) 145 } 146 147 func (s *bundleSuite) TestGetChangesSuccessV2(c *gc.C) { 148 args := params.BundleChangesParams{ 149 BundleDataYAML: ` 150 applications: 151 django: 152 charm: django 153 options: 154 debug: true 155 storage: 156 tmpfs: tmpfs,1G 157 devices: 158 bitcoinminer: 2,nvidia.com/gpu 159 haproxy: 160 charm: cs:trusty/haproxy-42 161 relations: 162 - - django:web 163 - haproxy:web 164 `, 165 } 166 r, err := s.facade.GetChanges(args) 167 c.Assert(err, jc.ErrorIsNil) 168 c.Assert(r.Changes, jc.DeepEquals, []*params.BundleChange{{ 169 Id: "addCharm-0", 170 Method: "addCharm", 171 Args: []interface{}{"django", ""}, 172 }, { 173 Id: "deploy-1", 174 Method: "deploy", 175 Args: []interface{}{ 176 "$addCharm-0", 177 "", 178 "django", 179 map[string]interface{}{"debug": true}, 180 "", 181 map[string]string{"tmpfs": "tmpfs,1G"}, 182 map[string]string{"bitcoinminer": "2,nvidia.com/gpu"}, 183 map[string]string{}, 184 map[string]int{}, 185 0, 186 "", 187 }, 188 Requires: []string{"addCharm-0"}, 189 }, { 190 Id: "addCharm-2", 191 Method: "addCharm", 192 Args: []interface{}{"cs:trusty/haproxy-42", "trusty"}, 193 }, { 194 Id: "deploy-3", 195 Method: "deploy", 196 Args: []interface{}{ 197 "$addCharm-2", 198 "trusty", 199 "haproxy", 200 map[string]interface{}{}, 201 "", 202 map[string]string{}, 203 map[string]string{}, 204 map[string]string{}, 205 map[string]int{}, 206 0, 207 "", 208 }, 209 Requires: []string{"addCharm-2"}, 210 }, { 211 Id: "addRelation-4", 212 Method: "addRelation", 213 Args: []interface{}{"$deploy-1:web", "$deploy-3:web"}, 214 Requires: []string{"deploy-1", "deploy-3"}, 215 }}) 216 c.Assert(r.Errors, gc.IsNil) 217 } 218 219 func (s *bundleSuite) TestGetChangesKubernetes(c *gc.C) { 220 args := params.BundleChangesParams{ 221 BundleDataYAML: ` 222 bundle: kubernetes 223 applications: 224 django: 225 charm: django 226 scale: 1 227 placement: foo=bar 228 options: 229 debug: true 230 storage: 231 tmpfs: tmpfs,1G 232 devices: 233 bitcoinminer: 2,nvidia.com/gpu 234 haproxy: 235 charm: cs:haproxy-42 236 relations: 237 - - django:web 238 - haproxy:web 239 `, 240 } 241 r, err := s.facade.GetChanges(args) 242 c.Assert(err, jc.ErrorIsNil) 243 c.Assert(r.Changes, jc.DeepEquals, []*params.BundleChange{{ 244 Id: "addCharm-0", 245 Method: "addCharm", 246 Args: []interface{}{"django", "kubernetes"}, 247 }, { 248 Id: "deploy-1", 249 Method: "deploy", 250 Args: []interface{}{ 251 "$addCharm-0", 252 "kubernetes", 253 "django", 254 map[string]interface{}{"debug": true}, 255 "", 256 map[string]string{"tmpfs": "tmpfs,1G"}, 257 map[string]string{"bitcoinminer": "2,nvidia.com/gpu"}, 258 map[string]string{}, 259 map[string]int{}, 260 1, 261 "foo=bar", 262 }, 263 Requires: []string{"addCharm-0"}, 264 }, { 265 Id: "addCharm-2", 266 Method: "addCharm", 267 Args: []interface{}{"cs:haproxy-42", "kubernetes"}, 268 }, { 269 Id: "deploy-3", 270 Method: "deploy", 271 Args: []interface{}{ 272 "$addCharm-2", 273 "kubernetes", 274 "haproxy", 275 map[string]interface{}{}, 276 "", 277 map[string]string{}, 278 map[string]string{}, 279 map[string]string{}, 280 map[string]int{}, 281 0, 282 "", 283 }, 284 Requires: []string{"addCharm-2"}, 285 }, { 286 Id: "addRelation-4", 287 Method: "addRelation", 288 Args: []interface{}{"$deploy-1:web", "$deploy-3:web"}, 289 Requires: []string{"deploy-1", "deploy-3"}, 290 }}) 291 c.Assert(r.Errors, gc.IsNil) 292 } 293 294 func (s *bundleSuite) TestGetChangesSuccessV1(c *gc.C) { 295 args := params.BundleChangesParams{ 296 BundleDataYAML: ` 297 applications: 298 django: 299 charm: django 300 options: 301 debug: true 302 storage: 303 tmpfs: tmpfs,1G 304 devices: 305 bitcoinminer: 2,nvidia.com/gpu 306 haproxy: 307 charm: cs:trusty/haproxy-42 308 relations: 309 - - django:web 310 - haproxy:web 311 `, 312 } 313 r, err := s.apiv1.GetChanges(args) 314 c.Assert(err, jc.ErrorIsNil) 315 c.Assert(r.Changes, jc.DeepEquals, []*params.BundleChange{{ 316 Id: "addCharm-0", 317 Method: "addCharm", 318 Args: []interface{}{"django", ""}, 319 }, { 320 Id: "deploy-1", 321 Method: "deploy", 322 Args: []interface{}{ 323 "$addCharm-0", 324 "", 325 "django", 326 map[string]interface{}{"debug": true}, 327 "", 328 map[string]string{"tmpfs": "tmpfs,1G"}, 329 map[string]string{}, 330 map[string]int{}, 331 0, 332 "", 333 }, 334 Requires: []string{"addCharm-0"}, 335 }, { 336 Id: "addCharm-2", 337 Method: "addCharm", 338 Args: []interface{}{"cs:trusty/haproxy-42", "trusty"}, 339 }, { 340 Id: "deploy-3", 341 Method: "deploy", 342 Args: []interface{}{ 343 "$addCharm-2", 344 "trusty", 345 "haproxy", 346 map[string]interface{}{}, 347 "", 348 map[string]string{}, 349 map[string]string{}, 350 map[string]int{}, 351 0, 352 "", 353 }, 354 Requires: []string{"addCharm-2"}, 355 }, { 356 Id: "addRelation-4", 357 Method: "addRelation", 358 Args: []interface{}{"$deploy-1:web", "$deploy-3:web"}, 359 Requires: []string{"deploy-1", "deploy-3"}, 360 }}) 361 c.Assert(r.Errors, gc.IsNil) 362 } 363 364 func (s *bundleSuite) TestGetChangesBundleEndpointBindingsSuccess(c *gc.C) { 365 args := params.BundleChangesParams{ 366 BundleDataYAML: ` 367 applications: 368 django: 369 charm: django 370 num_units: 1 371 bindings: 372 url: public 373 `, 374 } 375 r, err := s.facade.GetChanges(args) 376 c.Assert(err, jc.ErrorIsNil) 377 378 for _, change := range r.Changes { 379 if change.Method == "deploy" { 380 c.Assert(change, jc.DeepEquals, ¶ms.BundleChange{ 381 Id: "deploy-1", 382 Method: "deploy", 383 Args: []interface{}{ 384 "$addCharm-0", 385 "", 386 "django", 387 map[string]interface{}{}, 388 "", 389 map[string]string{}, 390 map[string]string{}, 391 map[string]string{"url": "public"}, 392 map[string]int{}, 393 0, 394 "", 395 }, 396 Requires: []string{"addCharm-0"}, 397 }) 398 } 399 } 400 } 401 402 func (s *bundleSuite) TestExportBundleFailNoApplication(c *gc.C) { 403 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 404 Config: map[string]interface{}{ 405 "name": "awesome", 406 "uuid": "some-uuid", 407 }, 408 CloudRegion: "some-region"}) 409 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 410 411 result, err := s.facade.ExportBundle() 412 c.Assert(err, gc.NotNil) 413 c.Assert(result, gc.Equals, params.StringResult{}) 414 c.Check(err, gc.ErrorMatches, "nothing to export as there are no applications") 415 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 416 } 417 418 func (s *bundleSuite) minimalApplicationArgs(modelType string) description.ApplicationArgs { 419 result := description.ApplicationArgs{ 420 Tag: names.NewApplicationTag("ubuntu"), 421 Series: "trusty", 422 Type: modelType, 423 CharmURL: "cs:trusty/ubuntu", 424 Channel: "stable", 425 CharmModifiedVersion: 1, 426 CharmConfig: map[string]interface{}{ 427 "key": "value", 428 }, 429 Leader: "ubuntu/0", 430 LeadershipSettings: map[string]interface{}{ 431 "leader": true, 432 }, 433 MetricsCredentials: []byte("sekrit"), 434 EndpointBindings: map[string]string{"juju-info": "vlan2", "another": ""}, 435 } 436 if modelType == description.CAAS { 437 result.PasswordHash = "some-hash" 438 result.PodSpec = "some-spec" 439 result.CloudService = &description.CloudServiceArgs{ 440 ProviderId: "some-provider", 441 Addresses: []description.AddressArgs{ 442 {Value: "10.0.0.1", Type: "special"}, 443 {Value: "10.0.0.2", Type: "other"}, 444 }, 445 } 446 } 447 return result 448 } 449 450 func minimalUnitArgs(modelType string) description.UnitArgs { 451 result := description.UnitArgs{ 452 Tag: names.NewUnitTag("ubuntu/0"), 453 Type: modelType, 454 Machine: names.NewMachineTag("0"), 455 PasswordHash: "secure-hash", 456 } 457 if modelType == description.CAAS { 458 result.CloudContainer = &description.CloudContainerArgs{ 459 ProviderId: "some-provider", 460 Address: description.AddressArgs{Value: "10.0.0.1", Type: "special"}, 461 Ports: []string{"80", "443"}, 462 } 463 } 464 return result 465 } 466 467 func minimalStatusArgs() description.StatusArgs { 468 return description.StatusArgs{ 469 Value: "running", 470 } 471 } 472 473 func (s *bundleSuite) TestExportBundleWithApplication(c *gc.C) { 474 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 475 Config: map[string]interface{}{ 476 "name": "awesome", 477 "uuid": "some-uuid", 478 }, 479 CloudRegion: "some-region"}) 480 481 app := s.st.model.AddApplication(s.minimalApplicationArgs(description.IAAS)) 482 app.SetStatus(minimalStatusArgs()) 483 484 u := app.AddUnit(minimalUnitArgs(app.Type())) 485 u.SetAgentStatus(minimalStatusArgs()) 486 487 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 488 489 result, err := s.facade.ExportBundle() 490 c.Assert(err, jc.ErrorIsNil) 491 expectedResult := params.StringResult{nil, ` 492 series: trusty 493 applications: 494 ubuntu: 495 charm: cs:trusty/ubuntu 496 num_units: 1 497 to: 498 - "0" 499 options: 500 key: value 501 bindings: 502 juju-info: vlan2 503 `[1:]} 504 505 c.Assert(result, gc.Equals, expectedResult) 506 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 507 } 508 509 func (s *bundleSuite) addApplicationToModel(model description.Model, name string, numUnits int) description.Application { 510 series := "xenial" 511 placement := "" 512 if model.Type() == "caas" { 513 series = "kubernetes" 514 placement = "foo=bar" 515 } 516 application := model.AddApplication(description.ApplicationArgs{ 517 Tag: names.NewApplicationTag(name), 518 CharmURL: "cs:" + name, 519 Series: series, 520 Placement: placement, 521 CharmConfig: map[string]interface{}{}, 522 LeadershipSettings: map[string]interface{}{}, 523 }) 524 application.SetStatus(minimalStatusArgs()) 525 for i := 0; i < numUnits; i++ { 526 machine := model.AddMachine(description.MachineArgs{ 527 Id: names.NewMachineTag(fmt.Sprint(i)), 528 Series: series, 529 }) 530 unit := application.AddUnit(description.UnitArgs{ 531 Tag: names.NewUnitTag(fmt.Sprintf("%s/%d", name, i)), 532 Machine: machine.Tag(), 533 }) 534 unit.SetAgentStatus(minimalStatusArgs()) 535 } 536 537 return application 538 } 539 540 func (s *bundleSuite) setEndpointSettings(ep description.Endpoint, units ...string) { 541 for _, unit := range units { 542 ep.SetUnitSettings(unit, map[string]interface{}{ 543 "key": "value", 544 }) 545 } 546 } 547 548 func (s *bundleSuite) newModel(modelType string, app1 string, app2 string) description.Model { 549 s.st.model = description.NewModel(description.ModelArgs{ 550 Type: modelType, 551 Owner: names.NewUserTag("magic"), 552 Config: map[string]interface{}{ 553 "name": "awesome", 554 "uuid": "some-uuid", 555 }, 556 CloudRegion: "some-region"}) 557 558 s.addApplicationToModel(s.st.model, app1, 2) 559 s.addApplicationToModel(s.st.model, app2, 1) 560 561 // Add a relation between wordpress and mysql. 562 rel := s.st.model.AddRelation(description.RelationArgs{ 563 Id: 42, 564 Key: "special key", 565 }) 566 rel.SetStatus(minimalStatusArgs()) 567 568 app1Endpoint := rel.AddEndpoint(description.EndpointArgs{ 569 ApplicationName: app1, 570 Name: "db", 571 // Ignoring other aspects of endpoints. 572 }) 573 s.setEndpointSettings(app1Endpoint, app1+"/0", app1+"/1") 574 575 app2Endpoint := rel.AddEndpoint(description.EndpointArgs{ 576 ApplicationName: "mysql", 577 Name: "mysql", 578 // Ignoring other aspects of endpoints. 579 }) 580 s.setEndpointSettings(app2Endpoint, app2+"/0") 581 582 return s.st.model 583 } 584 585 func (s *bundleSuite) TestExportBundleModelWithSettingsRelations(c *gc.C) { 586 model := s.newModel("iaas", "wordpress", "mysql") 587 model.SetStatus(description.StatusArgs{Value: "available"}) 588 589 result, err := s.facade.ExportBundle() 590 c.Assert(err, jc.ErrorIsNil) 591 592 output := ` 593 series: xenial 594 applications: 595 mysql: 596 charm: cs:mysql 597 num_units: 1 598 to: 599 - "0" 600 wordpress: 601 charm: cs:wordpress 602 num_units: 2 603 to: 604 - "0" 605 - "1" 606 machines: 607 "0": {} 608 "1": {} 609 relations: 610 - - wordpress:db 611 - mysql:mysql 612 `[1:] 613 expectedResult := params.StringResult{nil, output} 614 615 c.Assert(result, gc.Equals, expectedResult) 616 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 617 } 618 619 func (s *bundleSuite) addSubordinateEndpoints( 620 c *gc.C, 621 rel description.Relation, app string, 622 ) (description.Endpoint, description.Endpoint) { 623 appEndpoint := rel.AddEndpoint(description.EndpointArgs{ 624 ApplicationName: app, 625 Name: "logging", 626 Scope: "container", 627 // Ignoring other aspects of endpoints. 628 }) 629 loggingEndpoint := rel.AddEndpoint(description.EndpointArgs{ 630 ApplicationName: "logging", 631 Name: "logging", 632 Scope: "container", 633 // Ignoring other aspects of endpoints. 634 }) 635 return appEndpoint, loggingEndpoint 636 } 637 638 func (s *bundleSuite) TestExportBundleModelRelationsWithSubordinates(c *gc.C) { 639 model := s.newModel("iaas", "wordpress", "mysql") 640 model.SetStatus(description.StatusArgs{Value: "available"}) 641 642 // Add a subordinate relations between logging and both wordpress and mysql. 643 rel := model.AddRelation(description.RelationArgs{ 644 Id: 43, 645 Key: "some key", 646 }) 647 wordpressEndpoint, loggingEndpoint := s.addSubordinateEndpoints(c, rel, "wordpress") 648 s.setEndpointSettings(wordpressEndpoint, "wordpress/0", "wordpress/1") 649 s.setEndpointSettings(loggingEndpoint, "logging/0", "logging/1") 650 651 rel = model.AddRelation(description.RelationArgs{ 652 Id: 44, 653 Key: "other key", 654 }) 655 mysqlEndpoint, loggingEndpoint := s.addSubordinateEndpoints(c, rel, "mysql") 656 s.setEndpointSettings(mysqlEndpoint, "mysql/0") 657 s.setEndpointSettings(loggingEndpoint, "logging/2") 658 659 result, err := s.facade.ExportBundle() 660 c.Assert(err, jc.ErrorIsNil) 661 662 expectedResult := params.StringResult{nil, ` 663 series: xenial 664 applications: 665 mysql: 666 charm: cs:mysql 667 num_units: 1 668 to: 669 - "0" 670 wordpress: 671 charm: cs:wordpress 672 num_units: 2 673 to: 674 - "0" 675 - "1" 676 machines: 677 "0": {} 678 "1": {} 679 relations: 680 - - wordpress:db 681 - mysql:mysql 682 - - wordpress:logging 683 - logging:logging 684 - - mysql:logging 685 - logging:logging 686 `[1:]} 687 688 c.Assert(result, gc.Equals, expectedResult) 689 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 690 } 691 692 func (s *bundleSuite) TestExportBundleSubordinateApplication(c *gc.C) { 693 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 694 Config: map[string]interface{}{ 695 "name": "awesome", 696 "uuid": "some-uuid", 697 }, 698 CloudRegion: "some-region"}) 699 700 application := s.st.model.AddApplication(description.ApplicationArgs{ 701 Tag: names.NewApplicationTag("magic"), 702 Series: "zesty", 703 Subordinate: true, 704 CharmURL: "cs:zesty/magic", 705 Channel: "stable", 706 CharmModifiedVersion: 1, 707 ForceCharm: true, 708 Exposed: true, 709 EndpointBindings: map[string]string{ 710 "rel-name": "some-space", 711 }, 712 ApplicationConfig: map[string]interface{}{ 713 "config key": "config value", 714 }, 715 CharmConfig: map[string]interface{}{ 716 "key": "value", 717 }, 718 Leader: "magic/1", 719 LeadershipSettings: map[string]interface{}{ 720 "leader": true, 721 }, 722 MetricsCredentials: []byte("sekrit"), 723 PasswordHash: "passwordhash", 724 PodSpec: "podspec", 725 }) 726 application.SetStatus(minimalStatusArgs()) 727 728 result, err := s.facade.ExportBundle() 729 c.Assert(err, jc.ErrorIsNil) 730 731 expectedResult := params.StringResult{nil, ` 732 series: zesty 733 applications: 734 magic: 735 charm: cs:zesty/magic 736 expose: true 737 options: 738 key: value 739 bindings: 740 rel-name: some-space 741 `[1:]} 742 743 c.Assert(result, gc.Equals, expectedResult) 744 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 745 } 746 747 func (s *bundleSuite) TestExportBundleSubordinateApplicationAndMachine(c *gc.C) { 748 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 749 Config: map[string]interface{}{ 750 "name": "awesome", 751 "uuid": "some-uuid", 752 }, 753 CloudRegion: "some-region"}) 754 755 application := s.st.model.AddApplication(description.ApplicationArgs{ 756 Tag: names.NewApplicationTag("magic"), 757 Series: "zesty", 758 Subordinate: true, 759 CharmURL: "cs:zesty/magic", 760 Channel: "stable", 761 Exposed: true, 762 CharmConfig: map[string]interface{}{ 763 "key": "value", 764 }, 765 }) 766 application.SetStatus(minimalStatusArgs()) 767 768 s.addMinimalMachineWithConstraints(s.st.model, "0") 769 770 result, err := s.facade.ExportBundle() 771 c.Assert(err, jc.ErrorIsNil) 772 773 expectedResult := params.StringResult{nil, ` 774 series: zesty 775 applications: 776 magic: 777 charm: cs:zesty/magic 778 expose: true 779 options: 780 key: value 781 `[1:]} 782 783 c.Assert(result, gc.Equals, expectedResult) 784 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 785 } 786 787 func (s *bundleSuite) addMinimalMachineWithConstraints(model description.Model, id string) { 788 m := model.AddMachine(description.MachineArgs{ 789 Id: names.NewMachineTag(id), 790 Nonce: "a-nonce", 791 PasswordHash: "some-hash", 792 Series: "xenial", 793 Jobs: []string{"host-units"}, 794 }) 795 args := description.ConstraintsArgs{ 796 Architecture: "amd64", 797 Memory: 8 * 1024, 798 RootDisk: 40 * 1024, 799 CpuCores: 2, 800 CpuPower: 100, 801 InstanceType: "big-inst", 802 Tags: []string{"foo", "bar"}, 803 VirtType: "pv", 804 Container: "kvm", 805 Spaces: []string{"internal"}, 806 } 807 m.SetConstraints(args) 808 m.SetStatus(minimalStatusArgs()) 809 } 810 811 func (s *bundleSuite) TestExportBundleModelWithConstraints(c *gc.C) { 812 model := s.newModel("iaas", "mediawiki", "mysql") 813 814 s.addMinimalMachineWithConstraints(model, "0") 815 s.addMinimalMachineWithConstraints(model, "1") 816 817 model.SetStatus(description.StatusArgs{Value: "available"}) 818 819 result, err := s.facade.ExportBundle() 820 c.Assert(err, jc.ErrorIsNil) 821 expectedResult := params.StringResult{nil, ` 822 series: xenial 823 applications: 824 mediawiki: 825 charm: cs:mediawiki 826 num_units: 2 827 to: 828 - "0" 829 - "1" 830 mysql: 831 charm: cs:mysql 832 num_units: 1 833 to: 834 - "0" 835 machines: 836 "0": 837 constraints: arch=amd64 cpu-cores=2 cpu-power=100 mem=8192 root-disk=40960 instance-type=big-inst 838 container=kvm virt-type=pv tags=foo,bar spaces=internal 839 "1": 840 constraints: arch=amd64 cpu-cores=2 cpu-power=100 mem=8192 root-disk=40960 instance-type=big-inst 841 container=kvm virt-type=pv tags=foo,bar spaces=internal 842 relations: 843 - - mediawiki:db 844 - mysql:mysql 845 `[1:]} 846 847 c.Assert(result, gc.Equals, expectedResult) 848 849 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 850 } 851 852 func (s *bundleSuite) addMinimalMachineWithAnnotations(model description.Model, id string) { 853 m := model.AddMachine(description.MachineArgs{ 854 Id: names.NewMachineTag(id), 855 Nonce: "a-nonce", 856 PasswordHash: "some-hash", 857 Series: "xenial", 858 Jobs: []string{"host-units"}, 859 }) 860 m.SetAnnotations(map[string]string{ 861 "string": "value", 862 "another": "one", 863 }) 864 m.SetStatus(minimalStatusArgs()) 865 } 866 867 func (s *bundleSuite) TestExportBundleModelWithAnnotations(c *gc.C) { 868 model := s.newModel("iaas", "wordpress", "mysql") 869 870 s.addMinimalMachineWithAnnotations(model, "0") 871 s.addMinimalMachineWithAnnotations(model, "1") 872 873 model.SetStatus(description.StatusArgs{Value: "available"}) 874 875 result, err := s.facade.ExportBundle() 876 c.Assert(err, jc.ErrorIsNil) 877 expectedResult := params.StringResult{nil, ` 878 series: xenial 879 applications: 880 mysql: 881 charm: cs:mysql 882 num_units: 1 883 to: 884 - "0" 885 wordpress: 886 charm: cs:wordpress 887 num_units: 2 888 to: 889 - "0" 890 - "1" 891 machines: 892 "0": 893 annotations: 894 another: one 895 string: value 896 "1": 897 annotations: 898 another: one 899 string: value 900 relations: 901 - - wordpress:db 902 - mysql:mysql 903 `[1:]} 904 905 c.Assert(result, gc.Equals, expectedResult) 906 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 907 } 908 909 func (s *bundleSuite) TestExportBundleWithContainers(c *gc.C) { 910 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 911 Config: map[string]interface{}{ 912 "name": "awesome", 913 "uuid": "some-uuid", 914 }, 915 CloudRegion: "some-region"}) 916 917 application0 := s.st.model.AddApplication(description.ApplicationArgs{ 918 Tag: names.NewApplicationTag("wordpress"), 919 CharmURL: "cs:wordpress", 920 Series: "xenial", 921 }) 922 application0.SetStatus(minimalStatusArgs()) 923 924 m0 := s.st.model.AddMachine(description.MachineArgs{ 925 Id: names.NewMachineTag("0"), 926 Series: "xenial", 927 }) 928 args := description.ConstraintsArgs{ 929 Architecture: "amd64", 930 Memory: 8 * 1024, 931 RootDisk: 40 * 1024, 932 } 933 m0.SetConstraints(args) 934 ut0 := application0.AddUnit(description.UnitArgs{ 935 Tag: names.NewUnitTag("wordpress/0"), 936 Machine: names.NewMachineTag("0"), 937 }) 938 ut0.SetAgentStatus(minimalStatusArgs()) 939 940 application1 := s.st.model.AddApplication(description.ApplicationArgs{ 941 Tag: names.NewApplicationTag("mysql"), 942 CharmURL: "cs:mysql", 943 Series: "xenial", 944 }) 945 application1.SetStatus(minimalStatusArgs()) 946 947 m1 := s.st.model.AddMachine(description.MachineArgs{ 948 Id: names.NewMachineTag("1"), 949 Series: "xenial", 950 }) 951 args = description.ConstraintsArgs{ 952 Architecture: "amd64", 953 Memory: 8 * 1024, 954 RootDisk: 40 * 1024, 955 } 956 m1.SetConstraints(args) 957 958 ut := application1.AddUnit(description.UnitArgs{ 959 Tag: names.NewUnitTag("mysql/1"), 960 Machine: names.NewMachineTag("1/lxd/0"), 961 }) 962 ut.SetAgentStatus(minimalStatusArgs()) 963 964 result, err := s.facade.ExportBundle() 965 c.Assert(err, jc.ErrorIsNil) 966 expectedResult := params.StringResult{nil, ` 967 series: xenial 968 applications: 969 mysql: 970 charm: cs:mysql 971 num_units: 1 972 to: 973 - lxd:1 974 wordpress: 975 charm: cs:wordpress 976 num_units: 1 977 to: 978 - "0" 979 machines: 980 "0": 981 constraints: arch=amd64 mem=8192 root-disk=40960 982 "1": 983 constraints: arch=amd64 mem=8192 root-disk=40960 984 `[1:]} 985 986 c.Assert(result, gc.Equals, expectedResult) 987 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 988 } 989 990 func (s *bundleSuite) TestMixedSeries(c *gc.C) { 991 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 992 Config: map[string]interface{}{ 993 "name": "awesome", 994 "uuid": "some-uuid", 995 "default-series": "xenial", 996 }, 997 CloudRegion: "some-region"}) 998 999 application := s.st.model.AddApplication(description.ApplicationArgs{ 1000 Tag: names.NewApplicationTag("magic"), 1001 Series: "xenial", 1002 CharmURL: "cs:xenial/magic", 1003 }) 1004 application.AddUnit(description.UnitArgs{ 1005 Tag: names.NewUnitTag("magic/0"), 1006 Machine: names.NewMachineTag("0"), 1007 }) 1008 s.st.model.AddMachine(description.MachineArgs{ 1009 Id: names.NewMachineTag("0"), 1010 Series: "xenial", 1011 }) 1012 1013 application = s.st.model.AddApplication(description.ApplicationArgs{ 1014 Tag: names.NewApplicationTag("mojo"), 1015 Series: "trusty", 1016 CharmURL: "cs:mojo", 1017 }) 1018 application.AddUnit(description.UnitArgs{ 1019 Tag: names.NewUnitTag("mojo/0"), 1020 Machine: names.NewMachineTag("1"), 1021 }) 1022 s.st.model.AddMachine(description.MachineArgs{ 1023 Id: names.NewMachineTag("1"), 1024 Series: "trusty", 1025 }) 1026 1027 result, err := s.facade.ExportBundle() 1028 c.Assert(err, jc.ErrorIsNil) 1029 1030 expectedResult := params.StringResult{nil, ` 1031 series: xenial 1032 applications: 1033 magic: 1034 charm: cs:xenial/magic 1035 num_units: 1 1036 to: 1037 - "0" 1038 mojo: 1039 charm: cs:mojo 1040 series: trusty 1041 num_units: 1 1042 to: 1043 - "1" 1044 machines: 1045 "0": {} 1046 "1": 1047 series: trusty 1048 `[1:]} 1049 1050 c.Assert(result, gc.Equals, expectedResult) 1051 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1052 } 1053 1054 func (s *bundleSuite) TestMixedSeriesNoDefaultSeries(c *gc.C) { 1055 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 1056 Config: map[string]interface{}{ 1057 "name": "awesome", 1058 "uuid": "some-uuid", 1059 "default-series": "bionic", 1060 }, 1061 CloudRegion: "some-region"}) 1062 1063 application := s.st.model.AddApplication(description.ApplicationArgs{ 1064 Tag: names.NewApplicationTag("magic"), 1065 Series: "xenial", 1066 CharmURL: "cs:xenial/magic", 1067 }) 1068 application.AddUnit(description.UnitArgs{ 1069 Tag: names.NewUnitTag("magic/0"), 1070 Machine: names.NewMachineTag("0"), 1071 }) 1072 s.st.model.AddMachine(description.MachineArgs{ 1073 Id: names.NewMachineTag("0"), 1074 Series: "xenial", 1075 }) 1076 1077 application = s.st.model.AddApplication(description.ApplicationArgs{ 1078 Tag: names.NewApplicationTag("mojo"), 1079 Series: "trusty", 1080 CharmURL: "cs:mojo", 1081 }) 1082 application.AddUnit(description.UnitArgs{ 1083 Tag: names.NewUnitTag("mojo/0"), 1084 Machine: names.NewMachineTag("1"), 1085 }) 1086 s.st.model.AddMachine(description.MachineArgs{ 1087 Id: names.NewMachineTag("1"), 1088 Series: "trusty", 1089 }) 1090 1091 result, err := s.facade.ExportBundle() 1092 c.Assert(err, jc.ErrorIsNil) 1093 1094 expectedResult := params.StringResult{nil, ` 1095 applications: 1096 magic: 1097 charm: cs:xenial/magic 1098 series: xenial 1099 num_units: 1 1100 to: 1101 - "0" 1102 mojo: 1103 charm: cs:mojo 1104 series: trusty 1105 num_units: 1 1106 to: 1107 - "1" 1108 machines: 1109 "0": 1110 series: xenial 1111 "1": 1112 series: trusty 1113 `[1:]} 1114 1115 c.Assert(result, gc.Equals, expectedResult) 1116 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1117 } 1118 1119 func (s *bundleSuite) TestExportKubernetesBundle(c *gc.C) { 1120 model := s.newModel("caas", "wordpress", "mysql") 1121 model.SetStatus(description.StatusArgs{Value: "available"}) 1122 1123 result, err := s.facade.ExportBundle() 1124 c.Assert(err, jc.ErrorIsNil) 1125 1126 output := ` 1127 bundle: kubernetes 1128 applications: 1129 mysql: 1130 charm: cs:mysql 1131 scale: 1 1132 placement: foo=bar 1133 wordpress: 1134 charm: cs:wordpress 1135 scale: 2 1136 placement: foo=bar 1137 relations: 1138 - - wordpress:db 1139 - mysql:mysql 1140 `[1:] 1141 expectedResult := params.StringResult{nil, output} 1142 1143 c.Assert(result, gc.Equals, expectedResult) 1144 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1145 }