github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "strings" 9 10 "github.com/juju/charm/v12" 11 "github.com/juju/charm/v12/resource" 12 "github.com/juju/description/v5" 13 "github.com/juju/names/v5" 14 jc "github.com/juju/testing/checkers" 15 "github.com/kr/pretty" 16 gc "gopkg.in/check.v1" 17 18 appFacade "github.com/juju/juju/apiserver/facades/client/application" 19 "github.com/juju/juju/apiserver/facades/client/bundle" 20 apiservertesting "github.com/juju/juju/apiserver/testing" 21 "github.com/juju/juju/core/network" 22 "github.com/juju/juju/core/network/firewall" 23 "github.com/juju/juju/rpc/params" 24 coretesting "github.com/juju/juju/testing" 25 ) 26 27 type bundleSuite struct { 28 coretesting.BaseSuite 29 auth *apiservertesting.FakeAuthorizer 30 facade *bundle.APIv6 31 st *mockState 32 modelTag names.ModelTag 33 } 34 35 var _ = gc.Suite(&bundleSuite{}) 36 37 func (s *bundleSuite) SetUpTest(c *gc.C) { 38 s.BaseSuite.SetUpTest(c) 39 s.auth = &apiservertesting.FakeAuthorizer{ 40 Tag: names.NewUserTag("read"), 41 } 42 43 s.st = newMockState() 44 s.modelTag = names.NewModelTag("some-uuid") 45 s.facade = s.makeAPI(c) 46 } 47 48 func (s *bundleSuite) makeAPI(c *gc.C) *bundle.APIv6 { 49 api, err := bundle.NewBundleAPI( 50 s.st, 51 s.auth, 52 s.modelTag, 53 ) 54 c.Assert(err, jc.ErrorIsNil) 55 return &bundle.APIv6{api} 56 } 57 58 func (s *bundleSuite) TestGetChangesBundleContentError(c *gc.C) { 59 args := params.BundleChangesParams{ 60 BundleDataYAML: ":", 61 } 62 r, err := s.facade.GetChanges(args) 63 c.Assert(err, gc.ErrorMatches, `cannot read bundle YAML: malformed bundle: bundle is empty not valid`) 64 c.Assert(r, gc.DeepEquals, params.BundleChangesResults{}) 65 } 66 67 func (s *bundleSuite) TestGetChangesBundleVerificationErrors(c *gc.C) { 68 args := params.BundleChangesParams{ 69 BundleDataYAML: ` 70 applications: 71 django: 72 charm: django 73 to: [1] 74 haproxy: 75 charm: 42 76 num_units: -1 77 `, 78 } 79 r, err := s.facade.GetChanges(args) 80 c.Assert(err, jc.ErrorIsNil) 81 c.Assert(r.Changes, gc.IsNil) 82 c.Assert(r.Errors, jc.SameContents, []string{ 83 `placement "1" refers to a machine not defined in this bundle`, 84 `too many units specified in unit placement for application "django"`, 85 `invalid charm URL in application "haproxy": cannot parse name and/or revision in URL "42": name "42" not valid`, 86 `negative number of units specified on application "haproxy"`, 87 }) 88 } 89 90 func (s *bundleSuite) TestGetChangesBundleConstraintsError(c *gc.C) { 91 args := params.BundleChangesParams{ 92 BundleDataYAML: ` 93 applications: 94 django: 95 charm: django 96 num_units: 1 97 constraints: bad=wolf 98 `, 99 } 100 r, err := s.facade.GetChanges(args) 101 c.Assert(err, jc.ErrorIsNil) 102 c.Assert(r.Changes, gc.IsNil) 103 c.Assert(r.Errors, jc.SameContents, []string{ 104 `invalid constraints "bad=wolf" in application "django": unknown constraint "bad"`, 105 }) 106 } 107 108 func (s *bundleSuite) TestGetChangesBundleStorageError(c *gc.C) { 109 args := params.BundleChangesParams{ 110 BundleDataYAML: ` 111 applications: 112 django: 113 charm: django 114 num_units: 1 115 storage: 116 bad: 0,100M 117 `, 118 } 119 r, err := s.facade.GetChanges(args) 120 c.Assert(err, jc.ErrorIsNil) 121 c.Assert(r.Changes, gc.IsNil) 122 c.Assert(r.Errors, jc.SameContents, []string{ 123 `invalid storage "bad" in application "django": cannot parse count: count must be greater than zero, got "0"`, 124 }) 125 } 126 127 func (s *bundleSuite) TestGetChangesBundleDevicesError(c *gc.C) { 128 args := params.BundleChangesParams{ 129 BundleDataYAML: ` 130 applications: 131 django: 132 charm: django 133 num_units: 1 134 devices: 135 bad-gpu: -1,nvidia.com/gpu 136 `, 137 } 138 r, err := s.facade.GetChanges(args) 139 c.Assert(err, jc.ErrorIsNil) 140 c.Assert(r.Changes, gc.IsNil) 141 c.Assert(r.Errors, jc.SameContents, []string{ 142 `invalid device "bad-gpu" in application "django": count must be greater than zero, got "-1"`, 143 }) 144 } 145 146 func (s *bundleSuite) TestGetChangesSuccessV2(c *gc.C) { 147 args := params.BundleChangesParams{ 148 BundleDataYAML: ` 149 applications: 150 django: 151 charm: django 152 options: 153 debug: true 154 storage: 155 tmpfs: tmpfs,1G 156 devices: 157 bitcoinminer: 2,nvidia.com/gpu 158 haproxy: 159 charm: haproxy 160 revision: 42 161 channel: stable 162 base: ubuntu@22.04/stable 163 relations: 164 - - django:web 165 - haproxy:web 166 `, 167 } 168 r, err := s.facade.GetChanges(args) 169 c.Assert(err, jc.ErrorIsNil) 170 c.Check(r.Changes, jc.DeepEquals, []*params.BundleChange{{ 171 Id: "addCharm-0", 172 Method: "addCharm", 173 Args: []interface{}{"django", "", ""}, 174 }, { 175 Id: "deploy-1", 176 Method: "deploy", 177 Args: []interface{}{ 178 "$addCharm-0", 179 "", 180 "django", 181 map[string]interface{}{"debug": true}, 182 "", 183 map[string]string{"tmpfs": "tmpfs,1G"}, 184 map[string]string{"bitcoinminer": "2,nvidia.com/gpu"}, 185 map[string]string{}, 186 map[string]int{}, 187 0, 188 "", 189 }, 190 Requires: []string{"addCharm-0"}, 191 }, { 192 Id: "addCharm-2", 193 Method: "addCharm", 194 Args: []interface{}{"haproxy", "jammy", "stable"}, 195 }, { 196 Id: "deploy-3", 197 Method: "deploy", 198 Args: []interface{}{ 199 "$addCharm-2", 200 "jammy", 201 "haproxy", 202 map[string]interface{}{}, 203 "", 204 map[string]string{}, 205 map[string]string{}, 206 map[string]string{}, 207 map[string]int{}, 208 0, 209 "stable", 210 }, 211 Requires: []string{"addCharm-2"}, 212 }, { 213 Id: "addRelation-4", 214 Method: "addRelation", 215 Args: []interface{}{"$deploy-1:web", "$deploy-3:web"}, 216 Requires: []string{"deploy-1", "deploy-3"}, 217 }}, gc.Commentf("\nobtained: %s\n", pretty.Sprint(r.Changes))) 218 c.Assert(r.Errors, gc.IsNil) 219 } 220 221 func (s *bundleSuite) TestGetChangesWithOverlaysV6(c *gc.C) { 222 args := params.BundleChangesParams{ 223 BundleDataYAML: ` 224 applications: 225 django: 226 charm: django 227 options: 228 debug: true 229 storage: 230 tmpfs: tmpfs,1G 231 devices: 232 bitcoinminer: 2,nvidia.com/gpu 233 haproxy: 234 charm: ch:haproxy-42 235 relations: 236 - - django:web 237 - haproxy:web 238 --- # overlay 239 description: remove haproxy 240 applications: 241 haproxy: 242 `, 243 } 244 245 expectedChanges_V6 := []*params.BundleChange{{ 246 Id: "addCharm-0", 247 Method: "addCharm", 248 Args: []interface{}{"django", "", ""}, 249 }, { 250 Id: "deploy-1", 251 Method: "deploy", 252 Args: []interface{}{ 253 "$addCharm-0", 254 "", 255 "django", 256 map[string]interface{}{"debug": true}, 257 "", 258 map[string]string{"tmpfs": "tmpfs,1G"}, 259 map[string]string{"bitcoinminer": "2,nvidia.com/gpu"}, 260 map[string]string{}, 261 map[string]int{}, 262 0, 263 "", 264 }, 265 Requires: []string{"addCharm-0"}, 266 }} 267 268 apiv6 := s.makeAPI(c) 269 r_v6, err_v6 := apiv6.GetChanges(args) 270 c.Assert(err_v6, jc.ErrorIsNil) 271 c.Assert(r_v6.Changes, jc.DeepEquals, expectedChanges_V6) 272 } 273 274 func (s *bundleSuite) TestGetChangesKubernetes(c *gc.C) { 275 args := params.BundleChangesParams{ 276 BundleDataYAML: ` 277 bundle: kubernetes 278 applications: 279 django: 280 charm: django 281 scale: 1 282 options: 283 debug: true 284 storage: 285 tmpfs: tmpfs,1G 286 devices: 287 bitcoinminer: 2,nvidia.com/gpu 288 haproxy: 289 charm: ch:haproxy 290 revision: 42 291 channel: stable 292 relations: 293 - - django:web 294 - haproxy:web 295 `, 296 } 297 r, err := s.facade.GetChanges(args) 298 c.Assert(err, jc.ErrorIsNil) 299 c.Check(r.Changes, jc.DeepEquals, []*params.BundleChange{{ 300 Id: "addCharm-0", 301 Method: "addCharm", 302 Args: []interface{}{"django", "", ""}, 303 }, { 304 Id: "deploy-1", 305 Method: "deploy", 306 Args: []interface{}{ 307 "$addCharm-0", 308 "", 309 "django", 310 map[string]interface{}{"debug": true}, 311 "", 312 map[string]string{"tmpfs": "tmpfs,1G"}, 313 map[string]string{"bitcoinminer": "2,nvidia.com/gpu"}, 314 map[string]string{}, 315 map[string]int{}, 316 1, 317 "", 318 }, 319 Requires: []string{"addCharm-0"}, 320 }, { 321 Id: "addCharm-2", 322 Method: "addCharm", 323 Args: []interface{}{"ch:haproxy", "", "stable"}, 324 }, { 325 Id: "deploy-3", 326 Method: "deploy", 327 Args: []interface{}{ 328 "$addCharm-2", 329 "", 330 "haproxy", 331 map[string]interface{}{}, 332 "", 333 map[string]string{}, 334 map[string]string{}, 335 map[string]string{}, 336 map[string]int{}, 337 0, 338 "stable", 339 }, 340 Requires: []string{"addCharm-2"}, 341 }, { 342 Id: "addRelation-4", 343 Method: "addRelation", 344 Args: []interface{}{"$deploy-1:web", "$deploy-3:web"}, 345 Requires: []string{"deploy-1", "deploy-3"}, 346 }}, gc.Commentf("\nobtained: %s\n", pretty.Sprint(r.Changes))) 347 c.Assert(r.Errors, gc.IsNil) 348 } 349 350 func (s *bundleSuite) TestGetChangesBundleEndpointBindingsSuccess(c *gc.C) { 351 args := params.BundleChangesParams{ 352 BundleDataYAML: ` 353 applications: 354 django: 355 charm: django 356 num_units: 1 357 bindings: 358 url: public 359 `, 360 } 361 r, err := s.facade.GetChanges(args) 362 c.Assert(err, jc.ErrorIsNil) 363 364 for _, change := range r.Changes { 365 if change.Method == "deploy" { 366 c.Assert(change, jc.DeepEquals, ¶ms.BundleChange{ 367 Id: "deploy-1", 368 Method: "deploy", 369 Args: []interface{}{ 370 "$addCharm-0", 371 "", 372 "django", 373 map[string]interface{}{}, 374 "", 375 map[string]string{}, 376 map[string]string{}, 377 map[string]string{"url": "public"}, 378 map[string]int{}, 379 0, 380 "", 381 }, 382 Requires: []string{"addCharm-0"}, 383 }) 384 } 385 } 386 } 387 388 func (s *bundleSuite) TestGetChangesMapArgsBundleContentError(c *gc.C) { 389 args := params.BundleChangesParams{ 390 BundleDataYAML: ":", 391 } 392 r, err := s.facade.GetChangesMapArgs(args) 393 c.Assert(err, gc.ErrorMatches, `cannot read bundle YAML: malformed bundle: bundle is empty not valid`) 394 c.Assert(r, gc.DeepEquals, params.BundleChangesMapArgsResults{}) 395 } 396 397 func (s *bundleSuite) TestGetChangesMapArgsBundleVerificationErrors(c *gc.C) { 398 args := params.BundleChangesParams{ 399 BundleDataYAML: ` 400 applications: 401 django: 402 charm: django 403 to: [1] 404 haproxy: 405 charm: 42 406 num_units: -1 407 `, 408 } 409 r, err := s.facade.GetChangesMapArgs(args) 410 c.Assert(err, jc.ErrorIsNil) 411 c.Assert(r.Changes, gc.IsNil) 412 c.Assert(r.Errors, jc.SameContents, []string{ 413 `placement "1" refers to a machine not defined in this bundle`, 414 `too many units specified in unit placement for application "django"`, 415 `invalid charm URL in application "haproxy": cannot parse name and/or revision in URL "42": name "42" not valid`, 416 `negative number of units specified on application "haproxy"`, 417 }) 418 } 419 420 func (s *bundleSuite) TestGetChangesMapArgsBundleConstraintsError(c *gc.C) { 421 args := params.BundleChangesParams{ 422 BundleDataYAML: ` 423 applications: 424 django: 425 charm: django 426 num_units: 1 427 constraints: bad=wolf 428 `, 429 } 430 r, err := s.facade.GetChangesMapArgs(args) 431 c.Assert(err, jc.ErrorIsNil) 432 c.Assert(r.Changes, gc.IsNil) 433 c.Assert(r.Errors, jc.SameContents, []string{ 434 `invalid constraints "bad=wolf" in application "django": unknown constraint "bad"`, 435 }) 436 } 437 438 func (s *bundleSuite) TestGetChangesMapArgsBundleStorageError(c *gc.C) { 439 args := params.BundleChangesParams{ 440 BundleDataYAML: ` 441 applications: 442 django: 443 charm: django 444 num_units: 1 445 storage: 446 bad: 0,100M 447 `, 448 } 449 r, err := s.facade.GetChangesMapArgs(args) 450 c.Assert(err, jc.ErrorIsNil) 451 c.Assert(r.Changes, gc.IsNil) 452 c.Assert(r.Errors, jc.SameContents, []string{ 453 `invalid storage "bad" in application "django": cannot parse count: count must be greater than zero, got "0"`, 454 }) 455 } 456 457 func (s *bundleSuite) TestGetChangesMapArgsBundleDevicesError(c *gc.C) { 458 args := params.BundleChangesParams{ 459 BundleDataYAML: ` 460 applications: 461 django: 462 charm: django 463 num_units: 1 464 devices: 465 bad-gpu: -1,nvidia.com/gpu 466 `, 467 } 468 r, err := s.facade.GetChangesMapArgs(args) 469 c.Assert(err, jc.ErrorIsNil) 470 c.Assert(r.Changes, gc.IsNil) 471 c.Assert(r.Errors, jc.SameContents, []string{ 472 `invalid device "bad-gpu" in application "django": count must be greater than zero, got "-1"`, 473 }) 474 } 475 476 func (s *bundleSuite) TestGetChangesMapArgsSuccess(c *gc.C) { 477 args := params.BundleChangesParams{ 478 BundleDataYAML: ` 479 applications: 480 django: 481 charm: django 482 options: 483 debug: true 484 storage: 485 tmpfs: tmpfs,1G 486 devices: 487 bitcoinminer: 2,nvidia.com/gpu 488 haproxy: 489 charm: ch:haproxy 490 revision: 42 491 channel: stable 492 base: ubuntu@22.04/stable 493 relations: 494 - - django:web 495 - haproxy:web 496 `, 497 } 498 r, err := s.facade.GetChangesMapArgs(args) 499 c.Assert(err, jc.ErrorIsNil) 500 c.Check(r.Changes, jc.DeepEquals, []*params.BundleChangesMapArgs{{ 501 Id: "addCharm-0", 502 Method: "addCharm", 503 Args: map[string]interface{}{ 504 "charm": "django", 505 }, 506 }, { 507 Id: "deploy-1", 508 Method: "deploy", 509 Args: map[string]interface{}{ 510 "application": "django", 511 "charm": "$addCharm-0", 512 "devices": map[string]interface{}{ 513 "bitcoinminer": "2,nvidia.com/gpu", 514 }, 515 "options": map[string]interface{}{ 516 "debug": true, 517 }, 518 "storage": map[string]interface{}{ 519 "tmpfs": "tmpfs,1G", 520 }, 521 }, 522 Requires: []string{"addCharm-0"}, 523 }, { 524 Id: "addCharm-2", 525 Method: "addCharm", 526 Args: map[string]interface{}{ 527 "channel": "stable", 528 "charm": "ch:haproxy", 529 "revision": float64(42), 530 "series": "jammy", 531 "base": "ubuntu@22.04/stable", 532 }, 533 }, { 534 Id: "deploy-3", 535 Method: "deploy", 536 Args: map[string]interface{}{ 537 "channel": "stable", 538 "application": "haproxy", 539 "charm": "$addCharm-2", 540 "series": "jammy", 541 "base": "ubuntu@22.04/stable", 542 }, 543 Requires: []string{"addCharm-2"}, 544 }, { 545 Id: "addRelation-4", 546 Method: "addRelation", 547 Args: map[string]interface{}{ 548 "endpoint1": "$deploy-1:web", 549 "endpoint2": "$deploy-3:web", 550 }, 551 Requires: []string{"deploy-1", "deploy-3"}, 552 }}, gc.Commentf("\nobtained: %s\n", pretty.Sprint(r.Changes))) 553 for _, err := range r.Errors { 554 c.Assert(err, gc.Equals, "") 555 } 556 } 557 558 func (s *bundleSuite) TestGetChangesMapArgsSuccessCharmHubRevision(c *gc.C) { 559 args := params.BundleChangesParams{ 560 BundleDataYAML: ` 561 applications: 562 django: 563 charm: django 564 revision: 76 565 channel: candidate 566 `, 567 } 568 r, err := s.facade.GetChangesMapArgs(args) 569 c.Assert(err, jc.ErrorIsNil) 570 c.Check(r.Changes, jc.DeepEquals, []*params.BundleChangesMapArgs{{ 571 Id: "addCharm-0", 572 Method: "addCharm", 573 Args: map[string]interface{}{ 574 "revision": float64(76), 575 "channel": "candidate", 576 "charm": "django", 577 }, 578 }, { 579 Id: "deploy-1", 580 Method: "deploy", 581 Args: map[string]interface{}{ 582 "application": "django", 583 "channel": "candidate", 584 "charm": "$addCharm-0", 585 }, 586 Requires: []string{"addCharm-0"}, 587 }}) 588 for _, err := range r.Errors { 589 c.Assert(err, gc.Equals, "") 590 } 591 } 592 593 func (s *bundleSuite) TestGetChangesMapArgsKubernetes(c *gc.C) { 594 args := params.BundleChangesParams{ 595 BundleDataYAML: ` 596 bundle: kubernetes 597 applications: 598 django: 599 charm: django 600 scale: 1 601 options: 602 debug: true 603 storage: 604 tmpfs: tmpfs,1G 605 devices: 606 bitcoinminer: 2,nvidia.com/gpu 607 haproxy: 608 charm: ch:haproxy 609 revision: 42 610 channel: stable 611 relations: 612 - - django:web 613 - haproxy:web 614 `, 615 } 616 r, err := s.facade.GetChangesMapArgs(args) 617 c.Assert(err, jc.ErrorIsNil) 618 c.Check(r.Changes, jc.DeepEquals, []*params.BundleChangesMapArgs{{ 619 Id: "addCharm-0", 620 Method: "addCharm", 621 Args: map[string]interface{}{ 622 "charm": "django", 623 }, 624 }, { 625 Id: "deploy-1", 626 Method: "deploy", 627 Args: map[string]interface{}{ 628 "application": "django", 629 "charm": "$addCharm-0", 630 "devices": map[string]interface{}{ 631 "bitcoinminer": "2,nvidia.com/gpu", 632 }, 633 "num-units": float64(1), 634 "options": map[string]interface{}{ 635 "debug": true, 636 }, 637 "storage": map[string]interface{}{ 638 "tmpfs": "tmpfs,1G", 639 }, 640 }, 641 Requires: []string{"addCharm-0"}, 642 }, { 643 Id: "addCharm-2", 644 Method: "addCharm", 645 Args: map[string]interface{}{ 646 "channel": "stable", 647 "charm": "ch:haproxy", 648 "revision": float64(42), 649 }, 650 }, { 651 Id: "deploy-3", 652 Method: "deploy", 653 Args: map[string]interface{}{ 654 "channel": "stable", 655 "application": "haproxy", 656 "charm": "$addCharm-2", 657 }, 658 Requires: []string{"addCharm-2"}, 659 }, { 660 Id: "addRelation-4", 661 Method: "addRelation", 662 Args: map[string]interface{}{ 663 "endpoint1": "$deploy-1:web", 664 "endpoint2": "$deploy-3:web", 665 }, 666 Requires: []string{"deploy-1", "deploy-3"}, 667 }}, gc.Commentf("\nobtained: %s\n", pretty.Sprint(r.Changes))) 668 for _, err := range r.Errors { 669 c.Assert(err, gc.Equals, "") 670 } 671 } 672 673 func (s *bundleSuite) TestGetChangesMapArgsBundleEndpointBindingsSuccess(c *gc.C) { 674 args := params.BundleChangesParams{ 675 BundleDataYAML: ` 676 applications: 677 django: 678 charm: django 679 num_units: 1 680 bindings: 681 url: public 682 `, 683 } 684 r, err := s.facade.GetChangesMapArgs(args) 685 c.Assert(err, jc.ErrorIsNil) 686 687 for _, change := range r.Changes { 688 if change.Method == "deploy" { 689 c.Assert(change, jc.DeepEquals, ¶ms.BundleChangesMapArgs{ 690 Id: "deploy-1", 691 Method: "deploy", 692 Args: map[string]interface{}{ 693 "application": "django", 694 "charm": "$addCharm-0", 695 "endpoint-bindings": map[string]interface{}{ 696 "url": "public", 697 }, 698 }, 699 Requires: []string{"addCharm-0"}, 700 }) 701 } 702 } 703 } 704 705 func (s *bundleSuite) TestExportBundleFailNoApplication(c *gc.C) { 706 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 707 Config: coretesting.FakeConfig(), 708 CloudRegion: "some-region"}) 709 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 710 711 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 712 c.Assert(err, gc.NotNil) 713 c.Assert(result, gc.Equals, params.StringResult{}) 714 c.Check(err, gc.ErrorMatches, "nothing to export as there are no applications") 715 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 716 } 717 718 func (s *bundleSuite) minimalApplicationArgs(modelType string) description.ApplicationArgs { 719 return s.minimalApplicationArgsWithCharmConfig(modelType, map[string]interface{}{ 720 "key": "value", 721 }) 722 } 723 724 func (s *bundleSuite) minimalApplicationArgsWithCharmConfig(modelType string, charmConfig map[string]interface{}) description.ApplicationArgs { 725 s.st.Spaces["1"] = "vlan2" 726 s.st.Spaces[network.AlphaSpaceId] = network.AlphaSpaceName 727 result := description.ApplicationArgs{ 728 Tag: names.NewApplicationTag("ubuntu"), 729 Type: modelType, 730 CharmURL: "ch:ubuntu", 731 Channel: "stable", 732 CharmModifiedVersion: 1, 733 CharmConfig: charmConfig, 734 Leader: "ubuntu/0", 735 LeadershipSettings: map[string]interface{}{ 736 "leader": true, 737 }, 738 MetricsCredentials: []byte("sekrit"), 739 EndpointBindings: map[string]string{"juju-info": "1", "another": "0"}, 740 } 741 if modelType == description.CAAS { 742 result.PasswordHash = "some-hash" 743 result.PodSpec = "some-spec" 744 result.CloudService = &description.CloudServiceArgs{ 745 ProviderId: "some-provider", 746 Addresses: []description.AddressArgs{ 747 {Value: "10.0.0.1", Type: "special"}, 748 {Value: "10.0.0.2", Type: "other"}, 749 }, 750 } 751 } 752 return result 753 } 754 755 func minimalUnitArgs(modelType string) description.UnitArgs { 756 result := description.UnitArgs{ 757 Tag: names.NewUnitTag("ubuntu/0"), 758 Type: modelType, 759 Machine: names.NewMachineTag("0"), 760 PasswordHash: "secure-hash", 761 } 762 if modelType == description.CAAS { 763 result.CloudContainer = &description.CloudContainerArgs{ 764 ProviderId: "some-provider", 765 Address: description.AddressArgs{Value: "10.0.0.1", Type: "special"}, 766 Ports: []string{"80", "443"}, 767 } 768 } 769 return result 770 } 771 772 func minimalStatusArgs() description.StatusArgs { 773 return description.StatusArgs{ 774 Value: "running", 775 } 776 } 777 778 func (s *bundleSuite) TestExportBundleWithApplication(c *gc.C) { 779 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 780 Config: coretesting.FakeConfig(), 781 CloudRegion: "some-region"}) 782 783 app := s.st.model.AddApplication(s.minimalApplicationArgs(description.IAAS)) 784 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 785 app.SetStatus(minimalStatusArgs()) 786 787 u := app.AddUnit(minimalUnitArgs(app.Type())) 788 u.SetAgentStatus(minimalStatusArgs()) 789 790 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 791 792 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 793 c.Assert(err, jc.ErrorIsNil) 794 expectedResult := params.StringResult{Result: ` 795 default-base: ubuntu@20.04/stable 796 applications: 797 ubuntu: 798 charm: ubuntu 799 channel: stable 800 num_units: 1 801 to: 802 - "0" 803 options: 804 key: value 805 bindings: 806 another: alpha 807 juju-info: vlan2 808 `[1:]} 809 810 c.Assert(result, gc.Equals, expectedResult) 811 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 812 } 813 814 func (s *bundleSuite) TestExportBundleWithApplicationResources(c *gc.C) { 815 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 816 Config: coretesting.FakeConfig(), 817 CloudRegion: "some-region"}) 818 819 app := s.st.model.AddApplication(s.minimalApplicationArgs(description.IAAS)) 820 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 821 app.SetStatus(minimalStatusArgs()) 822 823 res := app.AddResource(description.ResourceArgs{Name: "foo-file"}) 824 res.SetApplicationRevision(description.ResourceRevisionArgs{ 825 Revision: 42, 826 Type: "file", 827 Origin: resource.OriginStore.String(), 828 }) 829 res2 := app.AddResource(description.ResourceArgs{Name: "bar-file"}) 830 res2.SetApplicationRevision(description.ResourceRevisionArgs{ 831 Revision: 0, 832 Type: "file", 833 Origin: resource.OriginUpload.String(), 834 }) 835 836 u := app.AddUnit(minimalUnitArgs(app.Type())) 837 u.SetAgentStatus(minimalStatusArgs()) 838 839 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 840 841 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 842 c.Assert(err, jc.ErrorIsNil) 843 expectedResult := params.StringResult{Result: ` 844 default-base: ubuntu@20.04/stable 845 applications: 846 ubuntu: 847 charm: ubuntu 848 channel: stable 849 resources: 850 foo-file: 42 851 num_units: 1 852 to: 853 - "0" 854 options: 855 key: value 856 bindings: 857 another: alpha 858 juju-info: vlan2 859 `[1:]} 860 861 c.Assert(result, gc.Equals, expectedResult) 862 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 863 } 864 865 func (s *bundleSuite) TestExportBundleWithApplicationStorage(c *gc.C) { 866 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 867 Config: coretesting.FakeConfig(), 868 CloudRegion: "some-region"}) 869 870 args := s.minimalApplicationArgs(description.IAAS) 871 // Add storage directives to the app 872 args.StorageDirectives = map[string]description.StorageDirectiveArgs{ 873 "storage1": { 874 Pool: "pool1", 875 Size: 1024, 876 Count: 3, 877 }, 878 "storage2": { 879 Pool: "pool2", 880 Size: 4096, 881 }, 882 "storage3": { 883 Size: 2048, 884 }, 885 } 886 app := s.st.model.AddApplication(args) 887 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 888 app.SetStatus(minimalStatusArgs()) 889 890 u := app.AddUnit(minimalUnitArgs(app.Type())) 891 u.SetAgentStatus(minimalStatusArgs()) 892 893 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 894 895 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 896 c.Assert(err, jc.ErrorIsNil) 897 expectedResult := params.StringResult{Result: ` 898 default-base: ubuntu@20.04/stable 899 applications: 900 ubuntu: 901 charm: ubuntu 902 channel: stable 903 num_units: 1 904 to: 905 - "0" 906 options: 907 key: value 908 storage: 909 storage1: pool1,3,1024M 910 storage2: pool2,4096M 911 storage3: 2048M 912 bindings: 913 another: alpha 914 juju-info: vlan2 915 `[1:]} 916 917 c.Assert(result, gc.Equals, expectedResult) 918 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 919 } 920 921 func (s *bundleSuite) TestExportBundleWithTrustedApplication(c *gc.C) { 922 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 923 Config: coretesting.FakeConfig(), 924 CloudRegion: "some-region"}) 925 926 appArgs := s.minimalApplicationArgs(description.IAAS) 927 appArgs.ApplicationConfig = map[string]interface{}{ 928 appFacade.TrustConfigOptionName: true, 929 } 930 931 app := s.st.model.AddApplication(appArgs) 932 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 933 app.SetStatus(minimalStatusArgs()) 934 935 u := app.AddUnit(minimalUnitArgs(app.Type())) 936 u.SetAgentStatus(minimalStatusArgs()) 937 938 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 939 940 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 941 c.Assert(err, jc.ErrorIsNil) 942 expectedResult := params.StringResult{Result: ` 943 default-base: ubuntu@20.04/stable 944 applications: 945 ubuntu: 946 charm: ubuntu 947 channel: stable 948 num_units: 1 949 to: 950 - "0" 951 options: 952 key: value 953 bindings: 954 another: alpha 955 juju-info: vlan2 956 trust: true 957 `[1:]} 958 959 c.Assert(result, gc.Equals, expectedResult) 960 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 961 } 962 963 func (s *bundleSuite) TestExportBundleWithApplicationOffers(c *gc.C) { 964 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 965 Config: coretesting.FakeConfig(), 966 CloudRegion: "some-region"}) 967 968 app := s.st.model.AddApplication(s.minimalApplicationArgs(description.IAAS)) 969 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 970 app.SetStatus(minimalStatusArgs()) 971 u := app.AddUnit(minimalUnitArgs(app.Type())) 972 u.SetAgentStatus(minimalStatusArgs()) 973 974 _ = app.AddOffer(description.ApplicationOfferArgs{ 975 OfferName: "my-offer", 976 Endpoints: map[string]string{ 977 "endpoint-1": "endpoint-1", 978 "endpoint-2": "endpoint-2", 979 }, 980 ACL: map[string]string{ 981 "admin": "admin", 982 "foo": "consume", 983 }, 984 }) 985 986 _ = app.AddOffer(description.ApplicationOfferArgs{ 987 OfferName: "my-other-offer", 988 Endpoints: map[string]string{ 989 "endpoint-1": "endpoint-1", 990 "endpoint-2": "endpoint-2", 991 }, 992 }) 993 994 // Add second app without an offer 995 app2Args := s.minimalApplicationArgs(description.IAAS) 996 app2Args.Tag = names.NewApplicationTag("foo") 997 app2 := s.st.model.AddApplication(app2Args) 998 app2.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 999 app2.SetStatus(minimalStatusArgs()) 1000 1001 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 1002 1003 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1004 c.Assert(err, jc.ErrorIsNil) 1005 expectedResult := params.StringResult{Result: ` 1006 default-base: ubuntu@20.04/stable 1007 applications: 1008 foo: 1009 charm: ubuntu 1010 channel: stable 1011 options: 1012 key: value 1013 bindings: 1014 another: alpha 1015 juju-info: vlan2 1016 ubuntu: 1017 charm: ubuntu 1018 channel: stable 1019 num_units: 1 1020 to: 1021 - "0" 1022 options: 1023 key: value 1024 bindings: 1025 another: alpha 1026 juju-info: vlan2 1027 --- # overlay.yaml 1028 applications: 1029 ubuntu: 1030 offers: 1031 my-offer: 1032 endpoints: 1033 - endpoint-1 1034 - endpoint-2 1035 acl: 1036 admin: admin 1037 foo: consume 1038 my-other-offer: 1039 endpoints: 1040 - endpoint-1 1041 - endpoint-2 1042 `[1:]} 1043 1044 c.Assert(result, gc.Equals, expectedResult) 1045 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1046 } 1047 1048 func (s *bundleSuite) TestExportBundleWithApplicationCharmConfig(c *gc.C) { 1049 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 1050 Config: coretesting.FakeConfig(), 1051 CloudRegion: "some-region"}) 1052 1053 app := s.st.model.AddApplication(s.minimalApplicationArgsWithCharmConfig(description.IAAS, map[string]interface{}{ 1054 "key": "value", 1055 "ssl_ca": `-----BEGIN RSA PRIVATE KEY----- 1056 Proc-Type: 4,ENCRYPTED 1057 DEK-Info: DES-EDE3-CBC,EA657EE6FE842AD0 1058 1059 B8UX3J49NUiCPo0y/fhUJWdSzKjWicY13BJrsX5cv20gVjPLkkSo54BvUwhn8QgJ 1060 D0n4TJ4TcpUtQCDhbXZNiNPNQOoahRi7AkViDSoGxZ/6HKaJR7NqDHaiikgQFcb5 1061 6dMG8C30VjuTrbS4y5O/LqmYV9Gj6bkxuHGgxJaDN2og2RJCRJb16Ubl0HR2S129 1062 dZA0UyQTtDkUwlhVBaQW0Rp0nwVTFZo8g5PXpvT+MsdGgs5Pop+lYq4gSCpuw3vV 1063 T2UBOEQyYcok//9J3N+GRgfQbTidbgs8uGFGT4L8b8Ybji7Dhm/SkKpgVdH2wMuD 1064 yxKSfIt/Gm7j1I2DnZtLrd+zUSJql2UiYMeQ7suHBHBKtFriv01zFeCzChQ17uiJ 1065 NUo5IBzc+fagnN6aNaA5ImR07PHwMu4es4ORAEB+q/N3/0Eoe1U9h+ZBd6a7DM5V 1066 5A/wizu2jM25tfTNwQfaSkYCZzpGmucVwMj7NO5Rbjw2/PCOo5IP8GNahXF/cxPQ 1067 KqbIyT9nMPvKX8o9MR3Adzv5to5s9cPSyKSFfSZhJv+oXRdr8z9WaElIBJ0K0um/ 1068 ueteAX4s+U2F1Y7FAG1ivUnCOrzezvivuiPq7JS2PJh/jbdyu30xAu2gG/LZRWx+ 1069 m9zwCMUSxdrsBefl05WTzTjlpclwdjoG1sztRZ1d3/PoaMtjfLGePyEPbPFpPraq 1070 //Dj/MNKy9sefqX4NcqRtMvGj/0VuFspXsl3B1fHRrM7BsB8IM0+BheS22+dXfOK 1071 0EicO5ePw5Ji2vptC3ME+X3OztzJtfG/FN2CNvnLIKaR/N0v9Qt66U6aNoKPWD8l 1072 2PW+b/BV0dz0YTo16sd6txcmcaUIo5ysZrCWLYGsoRLL1uNAlgRT8Qb2jO8liEhg 1073 rfg5GH6RWpUL4mPo6cZ6GFAVPcdbPPSPMUSGscixeRo7oUqhnZrhvJ7L+I3TF7Pp 1074 +aWbBpRkTvYiB3/BkgMUfZzozAv6WB2MQttHqjo7cQYgT9MklCJ80a92BkVtQsRa 1075 hgDd6BUMA3ag4j0LivJflEmLd7prFVAcfyuN13UFku49soESUVnbPaWogyrGtc5w 1076 CvbTazcsKiDEJqGuZhfT0ixCNPrgLj5dNJrWOmFdpZRqa/tgShkf91qCjtMq7AEi 1077 eRQ/yOFb0sPjntORQoo0hdmXP3Y+5Ob4NqL2MNbCEf4uMOZ6OtsK4yCTfzVCMQve 1078 pQeoosXle1nOjMrRotk6/QzbKh5rMApEjaNKekwcb47Qa8pnw2RFYZT9wsF0a7wd 1079 wqINbgN2rnQ6Aea4If7O6Nh1Ftkrk2BW7g8N3OVwryQTFIKQ1W6Pgq6SeqoOVy0m 1080 S6funfA+kqwEK+iXu2afvewDQkWY8+e5WkQc/5aGkbJ3K7pX8mnTgLdwGe8lZa+M 1081 3VClT/XiHxU0ka8meio/4tq/SQUqlxiNWzgw7TJZfD1if0lAStEMAcBzZ96EQrre 1082 HaXavAO1UPl2BczwaU2O2MDcXSE21Jw1cDCas2jc90X/iBEBM50xXTwAtNmJ84mg 1083 UGNmDMvj8tUYI7+SvffHrTBwBPvcGeXa7XP4Au+GoJUN0jHspCeik/04KwanRCmu 1084 -----END RSA PRIVATE KEY-----`, 1085 })) 1086 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1087 app.SetStatus(minimalStatusArgs()) 1088 u := app.AddUnit(minimalUnitArgs(app.Type())) 1089 u.SetAgentStatus(minimalStatusArgs()) 1090 1091 _ = app.AddOffer(description.ApplicationOfferArgs{ 1092 OfferName: "my-offer", 1093 Endpoints: map[string]string{ 1094 "endpoint-1": "endpoint-1", 1095 "endpoint-2": "endpoint-2", 1096 }, 1097 ACL: map[string]string{ 1098 "admin": "admin", 1099 "foo": "--- {}", 1100 "ssl_ca": `-----BEGIN RSA PRIVATE KEY----- 1101 Proc-Type: 4,ENCRYPTED 1102 DEK-Info: DES-EDE3-CBC,EA657EE6FE842AD0 1103 1104 B8UX3J49NUiCPo0y/fhUJWdSzKjWicY13BJrsX5cv20gVjPLkkSo54BvUwhn8QgJ 1105 D0n4TJ4TcpUtQCDhbXZNiNPNQOoahRi7AkViDSoGxZ/6HKaJR7NqDHaiikgQFcb5 1106 6dMG8C30VjuTrbS4y5O/LqmYV9Gj6bkxuHGgxJaDN2og2RJCRJb16Ubl0HR2S129 1107 dZA0UyQTtDkUwlhVBaQW0Rp0nwVTFZo8g5PXpvT+MsdGgs5Pop+lYq4gSCpuw3vV 1108 T2UBOEQyYcok//9J3N+GRgfQbTidbgs8uGFGT4L8b8Ybji7Dhm/SkKpgVdH2wMuD 1109 yxKSfIt/Gm7j1I2DnZtLrd+zUSJql2UiYMeQ7suHBHBKtFriv01zFeCzChQ17uiJ 1110 NUo5IBzc+fagnN6aNaA5ImR07PHwMu4es4ORAEB+q/N3/0Eoe1U9h+ZBd6a7DM5V 1111 5A/wizu2jM25tfTNwQfaSkYCZzpGmucVwMj7NO5Rbjw2/PCOo5IP8GNahXF/cxPQ 1112 KqbIyT9nMPvKX8o9MR3Adzv5to5s9cPSyKSFfSZhJv+oXRdr8z9WaElIBJ0K0um/ 1113 ueteAX4s+U2F1Y7FAG1ivUnCOrzezvivuiPq7JS2PJh/jbdyu30xAu2gG/LZRWx+ 1114 m9zwCMUSxdrsBefl05WTzTjlpclwdjoG1sztRZ1d3/PoaMtjfLGePyEPbPFpPraq 1115 //Dj/MNKy9sefqX4NcqRtMvGj/0VuFspXsl3B1fHRrM7BsB8IM0+BheS22+dXfOK 1116 0EicO5ePw5Ji2vptC3ME+X3OztzJtfG/FN2CNvnLIKaR/N0v9Qt66U6aNoKPWD8l 1117 2PW+b/BV0dz0YTo16sd6txcmcaUIo5ysZrCWLYGsoRLL1uNAlgRT8Qb2jO8liEhg 1118 rfg5GH6RWpUL4mPo6cZ6GFAVPcdbPPSPMUSGscixeRo7oUqhnZrhvJ7L+I3TF7Pp 1119 +aWbBpRkTvYiB3/BkgMUfZzozAv6WB2MQttHqjo7cQYgT9MklCJ80a92BkVtQsRa 1120 hgDd6BUMA3ag4j0LivJflEmLd7prFVAcfyuN13UFku49soESUVnbPaWogyrGtc5w 1121 CvbTazcsKiDEJqGuZhfT0ixCNPrgLj5dNJrWOmFdpZRqa/tgShkf91qCjtMq7AEi 1122 eRQ/yOFb0sPjntORQoo0hdmXP3Y+5Ob4NqL2MNbCEf4uMOZ6OtsK4yCTfzVCMQve 1123 pQeoosXle1nOjMrRotk6/QzbKh5rMApEjaNKekwcb47Qa8pnw2RFYZT9wsF0a7wd 1124 wqINbgN2rnQ6Aea4If7O6Nh1Ftkrk2BW7g8N3OVwryQTFIKQ1W6Pgq6SeqoOVy0m 1125 S6funfA+kqwEK+iXu2afvewDQkWY8+e5WkQc/5aGkbJ3K7pX8mnTgLdwGe8lZa+M 1126 3VClT/XiHxU0ka8meio/4tq/SQUqlxiNWzgw7TJZfD1if0lAStEMAcBzZ96EQrre 1127 HaXavAO1UPl2BczwaU2O2MDcXSE21Jw1cDCas2jc90X/iBEBM50xXTwAtNmJ84mg 1128 UGNmDMvj8tUYI7+SvffHrTBwBPvcGeXa7XP4Au+GoJUN0jHspCeik/04KwanRCmu 1129 -----END RSA PRIVATE KEY-----`, 1130 }, 1131 }) 1132 1133 _ = app.AddOffer(description.ApplicationOfferArgs{ 1134 OfferName: "my-other-offer", 1135 Endpoints: map[string]string{ 1136 "endpoint-1": "endpoint-1", 1137 "endpoint-2": "endpoint-2", 1138 }, 1139 }) 1140 1141 // Add second app without an offer 1142 app2Args := s.minimalApplicationArgs(description.IAAS) 1143 app2Args.Tag = names.NewApplicationTag("foo") 1144 app2 := s.st.model.AddApplication(app2Args) 1145 app2.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1146 app2.SetStatus(minimalStatusArgs()) 1147 1148 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 1149 1150 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1151 c.Assert(err, jc.ErrorIsNil) 1152 expectedResult := params.StringResult{Result: ` 1153 default-base: ubuntu@20.04/stable 1154 applications: 1155 foo: 1156 charm: ubuntu 1157 channel: stable 1158 options: 1159 key: value 1160 bindings: 1161 another: alpha 1162 juju-info: vlan2 1163 ubuntu: 1164 charm: ubuntu 1165 channel: stable 1166 num_units: 1 1167 to: 1168 - "0" 1169 options: 1170 key: value 1171 ssl_ca: |- 1172 -----BEGIN RSA PRIVATE KEY----- 1173 Proc-Type: 4,ENCRYPTED 1174 DEK-Info: DES-EDE3-CBC,EA657EE6FE842AD0 1175 1176 B8UX3J49NUiCPo0y/fhUJWdSzKjWicY13BJrsX5cv20gVjPLkkSo54BvUwhn8QgJ 1177 D0n4TJ4TcpUtQCDhbXZNiNPNQOoahRi7AkViDSoGxZ/6HKaJR7NqDHaiikgQFcb5 1178 6dMG8C30VjuTrbS4y5O/LqmYV9Gj6bkxuHGgxJaDN2og2RJCRJb16Ubl0HR2S129 1179 dZA0UyQTtDkUwlhVBaQW0Rp0nwVTFZo8g5PXpvT+MsdGgs5Pop+lYq4gSCpuw3vV 1180 T2UBOEQyYcok//9J3N+GRgfQbTidbgs8uGFGT4L8b8Ybji7Dhm/SkKpgVdH2wMuD 1181 yxKSfIt/Gm7j1I2DnZtLrd+zUSJql2UiYMeQ7suHBHBKtFriv01zFeCzChQ17uiJ 1182 NUo5IBzc+fagnN6aNaA5ImR07PHwMu4es4ORAEB+q/N3/0Eoe1U9h+ZBd6a7DM5V 1183 5A/wizu2jM25tfTNwQfaSkYCZzpGmucVwMj7NO5Rbjw2/PCOo5IP8GNahXF/cxPQ 1184 KqbIyT9nMPvKX8o9MR3Adzv5to5s9cPSyKSFfSZhJv+oXRdr8z9WaElIBJ0K0um/ 1185 ueteAX4s+U2F1Y7FAG1ivUnCOrzezvivuiPq7JS2PJh/jbdyu30xAu2gG/LZRWx+ 1186 m9zwCMUSxdrsBefl05WTzTjlpclwdjoG1sztRZ1d3/PoaMtjfLGePyEPbPFpPraq 1187 //Dj/MNKy9sefqX4NcqRtMvGj/0VuFspXsl3B1fHRrM7BsB8IM0+BheS22+dXfOK 1188 0EicO5ePw5Ji2vptC3ME+X3OztzJtfG/FN2CNvnLIKaR/N0v9Qt66U6aNoKPWD8l 1189 2PW+b/BV0dz0YTo16sd6txcmcaUIo5ysZrCWLYGsoRLL1uNAlgRT8Qb2jO8liEhg 1190 rfg5GH6RWpUL4mPo6cZ6GFAVPcdbPPSPMUSGscixeRo7oUqhnZrhvJ7L+I3TF7Pp 1191 +aWbBpRkTvYiB3/BkgMUfZzozAv6WB2MQttHqjo7cQYgT9MklCJ80a92BkVtQsRa 1192 hgDd6BUMA3ag4j0LivJflEmLd7prFVAcfyuN13UFku49soESUVnbPaWogyrGtc5w 1193 CvbTazcsKiDEJqGuZhfT0ixCNPrgLj5dNJrWOmFdpZRqa/tgShkf91qCjtMq7AEi 1194 eRQ/yOFb0sPjntORQoo0hdmXP3Y+5Ob4NqL2MNbCEf4uMOZ6OtsK4yCTfzVCMQve 1195 pQeoosXle1nOjMrRotk6/QzbKh5rMApEjaNKekwcb47Qa8pnw2RFYZT9wsF0a7wd 1196 wqINbgN2rnQ6Aea4If7O6Nh1Ftkrk2BW7g8N3OVwryQTFIKQ1W6Pgq6SeqoOVy0m 1197 S6funfA+kqwEK+iXu2afvewDQkWY8+e5WkQc/5aGkbJ3K7pX8mnTgLdwGe8lZa+M 1198 3VClT/XiHxU0ka8meio/4tq/SQUqlxiNWzgw7TJZfD1if0lAStEMAcBzZ96EQrre 1199 HaXavAO1UPl2BczwaU2O2MDcXSE21Jw1cDCas2jc90X/iBEBM50xXTwAtNmJ84mg 1200 UGNmDMvj8tUYI7+SvffHrTBwBPvcGeXa7XP4Au+GoJUN0jHspCeik/04KwanRCmu 1201 -----END RSA PRIVATE KEY----- 1202 bindings: 1203 another: alpha 1204 juju-info: vlan2 1205 --- # overlay.yaml 1206 applications: 1207 ubuntu: 1208 offers: 1209 my-offer: 1210 endpoints: 1211 - endpoint-1 1212 - endpoint-2 1213 acl: 1214 admin: admin 1215 foo: '--- {}' 1216 ssl_ca: |- 1217 -----BEGIN RSA PRIVATE KEY----- 1218 Proc-Type: 4,ENCRYPTED 1219 DEK-Info: DES-EDE3-CBC,EA657EE6FE842AD0 1220 1221 B8UX3J49NUiCPo0y/fhUJWdSzKjWicY13BJrsX5cv20gVjPLkkSo54BvUwhn8QgJ 1222 D0n4TJ4TcpUtQCDhbXZNiNPNQOoahRi7AkViDSoGxZ/6HKaJR7NqDHaiikgQFcb5 1223 6dMG8C30VjuTrbS4y5O/LqmYV9Gj6bkxuHGgxJaDN2og2RJCRJb16Ubl0HR2S129 1224 dZA0UyQTtDkUwlhVBaQW0Rp0nwVTFZo8g5PXpvT+MsdGgs5Pop+lYq4gSCpuw3vV 1225 T2UBOEQyYcok//9J3N+GRgfQbTidbgs8uGFGT4L8b8Ybji7Dhm/SkKpgVdH2wMuD 1226 yxKSfIt/Gm7j1I2DnZtLrd+zUSJql2UiYMeQ7suHBHBKtFriv01zFeCzChQ17uiJ 1227 NUo5IBzc+fagnN6aNaA5ImR07PHwMu4es4ORAEB+q/N3/0Eoe1U9h+ZBd6a7DM5V 1228 5A/wizu2jM25tfTNwQfaSkYCZzpGmucVwMj7NO5Rbjw2/PCOo5IP8GNahXF/cxPQ 1229 KqbIyT9nMPvKX8o9MR3Adzv5to5s9cPSyKSFfSZhJv+oXRdr8z9WaElIBJ0K0um/ 1230 ueteAX4s+U2F1Y7FAG1ivUnCOrzezvivuiPq7JS2PJh/jbdyu30xAu2gG/LZRWx+ 1231 m9zwCMUSxdrsBefl05WTzTjlpclwdjoG1sztRZ1d3/PoaMtjfLGePyEPbPFpPraq 1232 //Dj/MNKy9sefqX4NcqRtMvGj/0VuFspXsl3B1fHRrM7BsB8IM0+BheS22+dXfOK 1233 0EicO5ePw5Ji2vptC3ME+X3OztzJtfG/FN2CNvnLIKaR/N0v9Qt66U6aNoKPWD8l 1234 2PW+b/BV0dz0YTo16sd6txcmcaUIo5ysZrCWLYGsoRLL1uNAlgRT8Qb2jO8liEhg 1235 rfg5GH6RWpUL4mPo6cZ6GFAVPcdbPPSPMUSGscixeRo7oUqhnZrhvJ7L+I3TF7Pp 1236 +aWbBpRkTvYiB3/BkgMUfZzozAv6WB2MQttHqjo7cQYgT9MklCJ80a92BkVtQsRa 1237 hgDd6BUMA3ag4j0LivJflEmLd7prFVAcfyuN13UFku49soESUVnbPaWogyrGtc5w 1238 CvbTazcsKiDEJqGuZhfT0ixCNPrgLj5dNJrWOmFdpZRqa/tgShkf91qCjtMq7AEi 1239 eRQ/yOFb0sPjntORQoo0hdmXP3Y+5Ob4NqL2MNbCEf4uMOZ6OtsK4yCTfzVCMQve 1240 pQeoosXle1nOjMrRotk6/QzbKh5rMApEjaNKekwcb47Qa8pnw2RFYZT9wsF0a7wd 1241 wqINbgN2rnQ6Aea4If7O6Nh1Ftkrk2BW7g8N3OVwryQTFIKQ1W6Pgq6SeqoOVy0m 1242 S6funfA+kqwEK+iXu2afvewDQkWY8+e5WkQc/5aGkbJ3K7pX8mnTgLdwGe8lZa+M 1243 3VClT/XiHxU0ka8meio/4tq/SQUqlxiNWzgw7TJZfD1if0lAStEMAcBzZ96EQrre 1244 HaXavAO1UPl2BczwaU2O2MDcXSE21Jw1cDCas2jc90X/iBEBM50xXTwAtNmJ84mg 1245 UGNmDMvj8tUYI7+SvffHrTBwBPvcGeXa7XP4Au+GoJUN0jHspCeik/04KwanRCmu 1246 -----END RSA PRIVATE KEY----- 1247 my-other-offer: 1248 endpoints: 1249 - endpoint-1 1250 - endpoint-2 1251 `[1:]} 1252 1253 c.Assert(result, gc.Equals, expectedResult) 1254 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1255 } 1256 1257 func (s *bundleSuite) TestExportBundleWithSaas(c *gc.C) { 1258 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 1259 Config: coretesting.FakeConfig(), 1260 CloudRegion: "some-region"}) 1261 1262 app := s.st.model.AddApplication(s.minimalApplicationArgs(description.IAAS)) 1263 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1264 app.SetStatus(minimalStatusArgs()) 1265 1266 remoteApp := s.st.model.AddRemoteApplication(description.RemoteApplicationArgs{ 1267 Tag: names.NewApplicationTag("awesome"), 1268 URL: "test:admin/default.awesome", 1269 }) 1270 remoteApp.SetStatus(minimalStatusArgs()) 1271 1272 u := app.AddUnit(minimalUnitArgs(app.Type())) 1273 u.SetAgentStatus(minimalStatusArgs()) 1274 1275 s.st.model.SetStatus(description.StatusArgs{Value: "available"}) 1276 1277 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1278 c.Assert(err, jc.ErrorIsNil) 1279 expectedResult := params.StringResult{Result: ` 1280 default-base: ubuntu@20.04/stable 1281 saas: 1282 awesome: 1283 url: test:admin/default.awesome 1284 applications: 1285 ubuntu: 1286 charm: ubuntu 1287 channel: stable 1288 num_units: 1 1289 to: 1290 - "0" 1291 options: 1292 key: value 1293 bindings: 1294 another: alpha 1295 juju-info: vlan2 1296 `[1:]} 1297 1298 c.Assert(result, gc.Equals, expectedResult) 1299 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1300 } 1301 1302 func (s *bundleSuite) addApplicationToModel(model description.Model, name string, numUnits int) string { 1303 var charmURL string 1304 var channel string 1305 1306 if strings.HasPrefix(name, "ch:") { 1307 charmURL = name 1308 name = name[3:] 1309 channel = "stable" 1310 } else if strings.HasPrefix(name, "local:") { 1311 charmURL = name 1312 curl := charm.MustParseURL(name) 1313 name = curl.Name 1314 } else { 1315 charmURL = "ch:" + name 1316 } 1317 application := model.AddApplication(description.ApplicationArgs{ 1318 Tag: names.NewApplicationTag(name), 1319 CharmURL: charmURL, 1320 Channel: channel, 1321 CharmConfig: map[string]interface{}{}, 1322 LeadershipSettings: map[string]interface{}{}, 1323 }) 1324 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1325 application.SetStatus(minimalStatusArgs()) 1326 for i := 0; i < numUnits; i++ { 1327 machine := model.AddMachine(description.MachineArgs{ 1328 Id: names.NewMachineTag(fmt.Sprint(i)), 1329 Base: "ubuntu@20.04", 1330 }) 1331 unit := application.AddUnit(description.UnitArgs{ 1332 Tag: names.NewUnitTag(fmt.Sprintf("%s/%d", name, i)), 1333 Machine: machine.Tag(), 1334 }) 1335 unit.SetAgentStatus(minimalStatusArgs()) 1336 } 1337 1338 return name 1339 } 1340 1341 func (s *bundleSuite) setEndpointSettings(ep description.Endpoint, units ...string) { 1342 for _, unit := range units { 1343 ep.SetUnitSettings(unit, map[string]interface{}{ 1344 "key": "value", 1345 }) 1346 } 1347 } 1348 1349 func (s *bundleSuite) newModel(modelType string, app1 string, app2 string) description.Model { 1350 s.st.model = description.NewModel(description.ModelArgs{ 1351 Type: modelType, 1352 Owner: names.NewUserTag("magic"), 1353 Config: coretesting.FakeConfig(), 1354 CloudRegion: "some-region"}) 1355 1356 appName1 := s.addApplicationToModel(s.st.model, app1, 2) 1357 appName2 := s.addApplicationToModel(s.st.model, app2, 1) 1358 1359 // Add a relation between wordpress and mysql. 1360 rel := s.st.model.AddRelation(description.RelationArgs{ 1361 Id: 42, 1362 Key: "special key", 1363 }) 1364 rel.SetStatus(minimalStatusArgs()) 1365 1366 app1Endpoint := rel.AddEndpoint(description.EndpointArgs{ 1367 ApplicationName: appName1, 1368 Name: "db", 1369 // Ignoring other aspects of endpoints. 1370 }) 1371 s.setEndpointSettings(app1Endpoint, appName1+"/0", appName1+"/1") 1372 1373 app2Endpoint := rel.AddEndpoint(description.EndpointArgs{ 1374 ApplicationName: "mysql", 1375 Name: "mysql", 1376 // Ignoring other aspects of endpoints. 1377 }) 1378 s.setEndpointSettings(app2Endpoint, appName2+"/0") 1379 1380 return s.st.model 1381 } 1382 1383 func (s *bundleSuite) TestExportBundleModelWithSettingsRelations(c *gc.C) { 1384 model := s.newModel("iaas", "wordpress", "mysql") 1385 model.SetStatus(description.StatusArgs{Value: "available"}) 1386 1387 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1388 c.Assert(err, jc.ErrorIsNil) 1389 1390 output := ` 1391 default-base: ubuntu@20.04/stable 1392 applications: 1393 mysql: 1394 charm: mysql 1395 num_units: 1 1396 to: 1397 - "0" 1398 wordpress: 1399 charm: wordpress 1400 num_units: 2 1401 to: 1402 - "0" 1403 - "1" 1404 machines: 1405 "0": {} 1406 "1": {} 1407 relations: 1408 - - wordpress:db 1409 - mysql:mysql 1410 `[1:] 1411 expectedResult := params.StringResult{Result: output} 1412 1413 c.Assert(result, gc.Equals, expectedResult) 1414 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1415 } 1416 1417 func (s *bundleSuite) TestExportBundleModelWithCharmDefaults(c *gc.C) { 1418 model := s.newModel("iaas", "wordpress", "mysql") 1419 model.SetStatus(description.StatusArgs{Value: "available"}) 1420 app := model.AddApplication(description.ApplicationArgs{ 1421 Tag: names.NewApplicationTag("mariadb"), 1422 CharmURL: "ch:mariadb", 1423 CharmConfig: map[string]interface{}{ 1424 "mem": 200, 1425 "foo": "baz", 1426 }, 1427 }) 1428 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1429 1430 result, err := s.facade.ExportBundle(params.ExportBundleParams{IncludeCharmDefaults: true}) 1431 c.Assert(err, jc.ErrorIsNil) 1432 1433 output := ` 1434 default-base: ubuntu@20.04/stable 1435 applications: 1436 mariadb: 1437 charm: mariadb 1438 options: 1439 foo: baz 1440 mem: 200 1441 mysql: 1442 charm: mysql 1443 num_units: 1 1444 to: 1445 - "0" 1446 options: 1447 foo: bar 1448 wordpress: 1449 charm: wordpress 1450 num_units: 2 1451 to: 1452 - "0" 1453 - "1" 1454 options: 1455 foo: bar 1456 machines: 1457 "0": {} 1458 "1": {} 1459 relations: 1460 - - wordpress:db 1461 - mysql:mysql 1462 `[1:] 1463 expectedResult := params.StringResult{Result: output} 1464 1465 c.Assert(result, gc.Equals, expectedResult) 1466 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1467 } 1468 1469 func (s *bundleSuite) TestExportBundleModelWithIncludeSeries(c *gc.C) { 1470 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 1471 Config: coretesting.FakeConfig().Merge(map[string]interface{}{ 1472 "default-base": "ubuntu@20.04", 1473 }), 1474 CloudRegion: "some-region"}) 1475 1476 application := s.st.model.AddApplication(description.ApplicationArgs{ 1477 Tag: names.NewApplicationTag("magic"), 1478 CharmURL: "ch:magic", 1479 }) 1480 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1481 application.AddUnit(description.UnitArgs{ 1482 Tag: names.NewUnitTag("magic/0"), 1483 Machine: names.NewMachineTag("0"), 1484 }) 1485 s.st.model.AddMachine(description.MachineArgs{ 1486 Id: names.NewMachineTag("0"), 1487 Base: "ubuntu@20.04", 1488 }) 1489 1490 application = s.st.model.AddApplication(description.ApplicationArgs{ 1491 Tag: names.NewApplicationTag("mojo"), 1492 CharmURL: "ch:mojo", 1493 }) 1494 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/22.04/stable"}) 1495 application.AddUnit(description.UnitArgs{ 1496 Tag: names.NewUnitTag("mojo/0"), 1497 Machine: names.NewMachineTag("1"), 1498 }) 1499 s.st.model.AddMachine(description.MachineArgs{ 1500 Id: names.NewMachineTag("1"), 1501 Base: "ubuntu@22.04", 1502 }) 1503 1504 result, err := s.facade.ExportBundle(params.ExportBundleParams{IncludeSeries: true}) 1505 c.Assert(err, jc.ErrorIsNil) 1506 1507 expectedResult := params.StringResult{Result: ` 1508 default-base: ubuntu@20.04/stable 1509 series: focal 1510 applications: 1511 magic: 1512 charm: magic 1513 num_units: 1 1514 to: 1515 - "0" 1516 mojo: 1517 charm: mojo 1518 series: jammy 1519 base: ubuntu@22.04/stable 1520 num_units: 1 1521 to: 1522 - "1" 1523 machines: 1524 "0": {} 1525 "1": 1526 series: jammy 1527 base: ubuntu@22.04/stable 1528 `[1:]} 1529 1530 c.Assert(result, gc.Equals, expectedResult) 1531 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1532 } 1533 1534 func (s *bundleSuite) addSubordinateEndpoints(rel description.Relation, app string) (description.Endpoint, description.Endpoint) { 1535 appEndpoint := rel.AddEndpoint(description.EndpointArgs{ 1536 ApplicationName: app, 1537 Name: "logging", 1538 Scope: "container", 1539 // Ignoring other aspects of endpoints. 1540 }) 1541 loggingEndpoint := rel.AddEndpoint(description.EndpointArgs{ 1542 ApplicationName: "logging", 1543 Name: "logging", 1544 Scope: "container", 1545 // Ignoring other aspects of endpoints. 1546 }) 1547 return appEndpoint, loggingEndpoint 1548 } 1549 1550 func (s *bundleSuite) TestExportBundleModelRelationsWithSubordinates(c *gc.C) { 1551 model := s.newModel("iaas", "wordpress", "mysql") 1552 model.SetStatus(description.StatusArgs{Value: "available"}) 1553 1554 // Add a subordinate relations between logging and both wordpress and mysql. 1555 rel := model.AddRelation(description.RelationArgs{ 1556 Id: 43, 1557 Key: "some key", 1558 }) 1559 wordpressEndpoint, loggingEndpoint := s.addSubordinateEndpoints(rel, "wordpress") 1560 s.setEndpointSettings(wordpressEndpoint, "wordpress/0", "wordpress/1") 1561 s.setEndpointSettings(loggingEndpoint, "logging/0", "logging/1") 1562 1563 rel = model.AddRelation(description.RelationArgs{ 1564 Id: 44, 1565 Key: "other key", 1566 }) 1567 mysqlEndpoint, loggingEndpoint := s.addSubordinateEndpoints(rel, "mysql") 1568 s.setEndpointSettings(mysqlEndpoint, "mysql/0") 1569 s.setEndpointSettings(loggingEndpoint, "logging/2") 1570 1571 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1572 c.Assert(err, jc.ErrorIsNil) 1573 1574 expectedResult := params.StringResult{Result: ` 1575 default-base: ubuntu@20.04/stable 1576 applications: 1577 mysql: 1578 charm: mysql 1579 num_units: 1 1580 to: 1581 - "0" 1582 wordpress: 1583 charm: wordpress 1584 num_units: 2 1585 to: 1586 - "0" 1587 - "1" 1588 machines: 1589 "0": {} 1590 "1": {} 1591 relations: 1592 - - wordpress:db 1593 - mysql:mysql 1594 - - wordpress:logging 1595 - logging:logging 1596 - - mysql:logging 1597 - logging:logging 1598 `[1:]} 1599 1600 c.Assert(result, gc.Equals, expectedResult) 1601 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1602 } 1603 1604 func (s *bundleSuite) TestExportBundleSubordinateApplication(c *gc.C) { 1605 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 1606 Config: coretesting.FakeConfig(), 1607 CloudRegion: "some-region"}) 1608 1609 s.st.Spaces[network.AlphaSpaceId] = network.AlphaSpaceName 1610 s.st.Spaces["2"] = "some-space" 1611 application := s.st.model.AddApplication(description.ApplicationArgs{ 1612 Tag: names.NewApplicationTag("magic"), 1613 Subordinate: true, 1614 CharmURL: "ch:magic", 1615 Channel: "stable", 1616 CharmModifiedVersion: 1, 1617 ForceCharm: true, 1618 Exposed: true, 1619 EndpointBindings: map[string]string{ 1620 "rel-name": "2", 1621 "magic": "0", 1622 }, 1623 ApplicationConfig: map[string]interface{}{ 1624 "config key": "config value", 1625 }, 1626 CharmConfig: map[string]interface{}{ 1627 "key": "value", 1628 }, 1629 Leader: "magic/1", 1630 LeadershipSettings: map[string]interface{}{ 1631 "leader": true, 1632 }, 1633 MetricsCredentials: []byte("sekrit"), 1634 PasswordHash: "passwordhash", 1635 PodSpec: "podspec", 1636 }) 1637 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/18.04/stable"}) 1638 application.SetStatus(minimalStatusArgs()) 1639 1640 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1641 c.Assert(err, jc.ErrorIsNil) 1642 1643 expectedResult := params.StringResult{Result: ` 1644 default-base: ubuntu@18.04/stable 1645 applications: 1646 magic: 1647 charm: magic 1648 channel: stable 1649 expose: true 1650 options: 1651 key: value 1652 bindings: 1653 magic: alpha 1654 rel-name: some-space 1655 `[1:]} 1656 1657 c.Assert(result, gc.Equals, expectedResult) 1658 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1659 } 1660 1661 func (s *bundleSuite) setupExportBundleEndpointBindingsPrinted(all, oneOff string) { 1662 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 1663 Config: coretesting.FakeConfig(), 1664 CloudRegion: "some-region"}) 1665 1666 args := s.minimalApplicationArgs(description.IAAS) 1667 args.EndpointBindings = map[string]string{ 1668 "rel-name": all, 1669 "another": all, 1670 } 1671 app := s.st.model.AddApplication(args) 1672 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1673 1674 app = s.st.model.AddApplication(description.ApplicationArgs{ 1675 Tag: names.NewApplicationTag("magic"), 1676 Subordinate: true, 1677 CharmURL: "ch:magic", 1678 Channel: "stable", 1679 CharmModifiedVersion: 1, 1680 ForceCharm: true, 1681 Exposed: true, 1682 EndpointBindings: map[string]string{ 1683 "rel-name": all, 1684 "another": oneOff, 1685 }, 1686 ApplicationConfig: map[string]interface{}{ 1687 "config key": "config value", 1688 }, 1689 }) 1690 app.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/18.04/stable"}) 1691 } 1692 1693 func (s *bundleSuite) TestExportBundleNoEndpointBindingsPrinted(c *gc.C) { 1694 s.setupExportBundleEndpointBindingsPrinted("0", "0") 1695 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1696 c.Assert(err, jc.ErrorIsNil) 1697 1698 expectedResult := params.StringResult{Result: ` 1699 applications: 1700 magic: 1701 charm: magic 1702 channel: stable 1703 base: ubuntu@18.04/stable 1704 expose: true 1705 ubuntu: 1706 charm: ubuntu 1707 channel: stable 1708 base: ubuntu@20.04/stable 1709 options: 1710 key: value 1711 `[1:]} 1712 c.Assert(result, gc.Equals, expectedResult) 1713 } 1714 1715 func (s *bundleSuite) TestExportBundleEndpointBindingsPrinted(c *gc.C) { 1716 s.setupExportBundleEndpointBindingsPrinted("0", "1") 1717 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1718 c.Assert(err, jc.ErrorIsNil) 1719 1720 expectedResult := params.StringResult{Result: ` 1721 applications: 1722 magic: 1723 charm: magic 1724 channel: stable 1725 base: ubuntu@18.04/stable 1726 expose: true 1727 bindings: 1728 another: vlan2 1729 rel-name: alpha 1730 ubuntu: 1731 charm: ubuntu 1732 channel: stable 1733 base: ubuntu@20.04/stable 1734 options: 1735 key: value 1736 bindings: 1737 another: alpha 1738 rel-name: alpha 1739 `[1:]} 1740 c.Assert(result, gc.Equals, expectedResult) 1741 } 1742 1743 func (s *bundleSuite) TestExportBundleSubordinateApplicationAndMachine(c *gc.C) { 1744 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 1745 Config: coretesting.FakeConfig(), 1746 CloudRegion: "some-region"}) 1747 1748 application := s.st.model.AddApplication(description.ApplicationArgs{ 1749 Tag: names.NewApplicationTag("magic"), 1750 Subordinate: true, 1751 CharmURL: "ch:amd64/zesty/magic", 1752 Channel: "stable", 1753 Exposed: true, 1754 CharmConfig: map[string]interface{}{ 1755 "key": "value", 1756 }, 1757 }) 1758 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/17.04/stable"}) 1759 application.SetStatus(minimalStatusArgs()) 1760 1761 s.addMinimalMachineWithConstraints(s.st.model, "0") 1762 1763 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1764 c.Assert(err, jc.ErrorIsNil) 1765 1766 expectedResult := params.StringResult{Result: ` 1767 default-base: ubuntu@17.04/stable 1768 applications: 1769 magic: 1770 charm: magic 1771 channel: stable 1772 expose: true 1773 options: 1774 key: value 1775 `[1:]} 1776 1777 c.Assert(result, gc.Equals, expectedResult) 1778 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1779 } 1780 1781 func (s *bundleSuite) addMinimalMachineWithConstraints(model description.Model, id string) { 1782 m := model.AddMachine(description.MachineArgs{ 1783 Id: names.NewMachineTag(id), 1784 Nonce: "a-nonce", 1785 PasswordHash: "some-hash", 1786 Base: "ubuntu@20.04", 1787 Jobs: []string{"host-units"}, 1788 }) 1789 args := description.ConstraintsArgs{ 1790 Architecture: "amd64", 1791 Memory: 8 * 1024, 1792 RootDisk: 40 * 1024, 1793 CpuCores: 2, 1794 CpuPower: 100, 1795 InstanceType: "big-inst", 1796 Tags: []string{"foo", "bar"}, 1797 VirtType: "pv", 1798 Container: "kvm", 1799 Spaces: []string{"internal"}, 1800 Zones: []string{"zone-a"}, 1801 RootDiskSource: "source-of-all-evil", 1802 } 1803 m.SetConstraints(args) 1804 m.SetStatus(minimalStatusArgs()) 1805 } 1806 1807 func (s *bundleSuite) TestExportBundleModelWithConstraints(c *gc.C) { 1808 model := s.newModel("iaas", "mediawiki", "mysql") 1809 1810 s.addMinimalMachineWithConstraints(model, "0") 1811 s.addMinimalMachineWithConstraints(model, "1") 1812 1813 model.SetStatus(description.StatusArgs{Value: "available"}) 1814 1815 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1816 c.Assert(err, jc.ErrorIsNil) 1817 expectedResult := params.StringResult{Result: ` 1818 default-base: ubuntu@20.04/stable 1819 applications: 1820 mediawiki: 1821 charm: mediawiki 1822 num_units: 2 1823 to: 1824 - "0" 1825 - "1" 1826 mysql: 1827 charm: mysql 1828 num_units: 1 1829 to: 1830 - "0" 1831 machines: 1832 "0": 1833 constraints: arch=amd64 cpu-cores=2 cpu-power=100 mem=8192 root-disk=40960 instance-type=big-inst 1834 container=kvm virt-type=pv tags=foo,bar spaces=internal zones=zone-a root-disk-source=source-of-all-evil 1835 "1": 1836 constraints: arch=amd64 cpu-cores=2 cpu-power=100 mem=8192 root-disk=40960 instance-type=big-inst 1837 container=kvm virt-type=pv tags=foo,bar spaces=internal zones=zone-a root-disk-source=source-of-all-evil 1838 relations: 1839 - - mediawiki:db 1840 - mysql:mysql 1841 `[1:]} 1842 1843 c.Assert(result, gc.Equals, expectedResult) 1844 1845 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1846 } 1847 1848 func (s *bundleSuite) addMinimalMachineWithAnnotations(model description.Model, id string) { 1849 m := model.AddMachine(description.MachineArgs{ 1850 Id: names.NewMachineTag(id), 1851 Nonce: "a-nonce", 1852 PasswordHash: "some-hash", 1853 Base: "ubuntu@20.04", 1854 Jobs: []string{"host-units"}, 1855 }) 1856 m.SetAnnotations(map[string]string{ 1857 "string": "value", 1858 "another": "one", 1859 }) 1860 m.SetStatus(minimalStatusArgs()) 1861 } 1862 1863 func (s *bundleSuite) TestExportBundleModelWithAnnotations(c *gc.C) { 1864 model := s.newModel("iaas", "wordpress", "mysql") 1865 1866 s.addMinimalMachineWithAnnotations(model, "0") 1867 s.addMinimalMachineWithAnnotations(model, "1") 1868 1869 model.SetStatus(description.StatusArgs{Value: "available"}) 1870 1871 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1872 c.Assert(err, jc.ErrorIsNil) 1873 expectedResult := params.StringResult{Result: ` 1874 default-base: ubuntu@20.04/stable 1875 applications: 1876 mysql: 1877 charm: mysql 1878 num_units: 1 1879 to: 1880 - "0" 1881 wordpress: 1882 charm: wordpress 1883 num_units: 2 1884 to: 1885 - "0" 1886 - "1" 1887 machines: 1888 "0": 1889 annotations: 1890 another: one 1891 string: value 1892 "1": 1893 annotations: 1894 another: one 1895 string: value 1896 relations: 1897 - - wordpress:db 1898 - mysql:mysql 1899 `[1:]} 1900 1901 c.Assert(result, gc.Equals, expectedResult) 1902 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1903 } 1904 1905 func (s *bundleSuite) TestExportBundleWithContainers(c *gc.C) { 1906 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 1907 Config: coretesting.FakeConfig(), 1908 CloudRegion: "some-region"}) 1909 1910 application0 := s.st.model.AddApplication(description.ApplicationArgs{ 1911 Tag: names.NewApplicationTag("wordpress"), 1912 CharmURL: "ch:wordpress", 1913 }) 1914 application0.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1915 application0.SetStatus(minimalStatusArgs()) 1916 1917 m0 := s.st.model.AddMachine(description.MachineArgs{ 1918 Id: names.NewMachineTag("0"), 1919 Base: "ubuntu@20.04", 1920 }) 1921 args := description.ConstraintsArgs{ 1922 Architecture: "amd64", 1923 Memory: 8 * 1024, 1924 RootDisk: 40 * 1024, 1925 } 1926 m0.SetConstraints(args) 1927 ut0 := application0.AddUnit(description.UnitArgs{ 1928 Tag: names.NewUnitTag("wordpress/0"), 1929 Machine: names.NewMachineTag("0"), 1930 }) 1931 ut0.SetAgentStatus(minimalStatusArgs()) 1932 1933 application1 := s.st.model.AddApplication(description.ApplicationArgs{ 1934 Tag: names.NewApplicationTag("mysql"), 1935 CharmURL: "ch:mysql", 1936 }) 1937 application1.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1938 application1.SetStatus(minimalStatusArgs()) 1939 1940 m1 := s.st.model.AddMachine(description.MachineArgs{ 1941 Id: names.NewMachineTag("1"), 1942 Base: "ubuntu@20.04", 1943 }) 1944 args = description.ConstraintsArgs{ 1945 Architecture: "amd64", 1946 Memory: 8 * 1024, 1947 RootDisk: 40 * 1024, 1948 } 1949 m1.SetConstraints(args) 1950 1951 ut := application1.AddUnit(description.UnitArgs{ 1952 Tag: names.NewUnitTag("mysql/1"), 1953 Machine: names.NewMachineTag("1/lxd/0"), 1954 }) 1955 ut.SetAgentStatus(minimalStatusArgs()) 1956 1957 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 1958 c.Assert(err, jc.ErrorIsNil) 1959 expectedResult := params.StringResult{Result: ` 1960 default-base: ubuntu@20.04/stable 1961 applications: 1962 mysql: 1963 charm: mysql 1964 num_units: 1 1965 to: 1966 - lxd:1 1967 wordpress: 1968 charm: wordpress 1969 num_units: 1 1970 to: 1971 - "0" 1972 machines: 1973 "0": 1974 constraints: arch=amd64 mem=8192 root-disk=40960 1975 "1": 1976 constraints: arch=amd64 mem=8192 root-disk=40960 1977 `[1:]} 1978 1979 c.Assert(result, gc.Equals, expectedResult) 1980 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 1981 } 1982 1983 func (s *bundleSuite) TestMixedSeries(c *gc.C) { 1984 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 1985 Config: coretesting.FakeConfig().Merge(map[string]interface{}{ 1986 "default-base": "ubuntu@20.04", 1987 }), 1988 CloudRegion: "some-region"}) 1989 1990 application := s.st.model.AddApplication(description.ApplicationArgs{ 1991 Tag: names.NewApplicationTag("magic"), 1992 CharmURL: "ch:magic", 1993 }) 1994 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 1995 application.AddUnit(description.UnitArgs{ 1996 Tag: names.NewUnitTag("magic/0"), 1997 Machine: names.NewMachineTag("0"), 1998 }) 1999 s.st.model.AddMachine(description.MachineArgs{ 2000 Id: names.NewMachineTag("0"), 2001 Base: "ubuntu@20.04", 2002 }) 2003 2004 application = s.st.model.AddApplication(description.ApplicationArgs{ 2005 Tag: names.NewApplicationTag("mojo"), 2006 CharmURL: "ch:mojo", 2007 }) 2008 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/22.04/stable"}) 2009 application.AddUnit(description.UnitArgs{ 2010 Tag: names.NewUnitTag("mojo/0"), 2011 Machine: names.NewMachineTag("1"), 2012 }) 2013 s.st.model.AddMachine(description.MachineArgs{ 2014 Id: names.NewMachineTag("1"), 2015 Base: "ubuntu@22.04", 2016 }) 2017 2018 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 2019 c.Assert(err, jc.ErrorIsNil) 2020 2021 expectedResult := params.StringResult{Result: ` 2022 default-base: ubuntu@20.04/stable 2023 applications: 2024 magic: 2025 charm: magic 2026 num_units: 1 2027 to: 2028 - "0" 2029 mojo: 2030 charm: mojo 2031 base: ubuntu@22.04/stable 2032 num_units: 1 2033 to: 2034 - "1" 2035 machines: 2036 "0": {} 2037 "1": 2038 base: ubuntu@22.04/stable 2039 `[1:]} 2040 2041 c.Assert(result, gc.Equals, expectedResult) 2042 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 2043 } 2044 2045 func (s *bundleSuite) TestMixedSeriesNoDefaultSeries(c *gc.C) { 2046 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 2047 Config: coretesting.FakeConfig().Merge(map[string]interface{}{ 2048 "default-base": "ubuntu@20.04", 2049 }), 2050 CloudRegion: "some-region"}) 2051 2052 application := s.st.model.AddApplication(description.ApplicationArgs{ 2053 Tag: names.NewApplicationTag("magic"), 2054 CharmURL: "ch:magic", 2055 }) 2056 2057 // TODO(jack-w-shaw) this test is somewhat contrived since ubuntu@21.04 is not a supported base. 2058 // However, since this test requires at least 3 different bases, we need to do this until 24.04 2059 // is supported 2060 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/21.04/stable"}) 2061 application.AddUnit(description.UnitArgs{ 2062 Tag: names.NewUnitTag("magic/0"), 2063 Machine: names.NewMachineTag("0"), 2064 }) 2065 s.st.model.AddMachine(description.MachineArgs{ 2066 Id: names.NewMachineTag("0"), 2067 Base: "ubuntu@21.04", 2068 }) 2069 2070 application = s.st.model.AddApplication(description.ApplicationArgs{ 2071 Tag: names.NewApplicationTag("mojo"), 2072 CharmURL: "ch:mojo", 2073 }) 2074 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/22.04/stable"}) 2075 application.AddUnit(description.UnitArgs{ 2076 Tag: names.NewUnitTag("mojo/0"), 2077 Machine: names.NewMachineTag("1"), 2078 }) 2079 s.st.model.AddMachine(description.MachineArgs{ 2080 Id: names.NewMachineTag("1"), 2081 Base: "ubuntu@22.04", 2082 }) 2083 2084 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 2085 c.Assert(err, jc.ErrorIsNil) 2086 2087 expectedResult := params.StringResult{Result: ` 2088 applications: 2089 magic: 2090 charm: magic 2091 base: ubuntu@21.04/stable 2092 num_units: 1 2093 to: 2094 - "0" 2095 mojo: 2096 charm: mojo 2097 base: ubuntu@22.04/stable 2098 num_units: 1 2099 to: 2100 - "1" 2101 machines: 2102 "0": 2103 base: ubuntu@21.04/stable 2104 "1": 2105 base: ubuntu@22.04/stable 2106 `[1:]} 2107 2108 c.Assert(result, gc.Equals, expectedResult) 2109 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 2110 } 2111 2112 func (s *bundleSuite) TestExportKubernetesBundle(c *gc.C) { 2113 model := s.newModel("caas", "wordpress", "mysql") 2114 model.SetStatus(description.StatusArgs{Value: "available"}) 2115 2116 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 2117 c.Assert(err, jc.ErrorIsNil) 2118 2119 output := ` 2120 bundle: kubernetes 2121 applications: 2122 mysql: 2123 charm: mysql 2124 scale: 1 2125 wordpress: 2126 charm: wordpress 2127 scale: 2 2128 relations: 2129 - - wordpress:db 2130 - mysql:mysql 2131 `[1:] 2132 expectedResult := params.StringResult{Result: output} 2133 2134 c.Assert(result, gc.Equals, expectedResult) 2135 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 2136 } 2137 2138 func (s *bundleSuite) TestExportCharmhubBundle(c *gc.C) { 2139 model := s.newModel("iaas", "ch:wordpress", "ch:mysql") 2140 model.SetStatus(description.StatusArgs{Value: "available"}) 2141 2142 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 2143 c.Assert(err, jc.ErrorIsNil) 2144 2145 output := ` 2146 default-base: ubuntu@20.04/stable 2147 applications: 2148 mysql: 2149 charm: mysql 2150 channel: stable 2151 num_units: 1 2152 to: 2153 - "0" 2154 wordpress: 2155 charm: wordpress 2156 channel: stable 2157 num_units: 2 2158 to: 2159 - "0" 2160 - "1" 2161 machines: 2162 "0": {} 2163 "1": {} 2164 relations: 2165 - - wordpress:db 2166 - mysql:mysql 2167 `[1:] 2168 expectedResult := params.StringResult{Result: output} 2169 2170 c.Assert(result, gc.Equals, expectedResult) 2171 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 2172 } 2173 2174 func (s *bundleSuite) TestExportLocalBundle(c *gc.C) { 2175 model := s.newModel("iaas", "local:wordpress", "local:mysql") 2176 model.SetStatus(description.StatusArgs{Value: "available"}) 2177 2178 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 2179 c.Assert(err, jc.ErrorIsNil) 2180 2181 output := ` 2182 default-base: ubuntu@20.04/stable 2183 applications: 2184 mysql: 2185 charm: local:mysql 2186 num_units: 1 2187 to: 2188 - "0" 2189 wordpress: 2190 charm: local:wordpress 2191 num_units: 2 2192 to: 2193 - "0" 2194 - "1" 2195 machines: 2196 "0": {} 2197 "1": {} 2198 relations: 2199 - - wordpress:db 2200 - mysql:mysql 2201 `[1:] 2202 expectedResult := params.StringResult{Result: output} 2203 2204 c.Assert(result, gc.Equals, expectedResult) 2205 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 2206 } 2207 2208 func (s *bundleSuite) TestExportLocalBundleWithSeries(c *gc.C) { 2209 model := s.newModel("iaas", "local:focal/wordpress", "local:mysql") 2210 model.SetStatus(description.StatusArgs{Value: "available"}) 2211 2212 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 2213 c.Assert(err, jc.ErrorIsNil) 2214 2215 output := ` 2216 default-base: ubuntu@20.04/stable 2217 applications: 2218 mysql: 2219 charm: local:mysql 2220 num_units: 1 2221 to: 2222 - "0" 2223 wordpress: 2224 charm: local:wordpress 2225 num_units: 2 2226 to: 2227 - "0" 2228 - "1" 2229 machines: 2230 "0": {} 2231 "1": {} 2232 relations: 2233 - - wordpress:db 2234 - mysql:mysql 2235 `[1:] 2236 expectedResult := params.StringResult{Result: output} 2237 2238 c.Assert(result, gc.Equals, expectedResult) 2239 s.st.CheckCall(c, 0, "ExportPartial", s.st.GetExportConfig()) 2240 } 2241 2242 func (s *bundleSuite) TestExportBundleWithExposedEndpointSettings(c *gc.C) { 2243 specs := []struct { 2244 descr string 2245 exposed bool 2246 exposedEndpoints map[string]description.ExposedEndpointArgs 2247 expBundle string 2248 }{ 2249 { 2250 descr: "exposed application without exposed endpoint settings (upgraded 2.8 controller)", 2251 exposed: true, 2252 expBundle: ` 2253 default-base: ubuntu@20.04/stable 2254 applications: 2255 magic: 2256 charm: magic 2257 channel: stable 2258 expose: true 2259 bindings: 2260 hat: some-space 2261 rabbit: alpha 2262 `[1:], 2263 }, 2264 { 2265 descr: "exposed application where all endpoints are exposed to 0.0.0.0/0", 2266 exposed: true, 2267 // This is equivalent to having "expose:true" and skipping the exposed settings section from the overlay 2268 exposedEndpoints: map[string]description.ExposedEndpointArgs{ 2269 "": { 2270 ExposeToCIDRs: []string{firewall.AllNetworksIPV4CIDR}, 2271 }, 2272 }, 2273 expBundle: ` 2274 default-base: ubuntu@20.04/stable 2275 applications: 2276 magic: 2277 charm: magic 2278 channel: stable 2279 expose: true 2280 bindings: 2281 hat: some-space 2282 rabbit: alpha 2283 `[1:], 2284 }, 2285 { 2286 descr: "exposed application with per-endpoint expose settings", 2287 exposed: true, 2288 exposedEndpoints: map[string]description.ExposedEndpointArgs{ 2289 "rabbit": { 2290 ExposeToSpaceIDs: []string{"2"}, 2291 ExposeToCIDRs: []string{"10.0.0.0/24", "192.168.0.0/24"}, 2292 }, 2293 "hat": { 2294 ExposeToCIDRs: []string{"192.168.0.0/24"}, 2295 }, 2296 }, 2297 // The exposed:true will be omitted and only the exposed-endpoints section (in the overlay) will be present 2298 expBundle: ` 2299 default-base: ubuntu@20.04/stable 2300 applications: 2301 magic: 2302 charm: magic 2303 channel: stable 2304 bindings: 2305 hat: some-space 2306 rabbit: alpha 2307 --- # overlay.yaml 2308 applications: 2309 magic: 2310 exposed-endpoints: 2311 hat: 2312 expose-to-cidrs: 2313 - 192.168.0.0/24 2314 rabbit: 2315 expose-to-spaces: 2316 - some-space 2317 expose-to-cidrs: 2318 - 10.0.0.0/24 2319 - 192.168.0.0/24 2320 `[1:], 2321 }, 2322 } 2323 2324 for i, spec := range specs { 2325 c.Logf("%d. %s", i, spec.descr) 2326 2327 s.st.model = description.NewModel(description.ModelArgs{Owner: names.NewUserTag("magic"), 2328 Config: coretesting.FakeConfig(), 2329 CloudRegion: "some-region"}, 2330 ) 2331 s.st.Spaces[network.AlphaSpaceId] = network.AlphaSpaceName 2332 s.st.Spaces["2"] = "some-space" 2333 2334 application := s.st.model.AddApplication(description.ApplicationArgs{ 2335 Tag: names.NewApplicationTag("magic"), 2336 CharmURL: "ch:amd64/focal/magic", 2337 Channel: "stable", 2338 CharmModifiedVersion: 1, 2339 ForceCharm: true, 2340 Exposed: spec.exposed, 2341 ExposedEndpoints: spec.exposedEndpoints, 2342 EndpointBindings: map[string]string{ 2343 "hat": "2", 2344 "rabbit": "0", 2345 }, 2346 }) 2347 application.SetCharmOrigin(description.CharmOriginArgs{Platform: "amd64/ubuntu/20.04/stable"}) 2348 application.SetStatus(minimalStatusArgs()) 2349 2350 result, err := s.facade.ExportBundle(params.ExportBundleParams{}) 2351 c.Assert(err, jc.ErrorIsNil) 2352 2353 exp := params.StringResult{Result: spec.expBundle} 2354 c.Assert(result, gc.Equals, exp) 2355 } 2356 }