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