github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/system/destroy_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package system_test
     5  
     6  import (
     7  	"bytes"
     8  	"time"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/cmd/juju/system"
    17  	cmdtesting "github.com/juju/juju/cmd/testing"
    18  	"github.com/juju/juju/environs/config"
    19  	"github.com/juju/juju/environs/configstore"
    20  	_ "github.com/juju/juju/provider/dummy"
    21  	"github.com/juju/juju/testing"
    22  )
    23  
    24  type DestroySuite struct {
    25  	testing.FakeJujuHomeSuite
    26  	api       *fakeDestroyAPI
    27  	clientapi *fakeDestroyAPIClient
    28  	store     configstore.Storage
    29  	apierror  error
    30  }
    31  
    32  var _ = gc.Suite(&DestroySuite{})
    33  
    34  // fakeDestroyAPI mocks out the systemmanager API
    35  type fakeDestroyAPI struct {
    36  	err          error
    37  	env          map[string]interface{}
    38  	destroyAll   bool
    39  	ignoreBlocks bool
    40  	blocks       []params.EnvironmentBlockInfo
    41  	blocksErr    error
    42  }
    43  
    44  func (f *fakeDestroyAPI) Close() error { return nil }
    45  
    46  func (f *fakeDestroyAPI) EnvironmentConfig() (map[string]interface{}, error) {
    47  	if f.err != nil {
    48  		return nil, f.err
    49  	}
    50  	return f.env, nil
    51  }
    52  
    53  func (f *fakeDestroyAPI) DestroySystem(destroyAll bool, ignoreBlocks bool) error {
    54  	f.destroyAll = destroyAll
    55  	f.ignoreBlocks = ignoreBlocks
    56  	return f.err
    57  }
    58  
    59  func (f *fakeDestroyAPI) ListBlockedEnvironments() ([]params.EnvironmentBlockInfo, error) {
    60  	return f.blocks, f.blocksErr
    61  }
    62  
    63  // fakeDestroyAPIClient mocks out the client API
    64  type fakeDestroyAPIClient struct {
    65  	err           error
    66  	env           map[string]interface{}
    67  	envgetcalled  bool
    68  	destroycalled bool
    69  }
    70  
    71  func (f *fakeDestroyAPIClient) Close() error { return nil }
    72  
    73  func (f *fakeDestroyAPIClient) EnvironmentGet() (map[string]interface{}, error) {
    74  	f.envgetcalled = true
    75  	if f.err != nil {
    76  		return nil, f.err
    77  	}
    78  	return f.env, nil
    79  }
    80  
    81  func (f *fakeDestroyAPIClient) DestroyEnvironment() error {
    82  	f.destroycalled = true
    83  	return f.err
    84  }
    85  
    86  func createBootstrapInfo(c *gc.C, name string) map[string]interface{} {
    87  	cfg, err := config.New(config.UseDefaults, map[string]interface{}{
    88  		"type":         "dummy",
    89  		"name":         name,
    90  		"state-server": "true",
    91  		"state-id":     "1",
    92  	})
    93  	c.Assert(err, jc.ErrorIsNil)
    94  	return cfg.AllAttrs()
    95  }
    96  
    97  func (s *DestroySuite) SetUpTest(c *gc.C) {
    98  	s.FakeJujuHomeSuite.SetUpTest(c)
    99  	s.clientapi = &fakeDestroyAPIClient{}
   100  	s.api = &fakeDestroyAPI{}
   101  	s.apierror = nil
   102  
   103  	var err error
   104  	s.store, err = configstore.Default()
   105  	c.Assert(err, jc.ErrorIsNil)
   106  
   107  	var envList = []struct {
   108  		name         string
   109  		serverUUID   string
   110  		envUUID      string
   111  		bootstrapCfg map[string]interface{}
   112  	}{
   113  		{
   114  			name:         "test1",
   115  			serverUUID:   "test1-uuid",
   116  			envUUID:      "test1-uuid",
   117  			bootstrapCfg: createBootstrapInfo(c, "test1"),
   118  		}, {
   119  			name:       "test2",
   120  			serverUUID: "test1-uuid",
   121  			envUUID:    "test2-uuid",
   122  		}, {
   123  			name:    "test3",
   124  			envUUID: "test3-uuid",
   125  		},
   126  	}
   127  	for _, env := range envList {
   128  		info := s.store.CreateInfo(env.name)
   129  		info.SetAPIEndpoint(configstore.APIEndpoint{
   130  			Addresses:   []string{"localhost"},
   131  			CACert:      testing.CACert,
   132  			EnvironUUID: env.envUUID,
   133  			ServerUUID:  env.serverUUID,
   134  		})
   135  
   136  		if env.bootstrapCfg != nil {
   137  			info.SetBootstrapConfig(env.bootstrapCfg)
   138  		}
   139  		err := info.Write()
   140  		c.Assert(err, jc.ErrorIsNil)
   141  	}
   142  }
   143  
   144  func (s *DestroySuite) runDestroyCommand(c *gc.C, args ...string) (*cmd.Context, error) {
   145  	cmd := system.NewDestroyCommand(s.api, s.clientapi, s.apierror)
   146  	return testing.RunCommand(c, cmd, args...)
   147  }
   148  
   149  func (s *DestroySuite) newDestroyCommand() *system.DestroyCommand {
   150  	return system.NewDestroyCommand(s.api, s.clientapi, s.apierror)
   151  }
   152  
   153  func checkSystemExistsInStore(c *gc.C, name string, store configstore.Storage) {
   154  	_, err := store.ReadInfo(name)
   155  	c.Check(err, jc.ErrorIsNil)
   156  }
   157  
   158  func checkSystemRemovedFromStore(c *gc.C, name string, store configstore.Storage) {
   159  	_, err := store.ReadInfo(name)
   160  	c.Check(err, jc.Satisfies, errors.IsNotFound)
   161  }
   162  
   163  func (s *DestroySuite) TestDestroyNoSystemNameError(c *gc.C) {
   164  	_, err := s.runDestroyCommand(c)
   165  	c.Assert(err, gc.ErrorMatches, "no system specified")
   166  }
   167  
   168  func (s *DestroySuite) TestDestroyBadFlags(c *gc.C) {
   169  	_, err := s.runDestroyCommand(c, "-n")
   170  	c.Assert(err, gc.ErrorMatches, "flag provided but not defined: -n")
   171  }
   172  
   173  func (s *DestroySuite) TestDestroyUnknownArgument(c *gc.C) {
   174  	_, err := s.runDestroyCommand(c, "environment", "whoops")
   175  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["whoops"\]`)
   176  }
   177  
   178  func (s *DestroySuite) TestDestroyUnknownSystem(c *gc.C) {
   179  	_, err := s.runDestroyCommand(c, "foo")
   180  	c.Assert(err, gc.ErrorMatches, `cannot read system info: environment "foo" not found`)
   181  }
   182  
   183  func (s *DestroySuite) TestDestroyNonSystemEnvFails(c *gc.C) {
   184  	_, err := s.runDestroyCommand(c, "test2")
   185  	c.Assert(err, gc.ErrorMatches, "\"test2\" is not a system; use juju environment destroy to destroy it")
   186  }
   187  
   188  func (s *DestroySuite) TestDestroySystemNotFoundNotRemovedFromStore(c *gc.C) {
   189  	s.apierror = errors.NotFoundf("test1")
   190  	_, err := s.runDestroyCommand(c, "test1", "-y")
   191  	c.Assert(err, gc.ErrorMatches, "cannot connect to API: test1 not found")
   192  	c.Check(c.GetTestLog(), jc.Contains, "If the system is unusable")
   193  	checkSystemExistsInStore(c, "test1", s.store)
   194  }
   195  
   196  func (s *DestroySuite) TestDestroyCannotConnectToAPI(c *gc.C) {
   197  	s.apierror = errors.New("connection refused")
   198  	_, err := s.runDestroyCommand(c, "test1", "-y")
   199  	c.Assert(err, gc.ErrorMatches, "cannot connect to API: connection refused")
   200  	c.Check(c.GetTestLog(), jc.Contains, "If the system is unusable")
   201  	checkSystemExistsInStore(c, "test1", s.store)
   202  }
   203  
   204  func (s *DestroySuite) TestDestroy(c *gc.C) {
   205  	_, err := s.runDestroyCommand(c, "test1", "-y")
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	c.Assert(s.api.ignoreBlocks, jc.IsFalse)
   208  	c.Assert(s.api.destroyAll, jc.IsFalse)
   209  	c.Assert(s.clientapi.destroycalled, jc.IsFalse)
   210  	checkSystemRemovedFromStore(c, "test1", s.store)
   211  }
   212  
   213  func (s *DestroySuite) TestDestroyWithDestroyAllEnvsFlag(c *gc.C) {
   214  	_, err := s.runDestroyCommand(c, "test1", "-y", "--destroy-all-environments")
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	c.Assert(s.api.ignoreBlocks, jc.IsFalse)
   217  	c.Assert(s.api.destroyAll, jc.IsTrue)
   218  	checkSystemRemovedFromStore(c, "test1", s.store)
   219  }
   220  
   221  func (s *DestroySuite) TestDestroyEnvironmentGetFails(c *gc.C) {
   222  	s.api.err = errors.NotFoundf(`system "test3"`)
   223  	_, err := s.runDestroyCommand(c, "test3", "-y")
   224  	c.Assert(err, gc.ErrorMatches, "cannot obtain bootstrap information: system \"test3\" not found")
   225  	checkSystemExistsInStore(c, "test3", s.store)
   226  }
   227  
   228  func (s *DestroySuite) TestDestroyFallsBackToClient(c *gc.C) {
   229  	s.api.err = &params.Error{"DestroyEnvironment", params.CodeNotImplemented}
   230  	_, err := s.runDestroyCommand(c, "test1", "-y")
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	c.Assert(s.clientapi.destroycalled, jc.IsTrue)
   233  	checkSystemRemovedFromStore(c, "test1", s.store)
   234  }
   235  
   236  func (s *DestroySuite) TestEnvironmentGetFallsBackToClient(c *gc.C) {
   237  	s.api.err = &params.Error{"EnvironmentGet", params.CodeNotImplemented}
   238  	s.clientapi.env = createBootstrapInfo(c, "test3")
   239  	_, err := s.runDestroyCommand(c, "test3", "-y")
   240  	c.Assert(err, jc.ErrorIsNil)
   241  	c.Assert(s.clientapi.envgetcalled, jc.IsTrue)
   242  	c.Assert(s.clientapi.destroycalled, jc.IsTrue)
   243  	checkSystemRemovedFromStore(c, "test3", s.store)
   244  }
   245  
   246  func (s *DestroySuite) TestFailedDestroyEnvironment(c *gc.C) {
   247  	s.api.err = errors.New("permission denied")
   248  	_, err := s.runDestroyCommand(c, "test1", "-y")
   249  	c.Assert(err, gc.ErrorMatches, "cannot destroy system: permission denied")
   250  	c.Assert(s.api.ignoreBlocks, jc.IsFalse)
   251  	c.Assert(s.api.destroyAll, jc.IsFalse)
   252  	checkSystemExistsInStore(c, "test1", s.store)
   253  }
   254  
   255  func (s *DestroySuite) resetSystem(c *gc.C) {
   256  	info := s.store.CreateInfo("test1")
   257  	info.SetAPIEndpoint(configstore.APIEndpoint{
   258  		Addresses:   []string{"localhost"},
   259  		CACert:      testing.CACert,
   260  		EnvironUUID: "test1-uuid",
   261  		ServerUUID:  "test1-uuid",
   262  	})
   263  	info.SetBootstrapConfig(createBootstrapInfo(c, "test1"))
   264  	err := info.Write()
   265  	c.Assert(err, jc.ErrorIsNil)
   266  }
   267  
   268  func (s *DestroySuite) TestDestroyCommandConfirmation(c *gc.C) {
   269  	var stdin, stdout bytes.Buffer
   270  	ctx, err := cmd.DefaultContext()
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	ctx.Stdout = &stdout
   273  	ctx.Stdin = &stdin
   274  
   275  	// Ensure confirmation is requested if "-y" is not specified.
   276  	stdin.WriteString("n")
   277  	_, errc := cmdtesting.RunCommand(ctx, s.newDestroyCommand(), "test1")
   278  	select {
   279  	case err := <-errc:
   280  		c.Check(err, gc.ErrorMatches, "system destruction aborted")
   281  	case <-time.After(testing.LongWait):
   282  		c.Fatalf("command took too long")
   283  	}
   284  	c.Check(testing.Stdout(ctx), gc.Matches, "WARNING!.*test1(.|\n)*")
   285  	checkSystemExistsInStore(c, "test1", s.store)
   286  
   287  	// EOF on stdin: equivalent to answering no.
   288  	stdin.Reset()
   289  	stdout.Reset()
   290  	_, errc = cmdtesting.RunCommand(ctx, s.newDestroyCommand(), "test1")
   291  	select {
   292  	case err := <-errc:
   293  		c.Check(err, gc.ErrorMatches, "system destruction aborted")
   294  	case <-time.After(testing.LongWait):
   295  		c.Fatalf("command took too long")
   296  	}
   297  	c.Check(testing.Stdout(ctx), gc.Matches, "WARNING!.*test1(.|\n)*")
   298  	checkSystemExistsInStore(c, "test1", s.store)
   299  
   300  	for _, answer := range []string{"y", "Y", "yes", "YES"} {
   301  		stdin.Reset()
   302  		stdout.Reset()
   303  		stdin.WriteString(answer)
   304  		_, errc = cmdtesting.RunCommand(ctx, s.newDestroyCommand(), "test1")
   305  		select {
   306  		case err := <-errc:
   307  			c.Check(err, jc.ErrorIsNil)
   308  		case <-time.After(testing.LongWait):
   309  			c.Fatalf("command took too long")
   310  		}
   311  		checkSystemRemovedFromStore(c, "test1", s.store)
   312  
   313  		// Add the test1 system back into the store for the next test
   314  		s.resetSystem(c)
   315  	}
   316  }
   317  
   318  func (s *DestroySuite) TestBlockedDestroy(c *gc.C) {
   319  	s.api.err = &params.Error{Code: params.CodeOperationBlocked}
   320  	s.runDestroyCommand(c, "test1", "-y")
   321  	testLog := c.GetTestLog()
   322  	c.Check(testLog, jc.Contains, "To remove all blocks in the system, please run:")
   323  	c.Check(testLog, jc.Contains, "juju system remove-blocks")
   324  }
   325  
   326  func (s *DestroySuite) TestDestroyListBlocksError(c *gc.C) {
   327  	s.api.err = &params.Error{Code: params.CodeOperationBlocked}
   328  	s.api.blocksErr = errors.New("unexpected api error")
   329  	s.runDestroyCommand(c, "test1", "-y")
   330  	testLog := c.GetTestLog()
   331  	c.Check(testLog, jc.Contains, "To remove all blocks in the system, please run:")
   332  	c.Check(testLog, jc.Contains, "juju system remove-blocks")
   333  	c.Check(testLog, jc.Contains, "Unable to list blocked environments: unexpected api error")
   334  }
   335  
   336  func (s *DestroySuite) TestDestroyReturnsBlocks(c *gc.C) {
   337  	s.api.err = &params.Error{Code: params.CodeOperationBlocked}
   338  	s.api.blocks = []params.EnvironmentBlockInfo{
   339  		params.EnvironmentBlockInfo{
   340  			Name:     "test1",
   341  			UUID:     "test1-uuid",
   342  			OwnerTag: "cheryl@local",
   343  			Blocks: []string{
   344  				"BlockDestroy",
   345  			},
   346  		},
   347  		params.EnvironmentBlockInfo{
   348  			Name:     "test2",
   349  			UUID:     "test2-uuid",
   350  			OwnerTag: "bob@local",
   351  			Blocks: []string{
   352  				"BlockDestroy",
   353  				"BlockChange",
   354  			},
   355  		},
   356  	}
   357  	ctx, _ := s.runDestroyCommand(c, "test1", "-y", "--destroy-all-environments")
   358  	c.Assert(testing.Stderr(ctx), gc.Equals, ""+
   359  		"NAME   ENVIRONMENT UUID  OWNER         BLOCKS\n"+
   360  		"test1  test1-uuid        cheryl@local  destroy-environment\n"+
   361  		"test2  test2-uuid        bob@local     destroy-environment,all-changes\n")
   362  }