github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"github.com/golang/mock/gomock"
     8  	"github.com/juju/cmd/cmdtesting"
     9  	"github.com/juju/errors"
    10  	gitjujutesting "github.com/juju/testing"
    11  	jc "github.com/juju/testing/checkers"
    12  	"github.com/lxc/lxd/shared/api"
    13  	gc "gopkg.in/check.v1"
    14  	"gopkg.in/juju/charm.v6"
    15  
    16  	"github.com/juju/juju/cloudconfig/instancecfg"
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  	"github.com/juju/juju/core/instance"
    19  	"github.com/juju/juju/core/lxdprofile"
    20  	"github.com/juju/juju/environs"
    21  	"github.com/juju/juju/environs/context"
    22  	envtesting "github.com/juju/juju/environs/testing"
    23  	"github.com/juju/juju/provider/lxd"
    24  	coretesting "github.com/juju/juju/testing"
    25  )
    26  
    27  var errTestUnAuth = errors.New("not authorized")
    28  
    29  type environSuite struct {
    30  	lxd.BaseSuite
    31  
    32  	callCtx           context.ProviderCallContext
    33  	invalidCredential bool
    34  }
    35  
    36  var _ = gc.Suite(&environSuite{})
    37  
    38  func (s *environSuite) SetUpTest(c *gc.C) {
    39  	s.BaseSuite.SetUpTest(c)
    40  	s.callCtx = &context.CloudCallContext{
    41  		InvalidateCredentialFunc: func(string) error {
    42  			s.invalidCredential = true
    43  			return nil
    44  		},
    45  	}
    46  }
    47  
    48  func (s *environSuite) TearDownTest(c *gc.C) {
    49  	s.invalidCredential = false
    50  	s.BaseSuite.TearDownTest(c)
    51  }
    52  
    53  func (s *environSuite) TestName(c *gc.C) {
    54  	c.Check(s.Env.Name(), gc.Equals, "lxd")
    55  }
    56  
    57  func (s *environSuite) TestProvider(c *gc.C) {
    58  	c.Assert(s.Env.Provider(), gc.Equals, s.Provider)
    59  }
    60  
    61  func (s *environSuite) TestSetConfigOkay(c *gc.C) {
    62  	err := s.Env.SetConfig(s.Config)
    63  	c.Assert(err, jc.ErrorIsNil)
    64  
    65  	c.Check(lxd.ExposeEnvConfig(s.Env), jc.DeepEquals, s.EnvConfig)
    66  	// Ensure the client did not change.
    67  	c.Check(lxd.ExposeEnvServer(s.Env), gc.Equals, s.Client)
    68  }
    69  
    70  func (s *environSuite) TestSetConfigNoAPI(c *gc.C) {
    71  	err := s.Env.SetConfig(s.Config)
    72  
    73  	c.Assert(err, jc.ErrorIsNil)
    74  }
    75  
    76  func (s *environSuite) TestConfig(c *gc.C) {
    77  	cfg := s.Env.Config()
    78  
    79  	c.Check(cfg, jc.DeepEquals, s.Config)
    80  }
    81  
    82  func (s *environSuite) TestBootstrapOkay(c *gc.C) {
    83  	s.Common.BootstrapResult = &environs.BootstrapResult{
    84  		Arch:   "amd64",
    85  		Series: "trusty",
    86  		CloudBootstrapFinalizer: func(environs.BootstrapContext, *instancecfg.InstanceConfig, environs.BootstrapDialOpts) error {
    87  			return nil
    88  		},
    89  	}
    90  
    91  	ctx := cmdtesting.Context(c)
    92  	params := environs.BootstrapParams{
    93  		ControllerConfig: coretesting.FakeControllerConfig(),
    94  	}
    95  	result, err := s.Env.Bootstrap(modelcmd.BootstrapContext(ctx), s.callCtx, params)
    96  	c.Assert(err, jc.ErrorIsNil)
    97  
    98  	c.Check(result.Arch, gc.Equals, "amd64")
    99  	c.Check(result.Series, gc.Equals, "trusty")
   100  	// We don't check bsFinalizer because functions cannot be compared.
   101  	c.Check(result.CloudBootstrapFinalizer, gc.NotNil)
   102  
   103  	out := cmdtesting.Stderr(ctx)
   104  	c.Assert(out, gc.Equals, "To configure your system to better support LXD containers, please see: https://github.com/lxc/lxd/blob/master/doc/production-setup.md\n")
   105  }
   106  
   107  func (s *environSuite) TestBootstrapAPI(c *gc.C) {
   108  	ctx := envtesting.BootstrapContext(c)
   109  	params := environs.BootstrapParams{
   110  		ControllerConfig: coretesting.FakeControllerConfig(),
   111  	}
   112  	_, err := s.Env.Bootstrap(ctx, s.callCtx, params)
   113  	c.Assert(err, jc.ErrorIsNil)
   114  
   115  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{{
   116  		FuncName: "Bootstrap",
   117  		Args: []interface{}{
   118  			ctx,
   119  			s.callCtx,
   120  			params,
   121  		},
   122  	}})
   123  }
   124  
   125  func (s *environSuite) TestDestroy(c *gc.C) {
   126  	s.Client.Volumes = map[string][]api.StorageVolume{
   127  		"juju": {{
   128  			Name: "not-ours",
   129  			StorageVolumePut: api.StorageVolumePut{
   130  				Config: map[string]string{
   131  					"user.juju-model-uuid": "other",
   132  				},
   133  			},
   134  		}, {
   135  			Name: "ours",
   136  			StorageVolumePut: api.StorageVolumePut{
   137  				Config: map[string]string{
   138  					"user.juju-model-uuid": s.Config.UUID(),
   139  				},
   140  			},
   141  		}},
   142  	}
   143  
   144  	err := s.Env.Destroy(s.callCtx)
   145  	c.Assert(err, jc.ErrorIsNil)
   146  
   147  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   148  		{"Destroy", []interface{}{s.callCtx}},
   149  		{"StorageSupported", nil},
   150  		{"GetStoragePools", nil},
   151  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   152  		{"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}},
   153  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   154  	})
   155  }
   156  
   157  func (s *environSuite) TestDestroyInvalidCredentials(c *gc.C) {
   158  	c.Assert(s.invalidCredential, jc.IsFalse)
   159  	s.Client.Stub.SetErrors(errTestUnAuth)
   160  	err := s.Env.Destroy(s.callCtx)
   161  	c.Assert(err, gc.ErrorMatches, "not authorized")
   162  	c.Assert(s.invalidCredential, jc.IsTrue)
   163  }
   164  
   165  func (s *environSuite) TestDestroyInvalidCredentialsDestroyingFileSystems(c *gc.C) {
   166  	c.Assert(s.invalidCredential, jc.IsFalse)
   167  	// DeleteStoragePoolVolume will error w/ un-auth.
   168  	s.Client.Stub.SetErrors(nil, nil, nil, errTestUnAuth)
   169  
   170  	s.Client.Volumes = map[string][]api.StorageVolume{
   171  		"juju": {{
   172  			Name: "ours",
   173  			StorageVolumePut: api.StorageVolumePut{
   174  				Config: map[string]string{
   175  					"user.juju-model-uuid": s.Config.UUID(),
   176  				},
   177  			},
   178  		}},
   179  	}
   180  	err := s.Env.Destroy(s.callCtx)
   181  	c.Assert(err, gc.ErrorMatches, ".* not authorized")
   182  	c.Assert(s.invalidCredential, jc.IsTrue)
   183  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   184  		{"Destroy", []interface{}{s.callCtx}},
   185  		{"StorageSupported", nil},
   186  		{"GetStoragePools", nil},
   187  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   188  		{"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}},
   189  	})
   190  }
   191  
   192  func (s *environSuite) TestDestroyController(c *gc.C) {
   193  	s.UpdateConfig(c, map[string]interface{}{
   194  		"controller-uuid": s.Config.UUID(),
   195  	})
   196  	s.Stub.ResetCalls()
   197  
   198  	s.Client.Volumes = map[string][]api.StorageVolume{
   199  		"juju": {{
   200  			Name: "not-ours",
   201  			StorageVolumePut: api.StorageVolumePut{
   202  				Config: map[string]string{
   203  					"user.juju-controller-uuid": "other",
   204  				},
   205  			},
   206  		}, {
   207  			Name: "ours",
   208  			StorageVolumePut: api.StorageVolumePut{
   209  				Config: map[string]string{
   210  					"user.juju-controller-uuid": s.Config.UUID(),
   211  				},
   212  			},
   213  		}},
   214  	}
   215  
   216  	// machine0 is in the controller model.
   217  	machine0 := s.NewContainer(c, "juju-controller-machine-0")
   218  	machine0.Config["user.juju-model-uuid"] = s.Config.UUID()
   219  	machine0.Config["user.juju-controller-uuid"] = s.Config.UUID()
   220  
   221  	// machine1 is not in the controller model, but managed
   222  	// by the same controller.
   223  	machine1 := s.NewContainer(c, "juju-hosted-machine-1")
   224  	machine1.Config["user.juju-model-uuid"] = "not-" + s.Config.UUID()
   225  	machine1.Config["user.juju-controller-uuid"] = s.Config.UUID()
   226  
   227  	// machine2 is not managed by the same controller.
   228  	machine2 := s.NewContainer(c, "juju-controller-machine-2")
   229  	machine2.Config["user.juju-model-uuid"] = "not-" + s.Config.UUID()
   230  	machine2.Config["user.juju-controller-uuid"] = "not-" + s.Config.UUID()
   231  
   232  	s.Client.Containers = append(s.Client.Containers, *machine0, *machine1, *machine2)
   233  
   234  	err := s.Env.DestroyController(s.callCtx, s.Config.UUID())
   235  	c.Assert(err, jc.ErrorIsNil)
   236  
   237  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   238  		{"Destroy", []interface{}{s.callCtx}},
   239  		{"StorageSupported", nil},
   240  		{"GetStoragePools", nil},
   241  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   242  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   243  		{"AliveContainers", []interface{}{"juju-"}},
   244  		{"RemoveContainers", []interface{}{[]string{machine1.Name}}},
   245  		{"StorageSupported", nil},
   246  		{"GetStoragePools", nil},
   247  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   248  		{"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}},
   249  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   250  	})
   251  }
   252  
   253  func (s *environSuite) TestDestroyControllerInvalidCredentialsHostedModels(c *gc.C) {
   254  	c.Assert(s.invalidCredential, jc.IsFalse)
   255  	s.UpdateConfig(c, map[string]interface{}{
   256  		"controller-uuid": s.Config.UUID(),
   257  	})
   258  	s.Stub.ResetCalls()
   259  
   260  	s.Client.Volumes = map[string][]api.StorageVolume{
   261  		"juju": {{
   262  			Name: "ours",
   263  			StorageVolumePut: api.StorageVolumePut{
   264  				Config: map[string]string{
   265  					"user.juju-controller-uuid": s.Config.UUID(),
   266  				},
   267  			},
   268  		}},
   269  	}
   270  
   271  	// machine0 is in the controller model.
   272  	machine0 := s.NewContainer(c, "juju-controller-machine-0")
   273  	machine0.Config["user.juju-model-uuid"] = s.Config.UUID()
   274  	machine0.Config["user.juju-controller-uuid"] = s.Config.UUID()
   275  
   276  	s.Client.Containers = append(s.Client.Containers, *machine0)
   277  
   278  	// RemoveContainers will error not-auth.
   279  	s.Client.Stub.SetErrors(nil, nil, nil, nil, nil, errTestUnAuth)
   280  
   281  	err := s.Env.DestroyController(s.callCtx, s.Config.UUID())
   282  	c.Assert(err, gc.ErrorMatches, "not authorized")
   283  	c.Assert(s.invalidCredential, jc.IsTrue)
   284  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   285  		{"Destroy", []interface{}{s.callCtx}},
   286  		{"StorageSupported", nil},
   287  		{"GetStoragePools", nil},
   288  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   289  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   290  		{"AliveContainers", []interface{}{"juju-"}},
   291  		{"RemoveContainers", []interface{}{[]string{}}},
   292  	})
   293  	s.Stub.CheckCallNames(c,
   294  		"Destroy",
   295  		"StorageSupported",
   296  		"GetStoragePools",
   297  		"GetStoragePoolVolumes",
   298  		"GetStoragePoolVolumes",
   299  		"AliveContainers",
   300  		"RemoveContainers")
   301  }
   302  
   303  func (s *environSuite) TestDestroyControllerInvalidCredentialsDestroyFilesystem(c *gc.C) {
   304  	c.Assert(s.invalidCredential, jc.IsFalse)
   305  	s.UpdateConfig(c, map[string]interface{}{
   306  		"controller-uuid": s.Config.UUID(),
   307  	})
   308  	s.Stub.ResetCalls()
   309  
   310  	s.Client.Volumes = map[string][]api.StorageVolume{
   311  		"juju": {{
   312  			Name: "ours",
   313  			StorageVolumePut: api.StorageVolumePut{
   314  				Config: map[string]string{
   315  					"user.juju-controller-uuid": s.Config.UUID(),
   316  				},
   317  			},
   318  		}},
   319  	}
   320  
   321  	// machine0 is in the controller model.
   322  	machine0 := s.NewContainer(c, "juju-controller-machine-0")
   323  	machine0.Config["user.juju-model-uuid"] = s.Config.UUID()
   324  	machine0.Config["user.juju-controller-uuid"] = s.Config.UUID()
   325  
   326  	s.Client.Containers = append(s.Client.Containers, *machine0)
   327  
   328  	// RemoveContainers will error not-auth.
   329  	s.Client.Stub.SetErrors(nil, nil, nil, nil, nil, nil, nil, nil, errTestUnAuth)
   330  
   331  	err := s.Env.DestroyController(s.callCtx, s.Config.UUID())
   332  	c.Assert(err, gc.ErrorMatches, ".*not authorized")
   333  	c.Assert(s.invalidCredential, jc.IsTrue)
   334  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   335  		{"Destroy", []interface{}{s.callCtx}},
   336  		{"StorageSupported", nil},
   337  		{"GetStoragePools", nil},
   338  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   339  		{"GetStoragePoolVolumes", []interface{}{"juju-zfs"}},
   340  		{"AliveContainers", []interface{}{"juju-"}},
   341  		{"RemoveContainers", []interface{}{[]string{}}},
   342  		{"StorageSupported", nil},
   343  		{"GetStoragePools", nil},
   344  		{"GetStoragePoolVolumes", []interface{}{"juju"}},
   345  		{"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}},
   346  	})
   347  }
   348  
   349  func (s *environSuite) TestAvailabilityZonesInvalidCredentials(c *gc.C) {
   350  	c.Assert(s.invalidCredential, jc.IsFalse)
   351  	// GetClusterMembers will return un-auth error
   352  	s.Client.Stub.SetErrors(errTestUnAuth)
   353  	_, err := s.Env.AvailabilityZones(s.callCtx)
   354  	c.Assert(err, gc.ErrorMatches, ".*not authorized")
   355  	c.Assert(s.invalidCredential, jc.IsTrue)
   356  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   357  		{"IsClustered", nil},
   358  		{"GetClusterMembers", nil},
   359  	})
   360  }
   361  
   362  func (s *environSuite) TestInstanceAvailabilityZoneNamesInvalidCredentials(c *gc.C) {
   363  	c.Assert(s.invalidCredential, jc.IsFalse)
   364  	// AliveContainers will return un-auth error
   365  	s.Client.Stub.SetErrors(errTestUnAuth)
   366  
   367  	// the call to Instances takes care of updating invalid credential details
   368  	_, err := s.Env.InstanceAvailabilityZoneNames(s.callCtx, []instance.Id{"not-valid"})
   369  	c.Assert(err, gc.ErrorMatches, ".*not authorized")
   370  	c.Assert(s.invalidCredential, jc.IsTrue)
   371  	s.Stub.CheckCalls(c, []gitjujutesting.StubCall{
   372  		{"AliveContainers", []interface{}{s.Prefix()}},
   373  	})
   374  }
   375  
   376  type environProfileSuite struct {
   377  	lxd.EnvironSuite
   378  
   379  	callCtx context.ProviderCallContext
   380  }
   381  
   382  var _ = gc.Suite(&environProfileSuite{})
   383  
   384  func (s *environProfileSuite) TestMaybeWriteLXDProfile(c *gc.C) {
   385  	ctrl := gomock.NewController(c)
   386  	defer ctrl.Finish()
   387  
   388  	svr := lxd.NewMockServer(ctrl)
   389  	exp := svr.EXPECT()
   390  	gomock.InOrder(
   391  		exp.HasProfile("testname").Return(true, nil),
   392  		exp.HasProfile("testname").Return(false, nil),
   393  		exp.CreateProfile(api.ProfilesPost{
   394  			Name: "testname",
   395  			ProfilePut: api.ProfilePut{
   396  				Config: map[string]string{
   397  					"security.nesting": "true",
   398  				},
   399  				Description: "test profile",
   400  			},
   401  		}).Return(nil),
   402  	)
   403  
   404  	env := s.NewEnviron(c, svr, nil)
   405  	lxdEnv, ok := env.(environs.LXDProfiler)
   406  	c.Assert(ok, jc.IsTrue)
   407  	err := lxdEnv.MaybeWriteLXDProfile("testname", nil)
   408  	c.Assert(err, jc.ErrorIsNil)
   409  	err = lxdEnv.MaybeWriteLXDProfile("testname", &charm.LXDProfile{
   410  		Config: map[string]string{
   411  			"security.nesting": "true",
   412  		},
   413  		Description: "test profile",
   414  	})
   415  	c.Assert(err, jc.ErrorIsNil)
   416  }
   417  
   418  func (s *environProfileSuite) TestLXDProfileNames(c *gc.C) {
   419  	ctrl := gomock.NewController(c)
   420  	defer ctrl.Finish()
   421  
   422  	svr := lxd.NewMockServer(ctrl)
   423  	exp := svr.EXPECT()
   424  
   425  	exp.GetContainerProfiles("testname").Return([]string{
   426  		lxdprofile.Name("foo", "bar", 1),
   427  	}, nil)
   428  
   429  	env := s.NewEnviron(c, svr, nil)
   430  	lxdEnv, ok := env.(environs.LXDProfiler)
   431  	c.Assert(ok, jc.IsTrue)
   432  	result, err := lxdEnv.LXDProfileNames("testname")
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	c.Assert(result, jc.DeepEquals, []string{
   435  		lxdprofile.Name("foo", "bar", 1),
   436  	})
   437  }
   438  
   439  func (s *environProfileSuite) TestReplaceOrAddInstanceProfile(c *gc.C) {
   440  	ctrl := gomock.NewController(c)
   441  	defer ctrl.Finish()
   442  
   443  	instId := "testme"
   444  	old := "old-profile"
   445  	new := "new-profile"
   446  
   447  	svr := lxd.NewMockServer(ctrl)
   448  	exp := svr.EXPECT()
   449  	gomock.InOrder(
   450  		exp.HasProfile(new).Return(false, nil),
   451  		exp.CreateProfile(api.ProfilesPost{
   452  			Name: new,
   453  			ProfilePut: api.ProfilePut{
   454  				Config: map[string]string{
   455  					"security.nesting": "true",
   456  				},
   457  				Description: "test profile",
   458  			},
   459  		}).Return(nil),
   460  		exp.ReplaceOrAddContainerProfile(instId, old, new).Return(nil),
   461  		exp.DeleteProfile(old),
   462  		exp.GetContainerProfiles(instId).Return([]string{"default", "juju-default", new}, nil),
   463  	)
   464  
   465  	env := s.NewEnviron(c, svr, nil)
   466  	lxdEnv, ok := env.(environs.LXDProfiler)
   467  	c.Assert(ok, jc.IsTrue)
   468  	put := &charm.LXDProfile{
   469  		Config: map[string]string{
   470  			"security.nesting": "true",
   471  		},
   472  		Description: "test profile",
   473  	}
   474  	obtained, err := lxdEnv.ReplaceOrAddInstanceProfile(instId, old, new, put)
   475  	c.Assert(err, jc.ErrorIsNil)
   476  	c.Assert(obtained, gc.DeepEquals, []string{"default", "juju-default", new})
   477  }