github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/lxd/environ_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxd_test
     5  
     6  import (
     7  	"context"
     8  	stdcontext "context"
     9  
    10  	"github.com/canonical/lxd/shared/api"
    11  	"github.com/juju/cmd/v3/cmdtesting"
    12  	"github.com/juju/errors"
    13  	gitjujutesting "github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	"go.uber.org/mock/gomock"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/cloudconfig/instancecfg"
    19  	"github.com/juju/juju/cmd/modelcmd"
    20  	corebase "github.com/juju/juju/core/base"
    21  	"github.com/juju/juju/core/instance"
    22  	"github.com/juju/juju/core/lxdprofile"
    23  	"github.com/juju/juju/environs"
    24  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    25  	envcontext "github.com/juju/juju/environs/context"
    26  	envtesting "github.com/juju/juju/environs/testing"
    27  	"github.com/juju/juju/provider/lxd"
    28  	coretesting "github.com/juju/juju/testing"
    29  )
    30  
    31  var errTestUnAuth = errors.New("not authorized")
    32  
    33  type environSuite struct {
    34  	lxd.BaseSuite
    35  
    36  	callCtx           envcontext.ProviderCallContext
    37  	invalidCredential bool
    38  }
    39  
    40  var _ = gc.Suite(&environSuite{})
    41  
    42  func (s *environSuite) SetUpTest(c *gc.C) {
    43  	s.BaseSuite.SetUpTest(c)
    44  	s.callCtx = &envcontext.CloudCallContext{
    45  		InvalidateCredentialFunc: func(string) error {
    46  			s.invalidCredential = true
    47  			return nil
    48  		},
    49  	}
    50  }
    51  
    52  func (s *environSuite) TearDownTest(c *gc.C) {
    53  	s.invalidCredential = false
    54  	s.BaseSuite.TearDownTest(c)
    55  }
    56  
    57  func (s *environSuite) TestName(c *gc.C) {
    58  	c.Check(s.Env.Name(), gc.Equals, "lxd")
    59  }
    60  
    61  func (s *environSuite) TestProvider(c *gc.C) {
    62  	c.Assert(s.Env.Provider(), gc.Equals, s.Provider)
    63  }
    64  
    65  func (s *environSuite) TestSetConfigOkay(c *gc.C) {
    66  	err := s.Env.SetConfig(s.Config)
    67  	c.Assert(err, jc.ErrorIsNil)
    68  
    69  	c.Check(lxd.ExposeEnvConfig(s.Env), jc.DeepEquals, s.EnvConfig)
    70  	// Ensure the client did not change.
    71  	c.Check(lxd.ExposeEnvServer(s.Env), gc.Equals, s.Client)
    72  }
    73  
    74  func (s *environSuite) TestSetConfigNoAPI(c *gc.C) {
    75  	err := s.Env.SetConfig(s.Config)
    76  
    77  	c.Assert(err, jc.ErrorIsNil)
    78  }
    79  
    80  func (s *environSuite) TestConfig(c *gc.C) {
    81  	cfg := s.Env.Config()
    82  
    83  	c.Check(cfg, jc.DeepEquals, s.Config)
    84  }
    85  
    86  func (s *environSuite) TestBootstrapOkay(c *gc.C) {
    87  	s.Common.BootstrapResult = &environs.BootstrapResult{
    88  		Arch: "amd64",
    89  		Base: corebase.MakeDefaultBase("ubuntu", "22.04"),
    90  		CloudBootstrapFinalizer: func(environs.BootstrapContext, *instancecfg.InstanceConfig, environs.BootstrapDialOpts) error {
    91  			return nil
    92  		},
    93  	}
    94  
    95  	ctx := cmdtesting.Context(c)
    96  	params := environs.BootstrapParams{
    97  		ControllerConfig:         coretesting.FakeControllerConfig(),
    98  		SupportedBootstrapSeries: coretesting.FakeSupportedJujuSeries,
    99  	}
   100  	result, err := s.Env.Bootstrap(modelcmd.BootstrapContext(context.Background(), ctx), s.callCtx, params)
   101  	c.Assert(err, jc.ErrorIsNil)
   102  
   103  	c.Check(result.Arch, gc.Equals, "amd64")
   104  	c.Check(result.Base.DisplayString(), gc.Equals, "ubuntu@22.04")
   105  	// We don't check bsFinalizer because functions cannot be compared.
   106  	c.Check(result.CloudBootstrapFinalizer, gc.NotNil)
   107  
   108  	out := cmdtesting.Stderr(ctx)
   109  	c.Assert(out, gc.Matches, "To configure your system to better support LXD containers, please see: .*\n")
   110  }
   111  
   112  func (s *environSuite) TestBootstrapAPI(c *gc.C) {
   113  	ctx := envtesting.BootstrapContext(context.TODO(), c)
   114  	params := environs.BootstrapParams{
   115  		ControllerConfig:         coretesting.FakeControllerConfig(),
   116  		SupportedBootstrapSeries: coretesting.FakeSupportedJujuSeries,
   117  	}
   118  	_, err := s.Env.Bootstrap(ctx, s.callCtx, params)
   119  	c.Assert(err, jc.ErrorIsNil)
   120  
   121  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{{
   122  		FuncName: "Bootstrap",
   123  		Args: []interface{}{
   124  			ctx,
   125  			s.callCtx,
   126  			params,
   127  		},
   128  	}})
   129  }
   130  
   131  func (s *environSuite) TestDestroy(c *gc.C) {
   132  	s.Client.Volumes = map[string][]api.StorageVolume{
   133  		"juju": {{
   134  			Name: "not-ours",
   135  			StorageVolumePut: api.StorageVolumePut{
   136  				Config: map[string]string{
   137  					"user.juju-model-uuid": "other",
   138  				},
   139  			},
   140  		}, {
   141  			Name: "ours",
   142  			StorageVolumePut: api.StorageVolumePut{
   143  				Config: map[string]string{
   144  					"user.juju-model-uuid": s.Config.UUID(),
   145  				},
   146  			},
   147  		}},
   148  	}
   149  
   150  	err := s.Env.Destroy(s.callCtx)
   151  	c.Assert(err, jc.ErrorIsNil)
   152  
   153  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   154  		{"Destroy", []interface{}{s.callCtx}},
   155  		{"StorageSupported", nil},
   156  		{"GetStoragePools", nil},
   157  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   158  		{"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}},
   159  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   160  	})
   161  }
   162  
   163  func (s *environSuite) TestDestroyInvalidCredentials(c *gc.C) {
   164  	c.Assert(s.invalidCredential, jc.IsFalse)
   165  	s.Client.Stub.SetErrors(errTestUnAuth)
   166  	err := s.Env.Destroy(s.callCtx)
   167  	c.Assert(err, gc.ErrorMatches, "not authorized")
   168  	c.Assert(s.invalidCredential, jc.IsTrue)
   169  }
   170  
   171  func (s *environSuite) TestDestroyInvalidCredentialsDestroyingFileSystems(c *gc.C) {
   172  	c.Assert(s.invalidCredential, jc.IsFalse)
   173  	// DeleteStoragePoolVolume will error w/ un-auth.
   174  	s.Client.Stub.SetErrors(nil, nil, nil, errTestUnAuth)
   175  
   176  	s.Client.Volumes = map[string][]api.StorageVolume{
   177  		"juju": {{
   178  			Name: "ours",
   179  			StorageVolumePut: api.StorageVolumePut{
   180  				Config: map[string]string{
   181  					"user.juju-model-uuid": s.Config.UUID(),
   182  				},
   183  			},
   184  		}},
   185  	}
   186  	err := s.Env.Destroy(s.callCtx)
   187  	c.Assert(err, gc.ErrorMatches, ".* not authorized")
   188  	c.Assert(s.invalidCredential, jc.IsTrue)
   189  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   190  		{"Destroy", []interface{}{s.callCtx}},
   191  		{"StorageSupported", nil},
   192  		{"GetStoragePools", nil},
   193  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   194  		{"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}},
   195  	})
   196  }
   197  
   198  func (s *environSuite) TestDestroyController(c *gc.C) {
   199  	s.UpdateConfig(c, map[string]interface{}{
   200  		"controller-uuid": s.Config.UUID(),
   201  	})
   202  	s.Stub.ResetCalls()
   203  
   204  	s.Client.Volumes = map[string][]api.StorageVolume{
   205  		"juju": {{
   206  			Name: "not-ours",
   207  			StorageVolumePut: api.StorageVolumePut{
   208  				Config: map[string]string{
   209  					"user.juju-controller-uuid": "other",
   210  				},
   211  			},
   212  		}, {
   213  			Name: "ours",
   214  			StorageVolumePut: api.StorageVolumePut{
   215  				Config: map[string]string{
   216  					"user.juju-controller-uuid": s.Config.UUID(),
   217  				},
   218  			},
   219  		}},
   220  	}
   221  
   222  	// machine0 is in the controller model.
   223  	machine0 := s.NewContainer(c, "juju-controller-machine-0")
   224  	machine0.Config["user.juju-model-uuid"] = s.Config.UUID()
   225  	machine0.Config["user.juju-controller-uuid"] = s.Config.UUID()
   226  
   227  	// machine1 is not in the controller model, but managed
   228  	// by the same controller.
   229  	machine1 := s.NewContainer(c, "juju-hosted-machine-1")
   230  	machine1.Config["user.juju-model-uuid"] = "not-" + s.Config.UUID()
   231  	machine1.Config["user.juju-controller-uuid"] = s.Config.UUID()
   232  
   233  	// machine2 is not managed by the same controller.
   234  	machine2 := s.NewContainer(c, "juju-controller-machine-2")
   235  	machine2.Config["user.juju-model-uuid"] = "not-" + s.Config.UUID()
   236  	machine2.Config["user.juju-controller-uuid"] = "not-" + s.Config.UUID()
   237  
   238  	s.Client.Containers = append(s.Client.Containers, *machine0, *machine1, *machine2)
   239  
   240  	err := s.Env.DestroyController(s.callCtx, s.Config.UUID())
   241  	c.Assert(err, jc.ErrorIsNil)
   242  
   243  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   244  		{"Destroy", []interface{}{s.callCtx}},
   245  		{"StorageSupported", nil},
   246  		{"GetStoragePools", nil},
   247  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   248  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   249  		{"AliveContainers", []interface{}{"juju-"}},
   250  		{"RemoveContainers", []interface{}{[]string{machine1.Name}}},
   251  		{"StorageSupported", nil},
   252  		{"GetStoragePools", nil},
   253  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   254  		{"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}},
   255  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   256  	})
   257  }
   258  
   259  func (s *environSuite) TestDestroyControllerInvalidCredentialsHostedModels(c *gc.C) {
   260  	c.Assert(s.invalidCredential, jc.IsFalse)
   261  	s.UpdateConfig(c, map[string]interface{}{
   262  		"controller-uuid": s.Config.UUID(),
   263  	})
   264  	s.Stub.ResetCalls()
   265  
   266  	s.Client.Volumes = map[string][]api.StorageVolume{
   267  		"juju": {{
   268  			Name: "ours",
   269  			StorageVolumePut: api.StorageVolumePut{
   270  				Config: map[string]string{
   271  					"user.juju-controller-uuid": s.Config.UUID(),
   272  				},
   273  			},
   274  		}},
   275  	}
   276  
   277  	// machine0 is in the controller model.
   278  	machine0 := s.NewContainer(c, "juju-controller-machine-0")
   279  	machine0.Config["user.juju-model-uuid"] = s.Config.UUID()
   280  	machine0.Config["user.juju-controller-uuid"] = s.Config.UUID()
   281  
   282  	s.Client.Containers = append(s.Client.Containers, *machine0)
   283  
   284  	// RemoveContainers will error not-auth.
   285  	s.Client.Stub.SetErrors(nil, nil, nil, nil, nil, errTestUnAuth)
   286  
   287  	err := s.Env.DestroyController(s.callCtx, s.Config.UUID())
   288  	c.Assert(err, gc.ErrorMatches, "not authorized")
   289  	c.Assert(s.invalidCredential, jc.IsTrue)
   290  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   291  		{"Destroy", []interface{}{s.callCtx}},
   292  		{"StorageSupported", nil},
   293  		{"GetStoragePools", nil},
   294  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   295  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   296  		{"AliveContainers", []interface{}{"juju-"}},
   297  		{"RemoveContainers", []interface{}{[]string{}}},
   298  	})
   299  	s.Stub.CheckCallNames(c,
   300  		"Destroy",
   301  		"StorageSupported",
   302  		"GetStoragePools",
   303  		"GetStoragePoolVolumes",
   304  		"GetStoragePoolVolumes",
   305  		"AliveContainers",
   306  		"RemoveContainers")
   307  }
   308  
   309  func (s *environSuite) TestDestroyControllerInvalidCredentialsDestroyFilesystem(c *gc.C) {
   310  	c.Assert(s.invalidCredential, jc.IsFalse)
   311  	s.UpdateConfig(c, map[string]interface{}{
   312  		"controller-uuid": s.Config.UUID(),
   313  	})
   314  	s.Stub.ResetCalls()
   315  
   316  	s.Client.Volumes = map[string][]api.StorageVolume{
   317  		"juju": {{
   318  			Name: "ours",
   319  			StorageVolumePut: api.StorageVolumePut{
   320  				Config: map[string]string{
   321  					"user.juju-controller-uuid": s.Config.UUID(),
   322  				},
   323  			},
   324  		}},
   325  	}
   326  
   327  	// machine0 is in the controller model.
   328  	machine0 := s.NewContainer(c, "juju-controller-machine-0")
   329  	machine0.Config["user.juju-model-uuid"] = s.Config.UUID()
   330  	machine0.Config["user.juju-controller-uuid"] = s.Config.UUID()
   331  
   332  	s.Client.Containers = append(s.Client.Containers, *machine0)
   333  
   334  	// RemoveContainers will error not-auth.
   335  	s.Client.Stub.SetErrors(nil, nil, nil, nil, nil, nil, nil, nil, errTestUnAuth)
   336  
   337  	err := s.Env.DestroyController(s.callCtx, s.Config.UUID())
   338  	c.Assert(err, gc.ErrorMatches, ".*not authorized")
   339  	c.Assert(s.invalidCredential, jc.IsTrue)
   340  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   341  		{"Destroy", []interface{}{s.callCtx}},
   342  		{"StorageSupported", nil},
   343  		{"GetStoragePools", nil},
   344  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   345  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   346  		{"AliveContainers", []interface{}{"juju-"}},
   347  		{"RemoveContainers", []interface{}{[]string{}}},
   348  		{"StorageSupported", nil},
   349  		{"GetStoragePools", nil},
   350  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   351  		{"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}},
   352  	})
   353  }
   354  
   355  func (s *environSuite) TestAvailabilityZonesInvalidCredentials(c *gc.C) {
   356  	c.Assert(s.invalidCredential, jc.IsFalse)
   357  	// GetClusterMembers will return un-auth error
   358  	s.Client.Stub.SetErrors(errTestUnAuth)
   359  	_, err := s.Env.AvailabilityZones(s.callCtx)
   360  	c.Assert(err, gc.ErrorMatches, ".*not authorized")
   361  	c.Assert(s.invalidCredential, jc.IsTrue)
   362  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   363  		{"IsClustered", nil},
   364  		{"GetClusterMembers", nil},
   365  	})
   366  }
   367  
   368  func (s *environSuite) TestInstanceAvailabilityZoneNamesInvalidCredentials(c *gc.C) {
   369  	c.Assert(s.invalidCredential, jc.IsFalse)
   370  	// AliveContainers will return un-auth error
   371  	s.Client.Stub.SetErrors(errTestUnAuth)
   372  
   373  	// the call to Instances takes care of updating invalid credential details
   374  	_, err := s.Env.InstanceAvailabilityZoneNames(s.callCtx, []instance.Id{"not-valid"})
   375  	c.Assert(err, gc.ErrorMatches, ".*not authorized")
   376  	c.Assert(s.invalidCredential, jc.IsTrue)
   377  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   378  		{"AliveContainers", []interface{}{s.Prefix()}},
   379  	})
   380  }
   381  
   382  type environCloudProfileSuite struct {
   383  	lxd.EnvironSuite
   384  
   385  	svr          *lxd.MockServer
   386  	cloudSpecEnv environs.CloudSpecSetter
   387  }
   388  
   389  var _ = gc.Suite(&environCloudProfileSuite{})
   390  
   391  func (s *environCloudProfileSuite) TestSetCloudSpecCreateProfile(c *gc.C) {
   392  	defer s.setup(c, nil).Finish()
   393  	s.expectHasProfileFalse("juju-controller")
   394  	s.expectCreateProfile("juju-controller", nil)
   395  
   396  	err := s.cloudSpecEnv.SetCloudSpec(stdcontext.TODO(), lxdCloudSpec())
   397  	c.Assert(err, jc.ErrorIsNil)
   398  }
   399  
   400  func (s *environCloudProfileSuite) TestSetCloudSpecCreateProfileErrorSucceeds(c *gc.C) {
   401  	defer s.setup(c, nil).Finish()
   402  	s.expectForProfileCreateRace("juju-controller")
   403  	s.expectCreateProfile("juju-controller", errors.New("The profile already exists"))
   404  
   405  	err := s.cloudSpecEnv.SetCloudSpec(stdcontext.TODO(), lxdCloudSpec())
   406  	c.Assert(err, jc.ErrorIsNil)
   407  }
   408  
   409  func (s *environCloudProfileSuite) TestSetCloudSpecUsesConfiguredProject(c *gc.C) {
   410  	defer s.setup(c, map[string]interface{}{"project": "my-project"}).Finish()
   411  	s.expectHasProfileFalse("juju-controller")
   412  	s.expectCreateProfile("juju-controller", nil)
   413  
   414  	err := s.cloudSpecEnv.SetCloudSpec(stdcontext.TODO(), lxdCloudSpec())
   415  	c.Assert(err, jc.ErrorIsNil)
   416  }
   417  
   418  func (s *environCloudProfileSuite) setup(c *gc.C, cfgEdit map[string]interface{}) *gomock.Controller {
   419  	ctrl := gomock.NewController(c)
   420  	s.svr = lxd.NewMockServer(ctrl)
   421  
   422  	project, _ := cfgEdit["project"].(string)
   423  	cloudSpec := lxd.CloudSpec{
   424  		CloudSpec: lxdCloudSpec(),
   425  		Project:   project,
   426  	}
   427  
   428  	svrFactory := lxd.NewMockServerFactory(ctrl)
   429  	svrFactory.EXPECT().RemoteServer(cloudSpec).Return(s.svr, nil)
   430  
   431  	env, ok := s.NewEnvironWithServerFactory(c, svrFactory, cfgEdit).(environs.CloudSpecSetter)
   432  	c.Assert(ok, jc.IsTrue)
   433  	s.cloudSpecEnv = env
   434  
   435  	return ctrl
   436  }
   437  
   438  func (s *environCloudProfileSuite) expectForProfileCreateRace(name string) {
   439  	exp := s.svr.EXPECT()
   440  	gomock.InOrder(
   441  		exp.HasProfile(name).Return(false, nil),
   442  		exp.HasProfile(name).Return(true, nil),
   443  	)
   444  }
   445  
   446  func (s *environCloudProfileSuite) expectHasProfileFalse(name string) {
   447  	s.svr.EXPECT().HasProfile(name).Return(false, nil)
   448  }
   449  
   450  func (s *environCloudProfileSuite) expectCreateProfile(name string, err error) {
   451  	s.svr.EXPECT().CreateProfileWithConfig(name,
   452  		map[string]string{
   453  			"boot.autostart":   "true",
   454  			"security.nesting": "true",
   455  		}).Return(err)
   456  }
   457  
   458  type environProfileSuite struct {
   459  	lxd.EnvironSuite
   460  
   461  	svr    *lxd.MockServer
   462  	lxdEnv environs.LXDProfiler
   463  }
   464  
   465  var _ = gc.Suite(&environProfileSuite{})
   466  
   467  func (s *environProfileSuite) TestMaybeWriteLXDProfileYes(c *gc.C) {
   468  	defer s.setup(c, environscloudspec.CloudSpec{}).Finish()
   469  
   470  	profile := "testname"
   471  	s.expectMaybeWriteLXDProfile(false, profile)
   472  
   473  	err := s.lxdEnv.MaybeWriteLXDProfile(profile, lxdprofile.Profile{
   474  		Config: map[string]string{
   475  			"security.nesting": "true",
   476  		},
   477  		Description: "test profile",
   478  	})
   479  	c.Assert(err, jc.ErrorIsNil)
   480  }
   481  
   482  func (s *environProfileSuite) TestMaybeWriteLXDProfileNo(c *gc.C) {
   483  	defer s.setup(c, environscloudspec.CloudSpec{}).Finish()
   484  
   485  	profile := "testname"
   486  	s.expectMaybeWriteLXDProfile(true, profile)
   487  
   488  	err := s.lxdEnv.MaybeWriteLXDProfile(profile, lxdprofile.Profile{})
   489  	c.Assert(err, jc.ErrorIsNil)
   490  }
   491  
   492  func (s *environProfileSuite) TestLXDProfileNames(c *gc.C) {
   493  	defer s.setup(c, environscloudspec.CloudSpec{}).Finish()
   494  
   495  	exp := s.svr.EXPECT()
   496  	exp.GetContainerProfiles("testname").Return([]string{
   497  		lxdprofile.Name("foo", "bar", 1),
   498  	}, nil)
   499  
   500  	result, err := s.lxdEnv.LXDProfileNames("testname")
   501  	c.Assert(err, jc.ErrorIsNil)
   502  	c.Assert(result, jc.DeepEquals, []string{
   503  		lxdprofile.Name("foo", "bar", 1),
   504  	})
   505  }
   506  
   507  func (s *environProfileSuite) TestAssignLXDProfiles(c *gc.C) {
   508  	defer s.setup(c, environscloudspec.CloudSpec{}).Finish()
   509  
   510  	instId := "testme"
   511  	oldP := "old-profile"
   512  	newP := "new-profile"
   513  	expectedProfiles := []string{"default", "juju-default", newP}
   514  	s.expectAssignLXDProfiles(instId, oldP, newP, []string{}, expectedProfiles, nil)
   515  
   516  	obtained, err := s.lxdEnv.AssignLXDProfiles(instId, expectedProfiles, []lxdprofile.ProfilePost{
   517  		{
   518  			Name:    oldP,
   519  			Profile: nil,
   520  		}, {
   521  			Name: newP,
   522  			Profile: &lxdprofile.Profile{
   523  				Config: map[string]string{
   524  					"security.nesting": "true",
   525  				},
   526  				Description: "test profile",
   527  			},
   528  		},
   529  	})
   530  	c.Assert(err, jc.ErrorIsNil)
   531  	c.Assert(obtained, gc.DeepEquals, expectedProfiles)
   532  }
   533  
   534  func (s *environProfileSuite) TestAssignLXDProfilesErrorReturnsCurrent(c *gc.C) {
   535  	defer s.setup(c, environscloudspec.CloudSpec{}).Finish()
   536  
   537  	instId := "testme"
   538  	oldP := "old-profile"
   539  	newP := "new-profile"
   540  	expectedProfiles := []string{"default", "juju-default", oldP}
   541  	newProfiles := []string{"default", "juju-default", newP}
   542  	expectedErr := "fail UpdateContainerProfiles"
   543  	s.expectAssignLXDProfiles(instId, oldP, newP, expectedProfiles, newProfiles, errors.New(expectedErr))
   544  
   545  	obtained, err := s.lxdEnv.AssignLXDProfiles(instId, newProfiles, []lxdprofile.ProfilePost{
   546  		{
   547  			Name:    oldP,
   548  			Profile: nil,
   549  		}, {
   550  			Name: newP,
   551  			Profile: &lxdprofile.Profile{
   552  				Config: map[string]string{
   553  					"security.nesting": "true",
   554  				},
   555  				Description: "test profile",
   556  			},
   557  		},
   558  	})
   559  	c.Assert(err, gc.ErrorMatches, expectedErr)
   560  	c.Assert(obtained, gc.DeepEquals, []string{"default", "juju-default", oldP})
   561  }
   562  
   563  func (s *environProfileSuite) TestDetectCorrectHardwareEndpointIPOnly(c *gc.C) {
   564  	defer s.setup(c, environscloudspec.CloudSpec{
   565  		Endpoint: "1.1.1.1",
   566  	}).Finish()
   567  
   568  	detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector)
   569  	c.Assert(supported, jc.IsTrue)
   570  
   571  	hc, err := detector.DetectHardware()
   572  	c.Assert(err, gc.IsNil)
   573  	// 1.1.1.1 is not a local IP address, so we don't set ARCH in hc
   574  	c.Assert(hc, gc.IsNil)
   575  }
   576  
   577  func (s *environProfileSuite) TestDetectCorrectHardwareEndpointIPPort(c *gc.C) {
   578  	defer s.setup(c, environscloudspec.CloudSpec{
   579  		Endpoint: "1.1.1.1:8888",
   580  	}).Finish()
   581  
   582  	detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector)
   583  	c.Assert(supported, jc.IsTrue)
   584  
   585  	hc, err := detector.DetectHardware()
   586  	c.Assert(err, gc.IsNil)
   587  	// 1.1.1.1 is not a local IP address, so we don't set ARCH in hc
   588  	c.Assert(hc, gc.IsNil)
   589  }
   590  
   591  func (s *environProfileSuite) TestDetectCorrectHardwareEndpointSchemeIPPort(c *gc.C) {
   592  	defer s.setup(c, environscloudspec.CloudSpec{
   593  		Endpoint: "http://1.1.1.1:8888",
   594  	}).Finish()
   595  
   596  	detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector)
   597  	c.Assert(supported, jc.IsTrue)
   598  
   599  	hc, err := detector.DetectHardware()
   600  	c.Assert(err, gc.IsNil)
   601  	// 1.1.1.1 is not a local IP address, so we don't set ARCH in hc
   602  	c.Assert(hc, gc.IsNil)
   603  }
   604  
   605  func (s *environProfileSuite) TestDetectCorrectHardwareEndpointHostOnly(c *gc.C) {
   606  	defer s.setup(c, environscloudspec.CloudSpec{
   607  		Endpoint: "localhost",
   608  	}).Finish()
   609  
   610  	detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector)
   611  	c.Assert(supported, jc.IsTrue)
   612  
   613  	hc, err := detector.DetectHardware()
   614  	c.Assert(err, gc.IsNil)
   615  	// 1.1.1.1 is not a local IP address, so we don't set ARCH in hc
   616  	c.Assert(hc, gc.IsNil)
   617  }
   618  
   619  func (s *environProfileSuite) TestDetectCorrectHardwareEndpointHostPort(c *gc.C) {
   620  	defer s.setup(c, environscloudspec.CloudSpec{
   621  		Endpoint: "localhost:8888",
   622  	}).Finish()
   623  
   624  	detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector)
   625  	c.Assert(supported, jc.IsTrue)
   626  
   627  	hc, err := detector.DetectHardware()
   628  	c.Assert(err, gc.IsNil)
   629  	// localhost is not considered as a local IP address, so we don't set ARCH in hc
   630  	c.Assert(hc, gc.IsNil)
   631  }
   632  
   633  func (s *environProfileSuite) TestDetectCorrectHardwareEndpointSchemeHostPort(c *gc.C) {
   634  	defer s.setup(c, environscloudspec.CloudSpec{
   635  		Endpoint: "http://localhost:8888",
   636  	}).Finish()
   637  
   638  	detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector)
   639  	c.Assert(supported, jc.IsTrue)
   640  
   641  	hc, err := detector.DetectHardware()
   642  	c.Assert(err, gc.IsNil)
   643  	// localhost is not considered as a local IP address, so we don't set ARCH in hc
   644  	c.Assert(hc, gc.IsNil)
   645  }
   646  
   647  func (s *environProfileSuite) TestDetectCorrectHardwareWrongEndpoint(c *gc.C) {
   648  	defer s.setup(c, environscloudspec.CloudSpec{
   649  		Endpoint: "1.1:8888",
   650  	}).Finish()
   651  
   652  	detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector)
   653  	c.Assert(supported, jc.IsTrue)
   654  
   655  	hc, err := detector.DetectHardware()
   656  	// the endpoint is wrongly formatted but we don't return an error, that
   657  	// would mean we are stopping the bootstrap
   658  	c.Assert(err, gc.IsNil)
   659  	c.Assert(hc, gc.IsNil)
   660  }
   661  
   662  func (s *environProfileSuite) TestDetectCorrectHardwareEmptyEndpoint(c *gc.C) {
   663  	defer s.setup(c, environscloudspec.CloudSpec{
   664  		Endpoint: "",
   665  	}).Finish()
   666  
   667  	detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector)
   668  	c.Assert(supported, jc.IsTrue)
   669  
   670  	hc, err := detector.DetectHardware()
   671  	// the endpoint is wrongly formatted but we don't return an error, that
   672  	// would mean we are stopping the bootstrap
   673  	c.Assert(err, gc.IsNil)
   674  	c.Assert(hc, gc.IsNil)
   675  }
   676  
   677  func (s *environProfileSuite) setup(c *gc.C, cloudSpec environscloudspec.CloudSpec) *gomock.Controller {
   678  	ctrl := gomock.NewController(c)
   679  	s.svr = lxd.NewMockServer(ctrl)
   680  	lxdEnv, ok := s.NewEnviron(c, s.svr, nil, cloudSpec).(environs.LXDProfiler)
   681  	c.Assert(ok, jc.IsTrue)
   682  	s.lxdEnv = lxdEnv
   683  
   684  	return ctrl
   685  }
   686  
   687  func (s *environProfileSuite) expectMaybeWriteLXDProfile(hasProfile bool, name string) {
   688  	exp := s.svr.EXPECT()
   689  	exp.HasProfile(name).Return(hasProfile, nil)
   690  	if !hasProfile {
   691  		post := api.ProfilesPost{
   692  			Name: name,
   693  			ProfilePut: api.ProfilePut{
   694  				Config: map[string]string{
   695  					"security.nesting": "true",
   696  				},
   697  				Description: "test profile",
   698  			},
   699  		}
   700  		exp.CreateProfile(post).Return(nil)
   701  		expProfile := api.Profile{ProfilePut: post.ProfilePut}
   702  		exp.GetProfile(name).Return(&expProfile, "etag", nil)
   703  	}
   704  }
   705  
   706  func (s *environProfileSuite) expectAssignLXDProfiles(instId, old, new string, oldProfiles, newProfiles []string, updateErr error) {
   707  	s.expectMaybeWriteLXDProfile(false, new)
   708  	exp := s.svr.EXPECT()
   709  	exp.UpdateContainerProfiles(instId, newProfiles).Return(updateErr)
   710  	if updateErr != nil {
   711  		exp.GetContainerProfiles(instId).Return(oldProfiles, nil)
   712  		return
   713  	}
   714  	if old != "" {
   715  		exp.DeleteProfile(old)
   716  	}
   717  	exp.GetContainerProfiles(instId).Return(newProfiles, nil)
   718  }