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, &params.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  }