github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/controller/destroy_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller_test
     5  
     6  import (
     7  	"bytes"
     8  	"time"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	gitjujutesting "github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/api/base"
    18  	"github.com/juju/juju/apiserver/params"
    19  	"github.com/juju/juju/cmd/juju/controller"
    20  	"github.com/juju/juju/cmd/modelcmd"
    21  	cmdtesting "github.com/juju/juju/cmd/testing"
    22  	"github.com/juju/juju/environs/config"
    23  	"github.com/juju/juju/jujuclient"
    24  	"github.com/juju/juju/jujuclient/jujuclienttesting"
    25  	_ "github.com/juju/juju/provider/dummy"
    26  	"github.com/juju/juju/testing"
    27  )
    28  
    29  const (
    30  	test1UUID = "1871299e-1370-4f3e-83ab-1849ed7b1076"
    31  	test2UUID = "c59d0e3b-2bd7-4867-b1b9-f1ef8a0bb004"
    32  	test3UUID = "82bf9738-764b-49c1-9c19-18f6ee155854"
    33  )
    34  
    35  type DestroySuite struct {
    36  	baseDestroySuite
    37  }
    38  
    39  var _ = gc.Suite(&DestroySuite{})
    40  
    41  type baseDestroySuite struct {
    42  	testing.FakeJujuXDGDataHomeSuite
    43  	api       *fakeDestroyAPI
    44  	clientapi *fakeDestroyAPIClient
    45  	store     *jujuclienttesting.MemStore
    46  	apierror  error
    47  }
    48  
    49  // fakeDestroyAPI mocks out the controller API
    50  type fakeDestroyAPI struct {
    51  	gitjujutesting.Stub
    52  	env        map[string]interface{}
    53  	destroyAll bool
    54  	blocks     []params.ModelBlockInfo
    55  	envStatus  map[string]base.ModelStatus
    56  	allEnvs    []base.UserModel
    57  }
    58  
    59  func (f *fakeDestroyAPI) Close() error {
    60  	f.MethodCall(f, "Close")
    61  	return f.NextErr()
    62  }
    63  
    64  func (f *fakeDestroyAPI) ModelConfig() (map[string]interface{}, error) {
    65  	f.MethodCall(f, "ModelConfig")
    66  	if err := f.NextErr(); err != nil {
    67  		return nil, err
    68  	}
    69  	return f.env, nil
    70  }
    71  
    72  func (f *fakeDestroyAPI) DestroyController(destroyAll bool) error {
    73  	f.MethodCall(f, "DestroyController", destroyAll)
    74  	f.destroyAll = destroyAll
    75  	return f.NextErr()
    76  }
    77  
    78  func (f *fakeDestroyAPI) ListBlockedModels() ([]params.ModelBlockInfo, error) {
    79  	f.MethodCall(f, "ListBlockedModels")
    80  	return f.blocks, f.NextErr()
    81  }
    82  
    83  func (f *fakeDestroyAPI) ModelStatus(tags ...names.ModelTag) ([]base.ModelStatus, error) {
    84  	f.MethodCall(f, "ModelStatus", tags)
    85  	status := make([]base.ModelStatus, len(tags))
    86  	for i, tag := range tags {
    87  		status[i] = f.envStatus[tag.Id()]
    88  	}
    89  	return status, f.NextErr()
    90  }
    91  
    92  func (f *fakeDestroyAPI) AllModels() ([]base.UserModel, error) {
    93  	f.MethodCall(f, "AllModels")
    94  	return f.allEnvs, f.NextErr()
    95  }
    96  
    97  // fakeDestroyAPIClient mocks out the client API
    98  type fakeDestroyAPIClient struct {
    99  	err           error
   100  	env           map[string]interface{}
   101  	envgetcalled  bool
   102  	destroycalled bool
   103  }
   104  
   105  func (f *fakeDestroyAPIClient) Close() error { return nil }
   106  
   107  func (f *fakeDestroyAPIClient) ModelGet() (map[string]interface{}, error) {
   108  	f.envgetcalled = true
   109  	if f.err != nil {
   110  		return nil, f.err
   111  	}
   112  	return f.env, nil
   113  }
   114  
   115  func (f *fakeDestroyAPIClient) DestroyModel() error {
   116  	f.destroycalled = true
   117  	return f.err
   118  }
   119  
   120  func createBootstrapInfo(c *gc.C, name string) map[string]interface{} {
   121  	cfg, err := config.New(config.UseDefaults, map[string]interface{}{
   122  		"type":            "dummy",
   123  		"name":            name,
   124  		"uuid":            testing.ModelTag.Id(),
   125  		"controller-uuid": testing.ModelTag.Id(),
   126  		"controller":      "true",
   127  	})
   128  	c.Assert(err, jc.ErrorIsNil)
   129  	return cfg.AllAttrs()
   130  }
   131  
   132  func (s *baseDestroySuite) SetUpTest(c *gc.C) {
   133  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
   134  	s.clientapi = &fakeDestroyAPIClient{}
   135  	owner := names.NewUserTag("owner")
   136  	s.api = &fakeDestroyAPI{
   137  		envStatus: map[string]base.ModelStatus{},
   138  	}
   139  	s.apierror = nil
   140  
   141  	s.store = jujuclienttesting.NewMemStore()
   142  	s.store.Controllers["local.test1"] = jujuclient.ControllerDetails{
   143  		APIEndpoints:   []string{"localhost"},
   144  		CACert:         testing.CACert,
   145  		ControllerUUID: test1UUID,
   146  	}
   147  	s.store.Controllers["test3"] = jujuclient.ControllerDetails{
   148  		APIEndpoints:   []string{"localhost"},
   149  		CACert:         testing.CACert,
   150  		ControllerUUID: test3UUID,
   151  	}
   152  	s.store.Accounts["local.test1"] = &jujuclient.ControllerAccounts{
   153  		CurrentAccount: "admin@local",
   154  	}
   155  
   156  	var modelList = []struct {
   157  		name         string
   158  		serverUUID   string
   159  		modelUUID    string
   160  		bootstrapCfg map[string]interface{}
   161  	}{
   162  		{
   163  			name:         "local.test1:admin",
   164  			serverUUID:   test1UUID,
   165  			modelUUID:    test1UUID,
   166  			bootstrapCfg: createBootstrapInfo(c, "admin"),
   167  		}, {
   168  			name:       "test2:test2",
   169  			serverUUID: test1UUID,
   170  			modelUUID:  test2UUID,
   171  		}, {
   172  			name:       "test3:admin",
   173  			serverUUID: test3UUID,
   174  			modelUUID:  test3UUID,
   175  		},
   176  	}
   177  	for _, model := range modelList {
   178  		controllerName, modelName := modelcmd.SplitModelName(model.name)
   179  		s.store.UpdateController(controllerName, jujuclient.ControllerDetails{
   180  			ControllerUUID: model.serverUUID,
   181  			APIEndpoints:   []string{"localhost"},
   182  			CACert:         testing.CACert,
   183  		})
   184  		s.store.UpdateModel(controllerName, "admin@local", modelName, jujuclient.ModelDetails{
   185  			ModelUUID: model.modelUUID,
   186  		})
   187  		if model.bootstrapCfg != nil {
   188  			s.store.BootstrapConfig[controllerName] = jujuclient.BootstrapConfig{
   189  				Config: createBootstrapInfo(c, "admin"),
   190  			}
   191  		}
   192  
   193  		uuid := model.modelUUID
   194  		s.api.allEnvs = append(s.api.allEnvs, base.UserModel{
   195  			Name:  model.name,
   196  			UUID:  uuid,
   197  			Owner: owner.Canonical(),
   198  		})
   199  		s.api.envStatus[model.modelUUID] = base.ModelStatus{
   200  			UUID:               uuid,
   201  			Life:               params.Dead,
   202  			HostedMachineCount: 0,
   203  			ServiceCount:       0,
   204  			Owner:              owner.Canonical(),
   205  		}
   206  	}
   207  }
   208  
   209  func (s *DestroySuite) runDestroyCommand(c *gc.C, args ...string) (*cmd.Context, error) {
   210  	return testing.RunCommand(c, s.newDestroyCommand(), args...)
   211  }
   212  
   213  func (s *DestroySuite) newDestroyCommand() cmd.Command {
   214  	return controller.NewDestroyCommandForTest(s.api, s.clientapi, s.store, s.apierror)
   215  }
   216  
   217  func checkControllerExistsInStore(c *gc.C, name string, store jujuclient.ControllerGetter) {
   218  	_, err := store.ControllerByName(name)
   219  	c.Assert(err, jc.ErrorIsNil)
   220  }
   221  
   222  func checkControllerRemovedFromStore(c *gc.C, name string, store jujuclient.ControllerGetter) {
   223  	_, err := store.ControllerByName(name)
   224  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   225  }
   226  
   227  func (s *DestroySuite) TestDestroyNoControllerNameError(c *gc.C) {
   228  	_, err := s.runDestroyCommand(c)
   229  	c.Assert(err, gc.ErrorMatches, "no controller specified")
   230  }
   231  
   232  func (s *DestroySuite) TestDestroyBadFlags(c *gc.C) {
   233  	_, err := s.runDestroyCommand(c, "-n")
   234  	c.Assert(err, gc.ErrorMatches, "flag provided but not defined: -n")
   235  }
   236  
   237  func (s *DestroySuite) TestDestroyUnknownArgument(c *gc.C) {
   238  	_, err := s.runDestroyCommand(c, "model", "whoops")
   239  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["whoops"\]`)
   240  }
   241  
   242  func (s *DestroySuite) TestDestroyUnknownController(c *gc.C) {
   243  	_, err := s.runDestroyCommand(c, "foo")
   244  	c.Assert(err, gc.ErrorMatches, `controller foo not found`)
   245  }
   246  
   247  func (s *DestroySuite) TestDestroyControllerNotFoundNotRemovedFromStore(c *gc.C) {
   248  	s.apierror = errors.NotFoundf("local.test1")
   249  	_, err := s.runDestroyCommand(c, "local.test1", "-y")
   250  	c.Assert(err, gc.ErrorMatches, "cannot connect to API: local.test1 not found")
   251  	c.Check(c.GetTestLog(), jc.Contains, "If the controller is unusable")
   252  	checkControllerExistsInStore(c, "local.test1", s.store)
   253  }
   254  
   255  func (s *DestroySuite) TestDestroyCannotConnectToAPI(c *gc.C) {
   256  	s.apierror = errors.New("connection refused")
   257  	_, err := s.runDestroyCommand(c, "local.test1", "-y")
   258  	c.Assert(err, gc.ErrorMatches, "cannot connect to API: connection refused")
   259  	c.Check(c.GetTestLog(), jc.Contains, "If the controller is unusable")
   260  	checkControllerExistsInStore(c, "local.test1", s.store)
   261  }
   262  
   263  func (s *DestroySuite) TestDestroy(c *gc.C) {
   264  	_, err := s.runDestroyCommand(c, "local.test1", "-y")
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	c.Assert(s.api.destroyAll, jc.IsFalse)
   267  	c.Assert(s.clientapi.destroycalled, jc.IsFalse)
   268  	checkControllerRemovedFromStore(c, "local.test1", s.store)
   269  }
   270  
   271  func (s *DestroySuite) TestDestroyAlias(c *gc.C) {
   272  	_, err := s.runDestroyCommand(c, "test1", "-y")
   273  	c.Assert(err, jc.ErrorIsNil)
   274  	c.Assert(s.api.destroyAll, jc.IsFalse)
   275  	c.Assert(s.clientapi.destroycalled, jc.IsFalse)
   276  	checkControllerRemovedFromStore(c, "local.test1", s.store)
   277  }
   278  
   279  func (s *DestroySuite) TestDestroyWithDestroyAllEnvsFlag(c *gc.C) {
   280  	_, err := s.runDestroyCommand(c, "local.test1", "-y", "--destroy-all-models")
   281  	c.Assert(err, jc.ErrorIsNil)
   282  	c.Assert(s.api.destroyAll, jc.IsTrue)
   283  	checkControllerRemovedFromStore(c, "local.test1", s.store)
   284  }
   285  
   286  func (s *DestroySuite) TestDestroyControllerGetFails(c *gc.C) {
   287  	s.api.SetErrors(errors.NotFoundf(`controller "test3"`))
   288  	_, err := s.runDestroyCommand(c, "test3", "-y")
   289  	c.Assert(err, gc.ErrorMatches,
   290  		"getting controller environ: getting bootstrap config from API: controller \"test3\" not found",
   291  	)
   292  	checkControllerExistsInStore(c, "test3", s.store)
   293  }
   294  
   295  func (s *DestroySuite) TestFailedDestroyController(c *gc.C) {
   296  	s.api.SetErrors(errors.New("permission denied"))
   297  	_, err := s.runDestroyCommand(c, "local.test1", "-y")
   298  	c.Assert(err, gc.ErrorMatches, "cannot destroy controller: permission denied")
   299  	c.Assert(s.api.destroyAll, jc.IsFalse)
   300  	checkControllerExistsInStore(c, "local.test1", s.store)
   301  }
   302  
   303  func (s *DestroySuite) TestDestroyControllerAliveModels(c *gc.C) {
   304  	for uuid, status := range s.api.envStatus {
   305  		status.Life = params.Alive
   306  		s.api.envStatus[uuid] = status
   307  	}
   308  	s.api.SetErrors(&params.Error{Code: params.CodeHasHostedModels})
   309  	_, err := s.runDestroyCommand(c, "local.test1", "-y")
   310  	c.Assert(err.Error(), gc.Equals, `cannot destroy controller "local.test1"
   311  
   312  The controller has live hosted models. If you want
   313  to destroy all hosted models in the controller,
   314  run this command again with the --destroy-all-models
   315  flag.
   316  
   317  Models:
   318  	owner@local/test2:test2 (alive)
   319  	owner@local/test3:admin (alive)
   320  `)
   321  
   322  }
   323  
   324  func (s *DestroySuite) TestDestroyControllerReattempt(c *gc.C) {
   325  	// The first attempt to destroy should yield an error
   326  	// saying that the controller has hosted models. After
   327  	// checking, we find there are only dead hosted models,
   328  	// and reattempt the destroy the controller; this time
   329  	// it succeeds.
   330  	s.api.SetErrors(&params.Error{Code: params.CodeHasHostedModels})
   331  	_, err := s.runDestroyCommand(c, "local.test1", "-y")
   332  	c.Assert(err, jc.ErrorIsNil)
   333  	s.api.CheckCallNames(c,
   334  		"DestroyController",
   335  		"AllModels",
   336  		"ModelStatus",
   337  		"ModelStatus",
   338  		"DestroyController",
   339  		"AllModels",
   340  		"ModelStatus",
   341  		"ModelStatus",
   342  		"Close",
   343  	)
   344  }
   345  
   346  func (s *DestroySuite) resetController(c *gc.C) {
   347  	s.store.Controllers["local.test1"] = jujuclient.ControllerDetails{
   348  		APIEndpoints:   []string{"localhost"},
   349  		CACert:         testing.CACert,
   350  		ControllerUUID: test1UUID,
   351  	}
   352  	s.store.Accounts["local.test1"] = &jujuclient.ControllerAccounts{
   353  		CurrentAccount: "admin@local",
   354  	}
   355  	s.store.BootstrapConfig["local.test1"] = jujuclient.BootstrapConfig{
   356  		Config: createBootstrapInfo(c, "admin"),
   357  	}
   358  }
   359  
   360  func (s *DestroySuite) TestDestroyCommandConfirmation(c *gc.C) {
   361  	var stdin, stdout bytes.Buffer
   362  	ctx := testing.Context(c)
   363  	ctx.Stdout = &stdout
   364  	ctx.Stdin = &stdin
   365  
   366  	// Ensure confirmation is requested if "-y" is not specified.
   367  	stdin.WriteString("n")
   368  	_, errc := cmdtesting.RunCommand(ctx, s.newDestroyCommand(), "local.test1")
   369  	select {
   370  	case err := <-errc:
   371  		c.Check(err, gc.ErrorMatches, "controller destruction aborted")
   372  	case <-time.After(testing.LongWait):
   373  		c.Fatalf("command took too long")
   374  	}
   375  	c.Check(testing.Stdout(ctx), gc.Matches, "WARNING!.*local.test1(.|\n)*")
   376  	checkControllerExistsInStore(c, "local.test1", s.store)
   377  
   378  	// EOF on stdin: equivalent to answering no.
   379  	stdin.Reset()
   380  	stdout.Reset()
   381  	_, errc = cmdtesting.RunCommand(ctx, s.newDestroyCommand(), "local.test1")
   382  	select {
   383  	case err := <-errc:
   384  		c.Check(err, gc.ErrorMatches, "controller destruction aborted")
   385  	case <-time.After(testing.LongWait):
   386  		c.Fatalf("command took too long")
   387  	}
   388  	c.Check(testing.Stdout(ctx), gc.Matches, "WARNING!.*local.test1(.|\n)*")
   389  	checkControllerExistsInStore(c, "local.test1", s.store)
   390  
   391  	for _, answer := range []string{"y", "Y", "yes", "YES"} {
   392  		stdin.Reset()
   393  		stdout.Reset()
   394  		stdin.WriteString(answer)
   395  		_, errc = cmdtesting.RunCommand(ctx, s.newDestroyCommand(), "local.test1")
   396  		select {
   397  		case err := <-errc:
   398  			c.Check(err, jc.ErrorIsNil)
   399  		case <-time.After(testing.LongWait):
   400  			c.Fatalf("command took too long")
   401  		}
   402  		checkControllerRemovedFromStore(c, "local.test1", s.store)
   403  
   404  		// Add the local.test1 controller back into the store for the next test
   405  		s.resetController(c)
   406  	}
   407  }
   408  
   409  func (s *DestroySuite) TestBlockedDestroy(c *gc.C) {
   410  	s.api.SetErrors(&params.Error{Code: params.CodeOperationBlocked})
   411  	s.runDestroyCommand(c, "local.test1", "-y")
   412  	testLog := c.GetTestLog()
   413  	c.Check(testLog, jc.Contains, "To remove all blocks in the controller, please run:")
   414  	c.Check(testLog, jc.Contains, "juju controller remove-blocks")
   415  }
   416  
   417  func (s *DestroySuite) TestDestroyListBlocksError(c *gc.C) {
   418  	s.api.SetErrors(
   419  		&params.Error{Code: params.CodeOperationBlocked},
   420  		errors.New("unexpected api error"),
   421  	)
   422  	s.runDestroyCommand(c, "local.test1", "-y")
   423  	testLog := c.GetTestLog()
   424  	c.Check(testLog, jc.Contains, "To remove all blocks in the controller, please run:")
   425  	c.Check(testLog, jc.Contains, "juju controller remove-blocks")
   426  	c.Check(testLog, jc.Contains, "Unable to list blocked models: unexpected api error")
   427  }
   428  
   429  func (s *DestroySuite) TestDestroyReturnsBlocks(c *gc.C) {
   430  	s.api.SetErrors(&params.Error{Code: params.CodeOperationBlocked})
   431  	s.api.blocks = []params.ModelBlockInfo{
   432  		params.ModelBlockInfo{
   433  			Name:     "test1",
   434  			UUID:     test1UUID,
   435  			OwnerTag: "cheryl@local",
   436  			Blocks: []string{
   437  				"BlockDestroy",
   438  			},
   439  		},
   440  		params.ModelBlockInfo{
   441  			Name:     "test2",
   442  			UUID:     test2UUID,
   443  			OwnerTag: "bob@local",
   444  			Blocks: []string{
   445  				"BlockDestroy",
   446  				"BlockChange",
   447  			},
   448  		},
   449  	}
   450  	ctx, _ := s.runDestroyCommand(c, "local.test1", "-y", "--destroy-all-models")
   451  	c.Assert(testing.Stderr(ctx), gc.Equals, "Destroying controller\n"+
   452  		"NAME   MODEL UUID                            OWNER         BLOCKS\n"+
   453  		"test1  1871299e-1370-4f3e-83ab-1849ed7b1076  cheryl@local  destroy-model\n"+
   454  		"test2  c59d0e3b-2bd7-4867-b1b9-f1ef8a0bb004  bob@local     destroy-model,all-changes\n")
   455  }