github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/agent/agentbootstrap/bootstrap_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agentbootstrap_test
     5  
     6  import (
     7  	"io/ioutil"
     8  	"net"
     9  	"path/filepath"
    10  
    11  	"github.com/juju/os/series"
    12  	gitjujutesting "github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/names.v2"
    17  
    18  	"github.com/juju/juju/agent"
    19  	"github.com/juju/juju/agent/agentbootstrap"
    20  	"github.com/juju/juju/apiserver/params"
    21  	"github.com/juju/juju/cloud"
    22  	"github.com/juju/juju/cloudconfig/instancecfg"
    23  	"github.com/juju/juju/controller"
    24  	"github.com/juju/juju/core/constraints"
    25  	"github.com/juju/juju/core/instance"
    26  	"github.com/juju/juju/environs"
    27  	"github.com/juju/juju/environs/config"
    28  	"github.com/juju/juju/environs/context"
    29  	"github.com/juju/juju/mongo"
    30  	"github.com/juju/juju/mongo/mongotest"
    31  	"github.com/juju/juju/network"
    32  	"github.com/juju/juju/provider/dummy"
    33  	"github.com/juju/juju/state"
    34  	"github.com/juju/juju/state/multiwatcher"
    35  	"github.com/juju/juju/storage/provider"
    36  	"github.com/juju/juju/testing"
    37  	jujuversion "github.com/juju/juju/version"
    38  )
    39  
    40  type bootstrapSuite struct {
    41  	testing.BaseSuite
    42  	mgoInst gitjujutesting.MgoInstance
    43  }
    44  
    45  var _ = gc.Suite(&bootstrapSuite{})
    46  
    47  func (s *bootstrapSuite) SetUpTest(c *gc.C) {
    48  	s.BaseSuite.SetUpTest(c)
    49  	// Don't use MgoSuite, because we need to ensure
    50  	// we have a fresh mongo for each test case.
    51  	s.mgoInst.EnableAuth = true
    52  	err := s.mgoInst.Start(testing.Certs)
    53  	c.Assert(err, jc.ErrorIsNil)
    54  }
    55  
    56  func (s *bootstrapSuite) TearDownTest(c *gc.C) {
    57  	s.mgoInst.Destroy()
    58  	s.BaseSuite.TearDownTest(c)
    59  }
    60  
    61  func (s *bootstrapSuite) TestInitializeState(c *gc.C) {
    62  	dataDir := c.MkDir()
    63  
    64  	lxcFakeNetConfig := filepath.Join(c.MkDir(), "lxc-net")
    65  	netConf := []byte(`
    66    # comments ignored
    67  LXC_BR= ignored
    68  LXC_ADDR = "fooo"
    69  LXC_BRIDGE="foobar" # detected
    70  anything else ignored
    71  LXC_BRIDGE="ignored"`[1:])
    72  	err := ioutil.WriteFile(lxcFakeNetConfig, netConf, 0644)
    73  	c.Assert(err, jc.ErrorIsNil)
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	s.PatchValue(&network.InterfaceByNameAddrs, func(name string) ([]net.Addr, error) {
    76  		if name == "foobar" {
    77  			// The addresses on the LXC bridge
    78  			return []net.Addr{
    79  				&net.IPAddr{IP: net.IPv4(10, 0, 3, 1)},
    80  				&net.IPAddr{IP: net.IPv4(10, 0, 3, 4)},
    81  			}, nil
    82  		} else if name == network.DefaultLXDBridge {
    83  			// The addresses on the LXD bridge
    84  			return []net.Addr{
    85  				&net.IPAddr{IP: net.IPv4(10, 0, 4, 1)},
    86  				&net.IPAddr{IP: net.IPv4(10, 0, 4, 4)},
    87  			}, nil
    88  		} else if name == network.DefaultKVMBridge {
    89  			// claim we don't have a virbr0 bridge
    90  			return nil, nil
    91  		}
    92  		c.Fatalf("unknown bridge in testing: %v", name)
    93  		return nil, nil
    94  	})
    95  	s.PatchValue(&network.LXCNetDefaultConfig, lxcFakeNetConfig)
    96  
    97  	configParams := agent.AgentConfigParams{
    98  		Paths:             agent.Paths{DataDir: dataDir},
    99  		Tag:               names.NewMachineTag("0"),
   100  		UpgradedToVersion: jujuversion.Current,
   101  		APIAddresses:      []string{"localhost:17070"},
   102  		CACert:            testing.CACert,
   103  		Password:          testing.DefaultMongoPassword,
   104  		Controller:        testing.ControllerTag,
   105  		Model:             testing.ModelTag,
   106  	}
   107  	servingInfo := params.StateServingInfo{
   108  		Cert:           testing.ServerCert,
   109  		PrivateKey:     testing.ServerKey,
   110  		CAPrivateKey:   testing.CAKey,
   111  		APIPort:        1234,
   112  		StatePort:      s.mgoInst.Port(),
   113  		SystemIdentity: "def456",
   114  	}
   115  
   116  	cfg, err := agent.NewStateMachineConfig(configParams, servingInfo)
   117  	c.Assert(err, jc.ErrorIsNil)
   118  
   119  	_, available := cfg.StateServingInfo()
   120  	c.Assert(available, jc.IsTrue)
   121  	expectBootstrapConstraints := constraints.MustParse("mem=1024M")
   122  	expectModelConstraints := constraints.MustParse("mem=512M")
   123  	expectHW := instance.MustParseHardware("mem=2048M")
   124  	initialAddrs := network.NewAddresses(
   125  		"zeroonetwothree",
   126  		"0.1.2.3",
   127  		"10.0.3.1", // lxc bridge address filtered.
   128  		"10.0.3.4", // lxc bridge address filtered (-"-).
   129  		"10.0.3.3", // not a lxc bridge address
   130  		"10.0.4.1", // lxd bridge address filtered.
   131  		"10.0.4.4", // lxd bridge address filtered.
   132  		"10.0.4.5", // not an lxd bridge address
   133  	)
   134  	filteredAddrs := network.NewAddresses(
   135  		"zeroonetwothree",
   136  		"0.1.2.3",
   137  		"10.0.3.3",
   138  		"10.0.4.5",
   139  	)
   140  
   141  	modelAttrs := testing.FakeConfig().Merge(testing.Attrs{
   142  		"agent-version":  jujuversion.Current.String(),
   143  		"not-for-hosted": "foo",
   144  	})
   145  	modelCfg, err := config.New(config.NoDefaults, modelAttrs)
   146  	c.Assert(err, jc.ErrorIsNil)
   147  	controllerCfg := testing.FakeControllerConfig()
   148  
   149  	hostedModelUUID := utils.MustNewUUID().String()
   150  	hostedModelConfigAttrs := map[string]interface{}{
   151  		"name": "hosted",
   152  		"uuid": hostedModelUUID,
   153  	}
   154  	controllerInheritedConfig := map[string]interface{}{
   155  		"apt-mirror": "http://mirror",
   156  		"no-proxy":   "value",
   157  	}
   158  	regionConfig := cloud.RegionConfig{
   159  		"some-region": cloud.Attrs{
   160  			"no-proxy": "a-value",
   161  		},
   162  	}
   163  	var envProvider fakeProvider
   164  	args := agentbootstrap.InitializeStateParams{
   165  		StateInitializationParams: instancecfg.StateInitializationParams{
   166  			BootstrapMachineConstraints:             expectBootstrapConstraints,
   167  			BootstrapMachineInstanceId:              "i-bootstrap",
   168  			BootstrapMachineHardwareCharacteristics: &expectHW,
   169  			ControllerCloud: cloud.Cloud{
   170  				Name:         "dummy",
   171  				Type:         "dummy",
   172  				AuthTypes:    []cloud.AuthType{cloud.EmptyAuthType},
   173  				Regions:      []cloud.Region{{Name: "dummy-region"}},
   174  				RegionConfig: regionConfig,
   175  			},
   176  			ControllerCloudRegion:         "dummy-region",
   177  			ControllerConfig:              controllerCfg,
   178  			ControllerModelConfig:         modelCfg,
   179  			ControllerModelEnvironVersion: 666,
   180  			ModelConstraints:              expectModelConstraints,
   181  			ControllerInheritedConfig:     controllerInheritedConfig,
   182  			HostedModelConfig:             hostedModelConfigAttrs,
   183  		},
   184  		BootstrapMachineAddresses: initialAddrs,
   185  		BootstrapMachineJobs:      []multiwatcher.MachineJob{multiwatcher.JobManageModel},
   186  		SharedSecret:              "abc123",
   187  		Provider: func(t string) (environs.EnvironProvider, error) {
   188  			c.Assert(t, gc.Equals, "dummy")
   189  			return &envProvider, nil
   190  		},
   191  		StorageProviderRegistry: provider.CommonStorageProviders(),
   192  	}
   193  
   194  	adminUser := names.NewLocalUserTag("agent-admin")
   195  	ctlr, m, err := agentbootstrap.InitializeState(
   196  		adminUser, cfg, args, mongotest.DialOpts(), state.NewPolicyFunc(nil),
   197  	)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	defer ctlr.Close()
   200  	st := ctlr.SystemState()
   201  	err = cfg.Write()
   202  	c.Assert(err, jc.ErrorIsNil)
   203  
   204  	// Check that the model has been set up.
   205  	model, err := st.Model()
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	c.Assert(model.UUID(), gc.Equals, modelCfg.UUID())
   208  	c.Assert(model.EnvironVersion(), gc.Equals, 666)
   209  
   210  	// Check that initial admin user has been set up correctly.
   211  	modelTag := model.Tag().(names.ModelTag)
   212  	controllerTag := names.NewControllerTag(controllerCfg.ControllerUUID())
   213  	s.assertCanLogInAsAdmin(c, modelTag, controllerTag, testing.DefaultMongoPassword)
   214  	user, err := st.User(model.Owner())
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	c.Assert(user.PasswordValid(testing.DefaultMongoPassword), jc.IsTrue)
   217  
   218  	// Check controller config
   219  	controllerCfg, err = st.ControllerConfig()
   220  	c.Assert(err, jc.ErrorIsNil)
   221  	c.Assert(controllerCfg, jc.DeepEquals, controller.Config{
   222  		"controller-uuid":         testing.ControllerTag.Id(),
   223  		"ca-cert":                 testing.CACert,
   224  		"state-port":              1234,
   225  		"api-port":                17777,
   226  		"set-numa-control-policy": false,
   227  		"max-logs-age":            "72h",
   228  		"max-logs-size":           "4G",
   229  		"max-txn-log-size":        "10M",
   230  		"auditing-enabled":        false,
   231  		"audit-log-capture-args":  true,
   232  		"audit-log-max-size":      "200M",
   233  		"audit-log-max-backups":   5,
   234  	})
   235  
   236  	// Check that controller model configuration has been added, and
   237  	// model constraints set.
   238  	model, err = st.Model()
   239  	c.Assert(err, jc.ErrorIsNil)
   240  
   241  	newModelCfg, err := model.ModelConfig()
   242  	c.Assert(err, jc.ErrorIsNil)
   243  
   244  	// Add in the cloud attributes.
   245  	expectedCfg, err := config.New(config.UseDefaults, modelAttrs)
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	expectedAttrs := expectedCfg.AllAttrs()
   248  	expectedAttrs["apt-mirror"] = "http://mirror"
   249  	expectedAttrs["no-proxy"] = "value"
   250  	c.Assert(newModelCfg.AllAttrs(), jc.DeepEquals, expectedAttrs)
   251  
   252  	gotModelConstraints, err := st.ModelConstraints()
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	c.Assert(gotModelConstraints, gc.DeepEquals, expectModelConstraints)
   255  
   256  	// Check that the hosted model has been added, model constraints
   257  	// set, and its config contains the same authorized-keys as the
   258  	// controller model.
   259  	hostedModelSt, err := ctlr.StatePool().Get(hostedModelUUID)
   260  	c.Assert(err, jc.ErrorIsNil)
   261  	defer hostedModelSt.Release()
   262  	gotModelConstraints, err = hostedModelSt.ModelConstraints()
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	c.Assert(gotModelConstraints, gc.DeepEquals, expectModelConstraints)
   265  	hostedModel, err := hostedModelSt.Model()
   266  	c.Assert(err, jc.ErrorIsNil)
   267  	c.Assert(hostedModel.Name(), gc.Equals, "hosted")
   268  	c.Assert(hostedModel.CloudRegion(), gc.Equals, "dummy-region")
   269  	c.Assert(hostedModel.EnvironVersion(), gc.Equals, 123)
   270  	hostedCfg, err := hostedModel.ModelConfig()
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	_, hasUnexpected := hostedCfg.AllAttrs()["not-for-hosted"]
   273  	c.Assert(hasUnexpected, jc.IsFalse)
   274  	c.Assert(hostedCfg.AuthorizedKeys(), gc.Equals, newModelCfg.AuthorizedKeys())
   275  
   276  	// Check that the bootstrap machine looks correct.
   277  	c.Assert(m.Id(), gc.Equals, "0")
   278  	c.Assert(m.Jobs(), gc.DeepEquals, []state.MachineJob{state.JobManageModel})
   279  	c.Assert(m.Series(), gc.Equals, series.MustHostSeries())
   280  	c.Assert(m.CheckProvisioned(agent.BootstrapNonce), jc.IsTrue)
   281  	c.Assert(m.Addresses(), jc.DeepEquals, filteredAddrs)
   282  	gotBootstrapConstraints, err := m.Constraints()
   283  	c.Assert(err, jc.ErrorIsNil)
   284  	c.Assert(gotBootstrapConstraints, gc.DeepEquals, expectBootstrapConstraints)
   285  	c.Assert(err, jc.ErrorIsNil)
   286  	gotHW, err := m.HardwareCharacteristics()
   287  	c.Assert(err, jc.ErrorIsNil)
   288  	c.Assert(*gotHW, gc.DeepEquals, expectHW)
   289  
   290  	// Check that the API host ports are initialised correctly.
   291  	apiHostPorts, err := st.APIHostPortsForClients()
   292  	c.Assert(err, jc.ErrorIsNil)
   293  	c.Assert(apiHostPorts, jc.DeepEquals, [][]network.HostPort{
   294  		network.AddressesWithPort(filteredAddrs, 1234),
   295  	})
   296  
   297  	// Check that the state serving info is initialised correctly.
   298  	stateServingInfo, err := st.StateServingInfo()
   299  	c.Assert(err, jc.ErrorIsNil)
   300  	c.Assert(stateServingInfo, jc.DeepEquals, state.StateServingInfo{
   301  		APIPort:        1234,
   302  		StatePort:      s.mgoInst.Port(),
   303  		Cert:           testing.ServerCert,
   304  		PrivateKey:     testing.ServerKey,
   305  		CAPrivateKey:   testing.CAKey,
   306  		SharedSecret:   "abc123",
   307  		SystemIdentity: "def456",
   308  	})
   309  
   310  	// Check that the machine agent's config has been written
   311  	// and that we can use it to connect to mongo.
   312  	machine0 := names.NewMachineTag("0")
   313  	newCfg, err := agent.ReadConfig(agent.ConfigPath(dataDir, machine0))
   314  	c.Assert(err, jc.ErrorIsNil)
   315  	c.Assert(newCfg.Tag(), gc.Equals, machine0)
   316  	info, ok := cfg.MongoInfo()
   317  	c.Assert(ok, jc.IsTrue)
   318  	c.Assert(info.Password, gc.Not(gc.Equals), testing.DefaultMongoPassword)
   319  
   320  	session, err := mongo.DialWithInfo(*info, mongotest.DialOpts())
   321  	c.Assert(err, jc.ErrorIsNil)
   322  	session.Close()
   323  
   324  	// Make sure that the hosted model Environ's Create method is called.
   325  	envProvider.CheckCallNames(c,
   326  		"PrepareConfig",
   327  		"Validate",
   328  		"Open",
   329  		"Create",
   330  		"Provider",
   331  		"Version",
   332  	)
   333  	// Those attributes are configured during initialization, after "Open".
   334  	expectedCalledCfg, err := hostedCfg.Apply(map[string]interface{}{"container-networking-method": ""})
   335  	c.Assert(err, jc.ErrorIsNil)
   336  	envProvider.CheckCall(c, 2, "Open", environs.OpenParams{
   337  		Cloud: environs.CloudSpec{
   338  			Type:   "dummy",
   339  			Name:   "dummy",
   340  			Region: "dummy-region",
   341  		},
   342  		Config: expectedCalledCfg,
   343  	})
   344  	envProvider.CheckCall(c, 3, "Create",
   345  		envProvider.environ.callCtxUsed,
   346  		environs.CreateParams{
   347  			ControllerUUID: controllerCfg.ControllerUUID(),
   348  		})
   349  }
   350  
   351  func (s *bootstrapSuite) TestInitializeStateWithStateServingInfoNotAvailable(c *gc.C) {
   352  	configParams := agent.AgentConfigParams{
   353  		Paths:             agent.Paths{DataDir: c.MkDir()},
   354  		Tag:               names.NewMachineTag("0"),
   355  		UpgradedToVersion: jujuversion.Current,
   356  		APIAddresses:      []string{"localhost:17070"},
   357  		CACert:            testing.CACert,
   358  		Password:          "fake",
   359  		Controller:        testing.ControllerTag,
   360  		Model:             testing.ModelTag,
   361  	}
   362  	cfg, err := agent.NewAgentConfig(configParams)
   363  	c.Assert(err, jc.ErrorIsNil)
   364  
   365  	_, available := cfg.StateServingInfo()
   366  	c.Assert(available, jc.IsFalse)
   367  
   368  	args := agentbootstrap.InitializeStateParams{}
   369  
   370  	adminUser := names.NewLocalUserTag("agent-admin")
   371  	_, _, err = agentbootstrap.InitializeState(adminUser, cfg, args, mongotest.DialOpts(), nil)
   372  	// InitializeState will fail attempting to get the api port information
   373  	c.Assert(err, gc.ErrorMatches, "state serving information not available")
   374  }
   375  
   376  func (s *bootstrapSuite) TestInitializeStateFailsSecondTime(c *gc.C) {
   377  	dataDir := c.MkDir()
   378  
   379  	configParams := agent.AgentConfigParams{
   380  		Paths:             agent.Paths{DataDir: dataDir},
   381  		Tag:               names.NewMachineTag("0"),
   382  		UpgradedToVersion: jujuversion.Current,
   383  		APIAddresses:      []string{"localhost:17070"},
   384  		CACert:            testing.CACert,
   385  		Password:          testing.DefaultMongoPassword,
   386  		Controller:        testing.ControllerTag,
   387  		Model:             testing.ModelTag,
   388  	}
   389  	cfg, err := agent.NewAgentConfig(configParams)
   390  	c.Assert(err, jc.ErrorIsNil)
   391  	cfg.SetStateServingInfo(params.StateServingInfo{
   392  		APIPort:        5555,
   393  		StatePort:      s.mgoInst.Port(),
   394  		Cert:           "foo",
   395  		PrivateKey:     "bar",
   396  		SharedSecret:   "baz",
   397  		SystemIdentity: "qux",
   398  	})
   399  	modelAttrs := dummy.SampleConfig().Delete("admin-secret").Merge(testing.Attrs{
   400  		"agent-version": jujuversion.Current.String(),
   401  	})
   402  	modelCfg, err := config.New(config.NoDefaults, modelAttrs)
   403  	c.Assert(err, jc.ErrorIsNil)
   404  
   405  	hostedModelConfigAttrs := map[string]interface{}{
   406  		"name": "hosted",
   407  		"uuid": utils.MustNewUUID().String(),
   408  	}
   409  
   410  	args := agentbootstrap.InitializeStateParams{
   411  		StateInitializationParams: instancecfg.StateInitializationParams{
   412  			BootstrapMachineInstanceId: "i-bootstrap",
   413  			ControllerCloud: cloud.Cloud{
   414  				Name:      "dummy",
   415  				Type:      "dummy",
   416  				AuthTypes: []cloud.AuthType{cloud.EmptyAuthType},
   417  			},
   418  			ControllerConfig:      testing.FakeControllerConfig(),
   419  			ControllerModelConfig: modelCfg,
   420  			HostedModelConfig:     hostedModelConfigAttrs,
   421  		},
   422  		BootstrapMachineJobs: []multiwatcher.MachineJob{multiwatcher.JobManageModel},
   423  		SharedSecret:         "abc123",
   424  		Provider: func(t string) (environs.EnvironProvider, error) {
   425  			return &fakeProvider{}, nil
   426  		},
   427  		StorageProviderRegistry: provider.CommonStorageProviders(),
   428  	}
   429  
   430  	adminUser := names.NewLocalUserTag("agent-admin")
   431  	st, _, err := agentbootstrap.InitializeState(
   432  		adminUser, cfg, args, mongotest.DialOpts(), state.NewPolicyFunc(nil),
   433  	)
   434  	c.Assert(err, jc.ErrorIsNil)
   435  	st.Close()
   436  
   437  	st, _, err = agentbootstrap.InitializeState(
   438  		adminUser, cfg, args, mongotest.DialOpts(), state.NewPolicyFunc(nil),
   439  	)
   440  	if err == nil {
   441  		st.Close()
   442  	}
   443  	c.Assert(err, gc.ErrorMatches, "bootstrapping raft cluster: bootstrap only works on new clusters")
   444  }
   445  
   446  func (s *bootstrapSuite) TestMachineJobFromParams(c *gc.C) {
   447  	var tests = []struct {
   448  		name multiwatcher.MachineJob
   449  		want state.MachineJob
   450  		err  string
   451  	}{{
   452  		name: multiwatcher.JobHostUnits,
   453  		want: state.JobHostUnits,
   454  	}, {
   455  		name: multiwatcher.JobManageModel,
   456  		want: state.JobManageModel,
   457  	}, {
   458  		name: "invalid",
   459  		want: -1,
   460  		err:  `invalid machine job "invalid"`,
   461  	}}
   462  	for _, test := range tests {
   463  		got, err := agentbootstrap.MachineJobFromParams(test.name)
   464  		if err != nil {
   465  			c.Check(err, gc.ErrorMatches, test.err)
   466  		}
   467  		c.Check(got, gc.Equals, test.want)
   468  	}
   469  }
   470  
   471  func (s *bootstrapSuite) assertCanLogInAsAdmin(c *gc.C, modelTag names.ModelTag, controllerTag names.ControllerTag, password string) {
   472  	session, err := mongo.DialWithInfo(mongo.MongoInfo{
   473  		Info: mongo.Info{
   474  			Addrs:  []string{s.mgoInst.Addr()},
   475  			CACert: testing.CACert,
   476  		},
   477  		Tag:      nil, // admin user
   478  		Password: password,
   479  	}, mongotest.DialOpts())
   480  	c.Assert(err, jc.ErrorIsNil)
   481  	session.Close()
   482  }
   483  
   484  type fakeProvider struct {
   485  	environs.EnvironProvider
   486  	gitjujutesting.Stub
   487  	environ *fakeEnviron
   488  }
   489  
   490  func (p *fakeProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) {
   491  	p.MethodCall(p, "PrepareConfig", args)
   492  	return args.Config, p.NextErr()
   493  }
   494  
   495  func (p *fakeProvider) Validate(newCfg, oldCfg *config.Config) (*config.Config, error) {
   496  	p.MethodCall(p, "Validate", newCfg, oldCfg)
   497  	return newCfg, p.NextErr()
   498  }
   499  
   500  func (p *fakeProvider) Open(args environs.OpenParams) (environs.Environ, error) {
   501  	p.MethodCall(p, "Open", args)
   502  	p.environ = &fakeEnviron{Stub: &p.Stub, provider: p}
   503  	return p.environ, p.NextErr()
   504  }
   505  
   506  func (p *fakeProvider) Version() int {
   507  	p.MethodCall(p, "Version")
   508  	p.PopNoErr()
   509  	return 123
   510  }
   511  
   512  type fakeEnviron struct {
   513  	environs.Environ
   514  	*gitjujutesting.Stub
   515  	provider *fakeProvider
   516  
   517  	callCtxUsed context.ProviderCallContext
   518  }
   519  
   520  func (e *fakeEnviron) Create(ctx context.ProviderCallContext, args environs.CreateParams) error {
   521  	e.MethodCall(e, "Create", ctx, args)
   522  	e.callCtxUsed = ctx
   523  	return e.NextErr()
   524  }
   525  
   526  func (e *fakeEnviron) Provider() environs.EnvironProvider {
   527  	e.MethodCall(e, "Provider")
   528  	e.PopNoErr()
   529  	return e.provider
   530  }