github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/provisioner/provisioninginfo_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner_test
     5  
     6  import (
     7  	"fmt"
     8  
     9  	jc "github.com/juju/testing/checkers"
    10  	gc "gopkg.in/check.v1"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	"github.com/juju/juju/apiserver/facades/agent/provisioner"
    14  	"github.com/juju/juju/apiserver/params"
    15  	apiservertesting "github.com/juju/juju/apiserver/testing"
    16  	"github.com/juju/juju/core/constraints"
    17  	"github.com/juju/juju/environs/tags"
    18  	"github.com/juju/juju/juju/testing"
    19  	"github.com/juju/juju/provider/dummy"
    20  	"github.com/juju/juju/state"
    21  	"github.com/juju/juju/state/multiwatcher"
    22  	"github.com/juju/juju/storage"
    23  	"github.com/juju/juju/storage/poolmanager"
    24  	"github.com/juju/juju/storage/provider"
    25  	coretesting "github.com/juju/juju/testing"
    26  )
    27  
    28  func (s *withoutControllerSuite) TestProvisioningInfoWithStorage(c *gc.C) {
    29  	pm := poolmanager.New(state.NewStateSettings(s.State), storage.ChainedProviderRegistry{
    30  		dummy.StorageProviders(),
    31  		provider.CommonStorageProviders(),
    32  	})
    33  	_, err := pm.Create("static-pool", "static", map[string]interface{}{"foo": "bar"})
    34  	c.Assert(err, jc.ErrorIsNil)
    35  
    36  	cons := constraints.MustParse("cores=123 mem=8G")
    37  	template := state.MachineTemplate{
    38  		Series:      "quantal",
    39  		Jobs:        []state.MachineJob{state.JobHostUnits},
    40  		Constraints: cons,
    41  		Placement:   "valid",
    42  		Volumes: []state.HostVolumeParams{
    43  			{Volume: state.VolumeParams{Size: 1000, Pool: "static-pool"}},
    44  			{Volume: state.VolumeParams{Size: 2000, Pool: "static-pool"}},
    45  		},
    46  	}
    47  	placementMachine, err := s.State.AddOneMachine(template)
    48  	c.Assert(err, jc.ErrorIsNil)
    49  
    50  	args := params.Entities{Entities: []params.Entity{
    51  		{Tag: s.machines[0].Tag().String()},
    52  		{Tag: placementMachine.Tag().String()},
    53  	}}
    54  	result, err := s.provisioner.ProvisioningInfo(args)
    55  	c.Assert(err, jc.ErrorIsNil)
    56  
    57  	controllerCfg := coretesting.FakeControllerConfig()
    58  	// Dummy provider uses a random port, which is added to cfg used to create environment.
    59  	apiPort := dummy.APIPort(s.Environ.Provider())
    60  	controllerCfg["api-port"] = apiPort
    61  	expected := params.ProvisioningInfoResults{
    62  		Results: []params.ProvisioningInfoResult{
    63  			{Result: &params.ProvisioningInfo{
    64  				ControllerConfig: controllerCfg,
    65  				Series:           "quantal",
    66  				Jobs:             []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
    67  				Tags: map[string]string{
    68  					tags.JujuController: coretesting.ControllerTag.Id(),
    69  					tags.JujuModel:      coretesting.ModelTag.Id(),
    70  					tags.JujuMachine:    "controller-machine-0",
    71  				},
    72  			}},
    73  			{Result: &params.ProvisioningInfo{
    74  				ControllerConfig: controllerCfg,
    75  				Series:           "quantal",
    76  				Constraints:      template.Constraints,
    77  				Placement:        template.Placement,
    78  				Jobs:             []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
    79  				Tags: map[string]string{
    80  					tags.JujuController: coretesting.ControllerTag.Id(),
    81  					tags.JujuModel:      coretesting.ModelTag.Id(),
    82  					tags.JujuMachine:    "controller-machine-5",
    83  				},
    84  				Volumes: []params.VolumeParams{{
    85  					VolumeTag:  "volume-0",
    86  					Size:       1000,
    87  					Provider:   "static",
    88  					Attributes: map[string]interface{}{"foo": "bar"},
    89  					Tags: map[string]string{
    90  						tags.JujuController: coretesting.ControllerTag.Id(),
    91  						tags.JujuModel:      coretesting.ModelTag.Id(),
    92  					},
    93  					Attachment: &params.VolumeAttachmentParams{
    94  						MachineTag: placementMachine.Tag().String(),
    95  						VolumeTag:  "volume-0",
    96  						Provider:   "static",
    97  					},
    98  				}, {
    99  					VolumeTag:  "volume-1",
   100  					Size:       2000,
   101  					Provider:   "static",
   102  					Attributes: map[string]interface{}{"foo": "bar"},
   103  					Tags: map[string]string{
   104  						tags.JujuController: coretesting.ControllerTag.Id(),
   105  						tags.JujuModel:      coretesting.ModelTag.Id(),
   106  					},
   107  					Attachment: &params.VolumeAttachmentParams{
   108  						MachineTag: placementMachine.Tag().String(),
   109  						VolumeTag:  "volume-1",
   110  						Provider:   "static",
   111  					},
   112  				}},
   113  			}},
   114  		},
   115  	}
   116  	// The order of volumes is not predictable, so we make sure we
   117  	// compare the right ones. This only applies to Results[1] since
   118  	// it is the only result to contain volumes.
   119  	if expected.Results[1].Result.Volumes[0].VolumeTag != result.Results[1].Result.Volumes[0].VolumeTag {
   120  		vols := expected.Results[1].Result.Volumes
   121  		vols[0], vols[1] = vols[1], vols[0]
   122  	}
   123  	c.Assert(result, jc.DeepEquals, expected)
   124  }
   125  
   126  func (s *withoutControllerSuite) TestProvisioningInfoWithSingleNegativeAndPositiveSpaceInConstraints(c *gc.C) {
   127  	s.addSpacesAndSubnets(c)
   128  
   129  	cons := constraints.MustParse("cores=123 mem=8G spaces=^space1,space2")
   130  	template := state.MachineTemplate{
   131  		Series:      "quantal",
   132  		Jobs:        []state.MachineJob{state.JobHostUnits},
   133  		Constraints: cons,
   134  		Placement:   "valid",
   135  	}
   136  	placementMachine, err := s.State.AddOneMachine(template)
   137  	c.Assert(err, jc.ErrorIsNil)
   138  
   139  	args := params.Entities{Entities: []params.Entity{
   140  		{Tag: placementMachine.Tag().String()},
   141  	}}
   142  	result, err := s.provisioner.ProvisioningInfo(args)
   143  	c.Assert(err, jc.ErrorIsNil)
   144  
   145  	controllerCfg := coretesting.FakeControllerConfig()
   146  	// Dummy provider uses a random port, which is added to cfg used to create environment.
   147  	apiPort := dummy.APIPort(s.Environ.Provider())
   148  	controllerCfg["api-port"] = apiPort
   149  	expected := params.ProvisioningInfoResults{
   150  		Results: []params.ProvisioningInfoResult{{
   151  			Result: &params.ProvisioningInfo{
   152  				ControllerConfig: controllerCfg,
   153  				Series:           "quantal",
   154  				Constraints:      template.Constraints,
   155  				Placement:        template.Placement,
   156  				Jobs:             []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   157  				Tags: map[string]string{
   158  					tags.JujuController: coretesting.ControllerTag.Id(),
   159  					tags.JujuModel:      coretesting.ModelTag.Id(),
   160  					tags.JujuMachine:    "controller-machine-5",
   161  				},
   162  				SubnetsToZones: map[string][]string{
   163  					"subnet-1": {"zone1"},
   164  					"subnet-2": {"zone2"},
   165  				},
   166  			},
   167  		}}}
   168  	c.Assert(result, jc.DeepEquals, expected)
   169  }
   170  
   171  func (s *withoutControllerSuite) addSpacesAndSubnets(c *gc.C) {
   172  	// Add a couple of spaces.
   173  	_, err := s.State.AddSpace("space1", "first space id", nil, true)
   174  	c.Assert(err, jc.ErrorIsNil)
   175  	_, err = s.State.AddSpace("space2", "", nil, false) // no provider ID
   176  	c.Assert(err, jc.ErrorIsNil)
   177  	// Add 1 subnet into space1, and 2 into space2.
   178  	// Each subnet is in a matching zone (e.g "subnet-#" in "zone#").
   179  	testing.AddSubnetsWithTemplate(c, s.State, 3, state.SubnetInfo{
   180  		CIDR:             "10.10.{{.}}.0/24",
   181  		ProviderId:       "subnet-{{.}}",
   182  		AvailabilityZone: "zone{{.}}",
   183  		SpaceName:        "{{if (eq . 0)}}space1{{else}}space2{{end}}",
   184  		VLANTag:          42,
   185  	})
   186  }
   187  
   188  func (s *withoutControllerSuite) TestProvisioningInfoWithEndpointBindings(c *gc.C) {
   189  	s.addSpacesAndSubnets(c)
   190  
   191  	wordpressMachine, err := s.State.AddOneMachine(state.MachineTemplate{
   192  		Series: "quantal",
   193  		Jobs:   []state.MachineJob{state.JobHostUnits},
   194  	})
   195  	c.Assert(err, jc.ErrorIsNil)
   196  
   197  	// Use juju names for spaces in bindings, simulating ''juju deploy
   198  	// --bind...' was called.
   199  	bindings := map[string]string{
   200  		"url": "space1", // has both name and provider ID
   201  		"db":  "space2", // has only name, no provider ID
   202  	}
   203  	wordpressCharm := s.AddTestingCharm(c, "wordpress")
   204  	wordpressService := s.AddTestingApplicationWithBindings(c, "wordpress", wordpressCharm, bindings)
   205  	wordpressUnit, err := wordpressService.AddUnit(state.AddUnitParams{})
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	err = wordpressUnit.AssignToMachine(wordpressMachine)
   208  	c.Assert(err, jc.ErrorIsNil)
   209  
   210  	args := params.Entities{Entities: []params.Entity{
   211  		{Tag: wordpressMachine.Tag().String()},
   212  	}}
   213  	result, err := s.provisioner.ProvisioningInfo(args)
   214  	c.Assert(err, jc.ErrorIsNil)
   215  
   216  	controllerCfg := coretesting.FakeControllerConfig()
   217  	// Dummy provider uses a random port, which is added to cfg used to create environment.
   218  	apiPort := dummy.APIPort(s.Environ.Provider())
   219  	controllerCfg["api-port"] = apiPort
   220  	expected := params.ProvisioningInfoResults{
   221  		Results: []params.ProvisioningInfoResult{{
   222  			Result: &params.ProvisioningInfo{
   223  				ControllerConfig: controllerCfg,
   224  				Series:           "quantal",
   225  				Jobs:             []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   226  				Tags: map[string]string{
   227  					tags.JujuController:    coretesting.ControllerTag.Id(),
   228  					tags.JujuModel:         coretesting.ModelTag.Id(),
   229  					tags.JujuMachine:       "controller-machine-5",
   230  					tags.JujuUnitsDeployed: wordpressUnit.Name(),
   231  				},
   232  				// Ensure space names are translated to provider IDs, where
   233  				// possible.
   234  				EndpointBindings: map[string]string{
   235  					"db":  "space2",         // just name, no provider ID
   236  					"url": "first space id", // has provider ID
   237  					// We expect none of the unspecified bindings in the result.
   238  				},
   239  			},
   240  		}}}
   241  	c.Assert(result, jc.DeepEquals, expected)
   242  }
   243  
   244  func (s *withoutControllerSuite) TestProvisioningInfoWithUnsuitableSpacesConstraints(c *gc.C) {
   245  	// Add an empty space.
   246  	_, err := s.State.AddSpace("empty", "", nil, true)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  
   249  	consEmptySpace := constraints.MustParse("cores=123 mem=8G spaces=empty")
   250  	consMissingSpace := constraints.MustParse("cores=123 mem=8G spaces=missing")
   251  	templates := []state.MachineTemplate{{
   252  		Series:      "quantal",
   253  		Jobs:        []state.MachineJob{state.JobHostUnits},
   254  		Constraints: consEmptySpace,
   255  		Placement:   "valid",
   256  	}, {
   257  		Series:      "quantal",
   258  		Jobs:        []state.MachineJob{state.JobHostUnits},
   259  		Constraints: consMissingSpace,
   260  		Placement:   "valid",
   261  	}}
   262  	placementMachines, err := s.State.AddMachines(templates...)
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	c.Assert(placementMachines, gc.HasLen, 2)
   265  
   266  	args := params.Entities{Entities: []params.Entity{
   267  		{Tag: placementMachines[0].Tag().String()},
   268  		{Tag: placementMachines[1].Tag().String()},
   269  	}}
   270  	result, err := s.provisioner.ProvisioningInfo(args)
   271  	c.Assert(err, jc.ErrorIsNil)
   272  
   273  	expectedErrorEmptySpace := `cannot match subnets to zones: ` +
   274  		`cannot use space "empty" as deployment target: no subnets`
   275  	expectedErrorMissingSpace := `cannot match subnets to zones: ` +
   276  		`space "missing"` // " not found" will be appended by NotFoundError helper below.
   277  	expected := params.ProvisioningInfoResults{Results: []params.ProvisioningInfoResult{
   278  		{Error: apiservertesting.ServerError(expectedErrorEmptySpace)},
   279  		{Error: apiservertesting.NotFoundError(expectedErrorMissingSpace)},
   280  	}}
   281  	c.Assert(result, jc.DeepEquals, expected)
   282  }
   283  
   284  func (s *withoutControllerSuite) TestProvisioningInfoWithLXDProfile(c *gc.C) {
   285  	profileMachine, err := s.State.AddOneMachine(state.MachineTemplate{
   286  		Series: "quantal",
   287  		Jobs:   []state.MachineJob{state.JobHostUnits},
   288  	})
   289  	c.Assert(err, jc.ErrorIsNil)
   290  
   291  	profileCharm := s.AddTestingCharm(c, "lxd-profile")
   292  	profileService := s.AddTestingApplication(c, "lxd-profile", profileCharm)
   293  	profileUnit, err := profileService.AddUnit(state.AddUnitParams{})
   294  	c.Assert(err, jc.ErrorIsNil)
   295  	err = profileUnit.AssignToMachine(profileMachine)
   296  	c.Assert(err, jc.ErrorIsNil)
   297  
   298  	args := params.Entities{Entities: []params.Entity{
   299  		{Tag: profileMachine.Tag().String()},
   300  	}}
   301  	result, err := s.provisioner.ProvisioningInfo(args)
   302  	c.Assert(err, jc.ErrorIsNil)
   303  
   304  	controllerCfg := coretesting.FakeControllerConfig()
   305  	// Dummy provider uses a random port, which is added to cfg used to create environment.
   306  	apiPort := dummy.APIPort(s.Environ.Provider())
   307  	controllerCfg["api-port"] = apiPort
   308  
   309  	pName := fmt.Sprintf("juju-%s-lxd-profile-0", profileMachine.ModelName())
   310  	expected := params.ProvisioningInfoResults{
   311  		Results: []params.ProvisioningInfoResult{{
   312  			Result: &params.ProvisioningInfo{
   313  				ControllerConfig: controllerCfg,
   314  				Series:           "quantal",
   315  				Jobs:             []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   316  				Tags: map[string]string{
   317  					tags.JujuController:    coretesting.ControllerTag.Id(),
   318  					tags.JujuModel:         coretesting.ModelTag.Id(),
   319  					tags.JujuMachine:       "controller-machine-5",
   320  					tags.JujuUnitsDeployed: profileUnit.Name(),
   321  				},
   322  				EndpointBindings: map[string]string{},
   323  				CharmLXDProfiles: []string{pName},
   324  			},
   325  		}}}
   326  	c.Assert(result, jc.DeepEquals, expected)
   327  }
   328  
   329  func (s *withoutControllerSuite) TestStorageProviderFallbackToType(c *gc.C) {
   330  	template := state.MachineTemplate{
   331  		Series:    "quantal",
   332  		Jobs:      []state.MachineJob{state.JobHostUnits},
   333  		Placement: "valid",
   334  		Volumes: []state.HostVolumeParams{
   335  			{Volume: state.VolumeParams{Size: 1000, Pool: "loop"}},
   336  			{Volume: state.VolumeParams{Size: 1000, Pool: "static"}},
   337  		},
   338  	}
   339  	placementMachine, err := s.State.AddOneMachine(template)
   340  	c.Assert(err, jc.ErrorIsNil)
   341  
   342  	args := params.Entities{Entities: []params.Entity{
   343  		{Tag: placementMachine.Tag().String()},
   344  	}}
   345  	result, err := s.provisioner.ProvisioningInfo(args)
   346  	c.Assert(err, jc.ErrorIsNil)
   347  
   348  	controllerCfg := coretesting.FakeControllerConfig()
   349  	// Dummy provider uses a random port, which is added to cfg used to create environment.
   350  	apiPort := dummy.APIPort(s.Environ.Provider())
   351  	controllerCfg["api-port"] = apiPort
   352  	c.Assert(result, jc.DeepEquals, params.ProvisioningInfoResults{
   353  		Results: []params.ProvisioningInfoResult{
   354  			{Result: &params.ProvisioningInfo{
   355  				ControllerConfig: controllerCfg,
   356  				Series:           "quantal",
   357  				Constraints:      template.Constraints,
   358  				Placement:        template.Placement,
   359  				Jobs:             []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   360  				Tags: map[string]string{
   361  					tags.JujuController: coretesting.ControllerTag.Id(),
   362  					tags.JujuModel:      coretesting.ModelTag.Id(),
   363  					tags.JujuMachine:    "controller-machine-5",
   364  				},
   365  				// volume-0 should not be included as it is not managed by
   366  				// the environ provider.
   367  				Volumes: []params.VolumeParams{{
   368  					VolumeTag:  "volume-1",
   369  					Size:       1000,
   370  					Provider:   "static",
   371  					Attributes: nil,
   372  					Tags: map[string]string{
   373  						tags.JujuController: coretesting.ControllerTag.Id(),
   374  						tags.JujuModel:      coretesting.ModelTag.Id(),
   375  					},
   376  					Attachment: &params.VolumeAttachmentParams{
   377  						MachineTag: placementMachine.Tag().String(),
   378  						VolumeTag:  "volume-1",
   379  						Provider:   "static",
   380  					},
   381  				}},
   382  			}},
   383  		},
   384  	})
   385  }
   386  
   387  func (s *withoutControllerSuite) TestStorageProviderVolumes(c *gc.C) {
   388  	template := state.MachineTemplate{
   389  		Series: "quantal",
   390  		Jobs:   []state.MachineJob{state.JobHostUnits},
   391  		Volumes: []state.HostVolumeParams{
   392  			{Volume: state.VolumeParams{Size: 1000, Pool: "modelscoped"}},
   393  			{Volume: state.VolumeParams{Size: 1000, Pool: "modelscoped"}},
   394  		},
   395  	}
   396  	machine, err := s.State.AddOneMachine(template)
   397  	c.Assert(err, jc.ErrorIsNil)
   398  
   399  	// Provision just one of the volumes, but neither of the attachments.
   400  	sb, err := state.NewStorageBackend(s.State)
   401  	c.Assert(err, jc.ErrorIsNil)
   402  	err = sb.SetVolumeInfo(names.NewVolumeTag("1"), state.VolumeInfo{
   403  		Pool:       "modelscoped",
   404  		Size:       1000,
   405  		VolumeId:   "vol-ume",
   406  		Persistent: true,
   407  	})
   408  	c.Assert(err, jc.ErrorIsNil)
   409  
   410  	args := params.Entities{Entities: []params.Entity{
   411  		{Tag: machine.Tag().String()},
   412  	}}
   413  	result, err := s.provisioner.ProvisioningInfo(args)
   414  	c.Assert(err, jc.ErrorIsNil)
   415  	c.Assert(result.Results[0].Error, gc.IsNil)
   416  	c.Assert(result.Results[0].Result, gc.NotNil)
   417  
   418  	// volume-0 should be created, as it hasn't yet been provisioned.
   419  	c.Assert(result.Results[0].Result.Volumes, jc.DeepEquals, []params.VolumeParams{{
   420  		VolumeTag: "volume-0",
   421  		Size:      1000,
   422  		Provider:  "modelscoped",
   423  		Tags: map[string]string{
   424  			tags.JujuController: coretesting.ControllerTag.Id(),
   425  			tags.JujuModel:      coretesting.ModelTag.Id(),
   426  		},
   427  		Attachment: &params.VolumeAttachmentParams{
   428  			MachineTag: machine.Tag().String(),
   429  			VolumeTag:  "volume-0",
   430  			Provider:   "modelscoped",
   431  		},
   432  	}})
   433  
   434  	// volume-1 has already been provisioned, it just needs to be attached.
   435  	c.Assert(result.Results[0].Result.VolumeAttachments, jc.DeepEquals, []params.VolumeAttachmentParams{{
   436  		MachineTag: machine.Tag().String(),
   437  		VolumeTag:  "volume-1",
   438  		VolumeId:   "vol-ume",
   439  		Provider:   "modelscoped",
   440  	}})
   441  }
   442  
   443  func (s *withoutControllerSuite) TestProviderInfoCloudInitUserData(c *gc.C) {
   444  	attrs := map[string]interface{}{"cloudinit-userdata": validCloudInitUserData}
   445  	err := s.Model.UpdateModelConfig(attrs, nil)
   446  	c.Assert(err, jc.ErrorIsNil)
   447  	template := state.MachineTemplate{
   448  		Series: "quantal",
   449  		Jobs:   []state.MachineJob{state.JobHostUnits},
   450  	}
   451  	m, err := s.State.AddOneMachine(template)
   452  	c.Assert(err, jc.ErrorIsNil)
   453  
   454  	args := params.Entities{Entities: []params.Entity{
   455  		{Tag: m.Tag().String()},
   456  	}}
   457  	result, err := s.provisioner.ProvisioningInfo(args)
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	c.Assert(result.Results[0].Result.CloudInitUserData, gc.DeepEquals, map[string]interface{}{
   460  		"packages":        []interface{}{"python-keystoneclient", "python-glanceclient"},
   461  		"preruncmd":       []interface{}{"mkdir /tmp/preruncmd", "mkdir /tmp/preruncmd2"},
   462  		"postruncmd":      []interface{}{"mkdir /tmp/postruncmd", "mkdir /tmp/postruncmd2"},
   463  		"package_upgrade": false})
   464  }
   465  
   466  var validCloudInitUserData = `
   467  packages:
   468    - 'python-keystoneclient'
   469    - 'python-glanceclient'
   470  preruncmd:
   471    - mkdir /tmp/preruncmd
   472    - mkdir /tmp/preruncmd2
   473  postruncmd:
   474    - mkdir /tmp/postruncmd
   475    - mkdir /tmp/postruncmd2
   476  package_upgrade: false
   477  `[1:]
   478  
   479  func (s *withoutControllerSuite) TestProvisioningInfoPermissions(c *gc.C) {
   480  	// Login as a machine agent for machine 0.
   481  	anAuthorizer := s.authorizer
   482  	anAuthorizer.Controller = false
   483  	anAuthorizer.Tag = s.machines[0].Tag()
   484  	aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer)
   485  	c.Assert(err, jc.ErrorIsNil)
   486  	c.Assert(aProvisioner, gc.NotNil)
   487  
   488  	args := params.Entities{Entities: []params.Entity{
   489  		{Tag: s.machines[0].Tag().String()},
   490  		{Tag: s.machines[0].Tag().String() + "-lxd-0"},
   491  		{Tag: "machine-42"},
   492  		{Tag: s.machines[1].Tag().String()},
   493  		{Tag: "application-bar"},
   494  	}}
   495  
   496  	// Only machine 0 and containers therein can be accessed.
   497  	results, err := aProvisioner.ProvisioningInfo(args)
   498  	controllerCfg := coretesting.FakeControllerConfig()
   499  	// Dummy provider uses a random port, which is added to cfg used to create environment.
   500  	apiPort := dummy.APIPort(s.Environ.Provider())
   501  	controllerCfg["api-port"] = apiPort
   502  	c.Assert(results, jc.DeepEquals, params.ProvisioningInfoResults{
   503  		Results: []params.ProvisioningInfoResult{
   504  			{Result: &params.ProvisioningInfo{
   505  				ControllerConfig: controllerCfg,
   506  				Series:           "quantal",
   507  				Jobs:             []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   508  				Tags: map[string]string{
   509  					tags.JujuController: coretesting.ControllerTag.Id(),
   510  					tags.JujuModel:      coretesting.ModelTag.Id(),
   511  					tags.JujuMachine:    "controller-machine-0",
   512  				},
   513  			}},
   514  			{Error: apiservertesting.NotFoundError("machine 0/lxd/0")},
   515  			{Error: apiservertesting.ErrUnauthorized},
   516  			{Error: apiservertesting.ErrUnauthorized},
   517  			{Error: apiservertesting.ErrUnauthorized},
   518  		},
   519  	})
   520  }