github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/controller/kill_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/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils/clock"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/names.v2"
    17  
    18  	"github.com/juju/juju/api"
    19  	"github.com/juju/juju/api/base"
    20  	"github.com/juju/juju/apiserver/common"
    21  	"github.com/juju/juju/apiserver/params"
    22  	"github.com/juju/juju/cmd/juju/controller"
    23  	"github.com/juju/juju/cmd/modelcmd"
    24  	cmdtesting "github.com/juju/juju/cmd/testing"
    25  	"github.com/juju/juju/jujuclient"
    26  	_ "github.com/juju/juju/provider/dummy"
    27  	coretesting "github.com/juju/juju/testing"
    28  )
    29  
    30  type KillSuite struct {
    31  	baseDestroySuite
    32  
    33  	clock *testing.Clock
    34  }
    35  
    36  var _ = gc.Suite(&KillSuite{})
    37  
    38  func (s *KillSuite) SetUpTest(c *gc.C) {
    39  	s.baseDestroySuite.SetUpTest(c)
    40  	s.clock = testing.NewClock(time.Now())
    41  }
    42  
    43  func (s *KillSuite) runKillCommand(c *gc.C, args ...string) (*cmd.Context, error) {
    44  	return coretesting.RunCommand(c, s.newKillCommand(), args...)
    45  }
    46  
    47  func (s *KillSuite) newKillCommand() cmd.Command {
    48  	wrapped, _ := controller.NewKillCommandForTest(
    49  		s.api, s.clientapi, s.store, s.apierror, s.clock, nil)
    50  	return wrapped
    51  }
    52  
    53  func (s *KillSuite) newKillCommandBoth() (cmd.Command, cmd.Command) {
    54  	clock := s.clock
    55  	if clock == nil {
    56  		clock = testing.NewClock(time.Now())
    57  	}
    58  	return controller.NewKillCommandForTest(
    59  		s.api, s.clientapi, s.store, s.apierror, clock, nil)
    60  }
    61  
    62  func (s *KillSuite) TestKillNoControllerNameError(c *gc.C) {
    63  	_, err := s.runKillCommand(c)
    64  	c.Assert(err, gc.ErrorMatches, "no controller specified")
    65  }
    66  
    67  func (s *KillSuite) TestKillBadFlags(c *gc.C) {
    68  	_, err := s.runKillCommand(c, "-n")
    69  	c.Assert(err, gc.ErrorMatches, "flag provided but not defined: -n")
    70  }
    71  
    72  func (s *KillSuite) TestKillDurationFlags(c *gc.C) {
    73  	for i, test := range []struct {
    74  		args     []string
    75  		expected time.Duration
    76  		err      string
    77  	}{
    78  		{
    79  			expected: 5 * time.Minute,
    80  		}, {
    81  			args:     []string{"-t", "2m"},
    82  			expected: 2 * time.Minute,
    83  		}, {
    84  			args:     []string{"--timeout", "2m"},
    85  			expected: 2 * time.Minute,
    86  		}, {
    87  			args:     []string{"-t", "0"},
    88  			expected: 0,
    89  		},
    90  	} {
    91  		c.Logf("duration test %d", i)
    92  		wrapped, inner := s.newKillCommandBoth()
    93  		args := append([]string{"test1"}, test.args...)
    94  		err := coretesting.InitCommand(wrapped, args)
    95  		if test.err == "" {
    96  			c.Check(err, jc.ErrorIsNil)
    97  			c.Check(controller.KillTimeout(c, inner), gc.Equals, test.expected)
    98  		} else {
    99  			c.Check(err, gc.ErrorMatches, test.err)
   100  		}
   101  	}
   102  }
   103  
   104  func (s *KillSuite) TestKillWaitForModels_AllGood(c *gc.C) {
   105  	s.resetAPIModels(c)
   106  	wrapped, inner := s.newKillCommandBoth()
   107  	err := coretesting.InitCommand(wrapped, []string{"test1", "--timeout=1m"})
   108  	c.Assert(err, jc.ErrorIsNil)
   109  
   110  	ctx := coretesting.Context(c)
   111  	err = controller.KillWaitForModels(inner, ctx, s.api, test1UUID)
   112  	c.Assert(err, jc.ErrorIsNil)
   113  	c.Assert(coretesting.Stderr(ctx), gc.Equals, "All hosted models reclaimed, cleaning up controller machines\n")
   114  }
   115  
   116  func (s *KillSuite) TestKillWaitForModels_ActuallyWaits(c *gc.C) {
   117  	s.resetAPIModels(c)
   118  	s.addModel("model-1", base.ModelStatus{
   119  		UUID:               test2UUID,
   120  		Life:               string(params.Dying),
   121  		Owner:              "admin",
   122  		HostedMachineCount: 2,
   123  		ServiceCount:       2,
   124  	})
   125  	wrapped, inner := s.newKillCommandBoth()
   126  	err := coretesting.InitCommand(wrapped, []string{"test1", "--timeout=1m"})
   127  	c.Assert(err, jc.ErrorIsNil)
   128  
   129  	ctx := coretesting.Context(c)
   130  	result := make(chan error)
   131  	go func() {
   132  		err := controller.KillWaitForModels(inner, ctx, s.api, test1UUID)
   133  		result <- err
   134  	}()
   135  
   136  	s.syncClockAlarm(c)
   137  	s.setModelStatus(base.ModelStatus{
   138  		UUID:               test2UUID,
   139  		Life:               string(params.Dying),
   140  		Owner:              "admin",
   141  		HostedMachineCount: 1,
   142  	})
   143  	s.clock.Advance(5 * time.Second)
   144  
   145  	s.syncClockAlarm(c)
   146  	s.removeModel(test2UUID)
   147  	s.clock.Advance(5 * time.Second)
   148  
   149  	select {
   150  	case err := <-result:
   151  		c.Assert(err, jc.ErrorIsNil)
   152  	case <-time.After(coretesting.LongWait):
   153  		c.Fatal("timed out waiting for result")
   154  	}
   155  	expect := "" +
   156  		"Waiting on 1 model, 2 machines, 2 applications\n" +
   157  		"Waiting on 1 model, 1 machine\n" +
   158  		"All hosted models reclaimed, cleaning up controller machines\n"
   159  
   160  	c.Assert(coretesting.Stderr(ctx), gc.Equals, expect)
   161  }
   162  
   163  func (s *KillSuite) TestKillWaitForModels_TimeoutResetsWithChange(c *gc.C) {
   164  	s.resetAPIModels(c)
   165  	s.addModel("model-1", base.ModelStatus{
   166  		UUID:               test2UUID,
   167  		Life:               string(params.Dying),
   168  		Owner:              "admin",
   169  		HostedMachineCount: 2,
   170  		ServiceCount:       2,
   171  	})
   172  	wrapped, inner := s.newKillCommandBoth()
   173  	err := coretesting.InitCommand(wrapped, []string{"test1", "--timeout=20s"})
   174  	c.Assert(err, jc.ErrorIsNil)
   175  
   176  	ctx := coretesting.Context(c)
   177  	result := make(chan error)
   178  	go func() {
   179  		err := controller.KillWaitForModels(inner, ctx, s.api, test1UUID)
   180  		result <- err
   181  	}()
   182  
   183  	s.syncClockAlarm(c)
   184  	s.clock.Advance(5 * time.Second)
   185  
   186  	s.syncClockAlarm(c)
   187  	s.setModelStatus(base.ModelStatus{
   188  		UUID:               test2UUID,
   189  		Life:               string(params.Dying),
   190  		Owner:              "admin",
   191  		HostedMachineCount: 1,
   192  	})
   193  	s.clock.Advance(5 * time.Second)
   194  
   195  	s.syncClockAlarm(c)
   196  	s.removeModel(test2UUID)
   197  	s.clock.Advance(5 * time.Second)
   198  
   199  	select {
   200  	case err := <-result:
   201  		c.Assert(err, jc.ErrorIsNil)
   202  	case <-time.After(coretesting.LongWait):
   203  		c.Fatal("timed out waiting for result")
   204  	}
   205  	expect := "" +
   206  		"Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 20s\n" +
   207  		"Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 15s\n" +
   208  		"Waiting on 1 model, 1 machine, will kill machines directly in 20s\n" +
   209  		"All hosted models reclaimed, cleaning up controller machines\n"
   210  
   211  	c.Assert(coretesting.Stderr(ctx), gc.Equals, expect)
   212  }
   213  
   214  func (s *KillSuite) TestKillWaitForModels_TimeoutWithNoChange(c *gc.C) {
   215  	s.resetAPIModels(c)
   216  	s.addModel("model-1", base.ModelStatus{
   217  		UUID:               test2UUID,
   218  		Life:               string(params.Dying),
   219  		Owner:              "admin",
   220  		HostedMachineCount: 2,
   221  		ServiceCount:       2,
   222  	})
   223  	wrapped, inner := s.newKillCommandBoth()
   224  	err := coretesting.InitCommand(wrapped, []string{"test1", "--timeout=1m"})
   225  	c.Assert(err, jc.ErrorIsNil)
   226  
   227  	ctx := coretesting.Context(c)
   228  	result := make(chan error)
   229  	go func() {
   230  		err := controller.KillWaitForModels(inner, ctx, s.api, test1UUID)
   231  		result <- err
   232  	}()
   233  
   234  	for i := 0; i < 12; i++ {
   235  		s.syncClockAlarm(c)
   236  		s.clock.Advance(5 * time.Second)
   237  	}
   238  
   239  	select {
   240  	case err := <-result:
   241  		c.Assert(err, gc.ErrorMatches, "timed out")
   242  	case <-time.After(coretesting.LongWait):
   243  		c.Fatal("timed out waiting for result")
   244  	}
   245  	expect := "" +
   246  		"Waiting on 1 model, 2 machines, 2 applications\n" +
   247  		"Waiting on 1 model, 2 machines, 2 applications\n" +
   248  		"Waiting on 1 model, 2 machines, 2 applications\n" +
   249  		"Waiting on 1 model, 2 machines, 2 applications\n" +
   250  		"Waiting on 1 model, 2 machines, 2 applications\n" +
   251  		"Waiting on 1 model, 2 machines, 2 applications\n" +
   252  		"Waiting on 1 model, 2 machines, 2 applications\n" +
   253  		"Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 25s\n" +
   254  		"Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 20s\n" +
   255  		"Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 15s\n" +
   256  		"Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 10s\n" +
   257  		"Waiting on 1 model, 2 machines, 2 applications, will kill machines directly in 5s\n"
   258  
   259  	c.Assert(coretesting.Stderr(ctx), gc.Equals, expect)
   260  }
   261  
   262  func (s *KillSuite) resetAPIModels(c *gc.C) {
   263  	s.api.allModels = nil
   264  	s.api.envStatus = map[string]base.ModelStatus{}
   265  	s.addModel("controller", base.ModelStatus{
   266  		UUID:              test1UUID,
   267  		Life:              string(params.Alive),
   268  		Owner:             "admin",
   269  		TotalMachineCount: 1,
   270  	})
   271  }
   272  
   273  func (s *KillSuite) addModel(name string, status base.ModelStatus) {
   274  	s.api.allModels = append(s.api.allModels, base.UserModel{
   275  		Name:  name,
   276  		UUID:  status.UUID,
   277  		Owner: status.Owner,
   278  	})
   279  	s.api.envStatus[status.UUID] = status
   280  }
   281  
   282  func (s *KillSuite) setModelStatus(status base.ModelStatus) {
   283  	s.api.envStatus[status.UUID] = status
   284  }
   285  
   286  func (s *KillSuite) removeModel(uuid string) {
   287  	for i, v := range s.api.allModels {
   288  		if v.UUID == uuid {
   289  			s.api.allModels = append(s.api.allModels[:i], s.api.allModels[i+1:]...)
   290  			break
   291  		}
   292  	}
   293  	delete(s.api.envStatus, uuid)
   294  }
   295  
   296  func (s *KillSuite) syncClockAlarm(c *gc.C) {
   297  	select {
   298  	case <-s.clock.Alarms():
   299  	case <-time.After(coretesting.LongWait):
   300  		c.Fatal("timed out waiting for test clock After call")
   301  	}
   302  }
   303  
   304  func (s *KillSuite) TestKillUnknownArgument(c *gc.C) {
   305  	_, err := s.runKillCommand(c, "model", "whoops")
   306  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["whoops"\]`)
   307  }
   308  
   309  func (s *KillSuite) TestKillUnknownController(c *gc.C) {
   310  	_, err := s.runKillCommand(c, "foo")
   311  	c.Assert(err, gc.ErrorMatches, `controller foo not found`)
   312  }
   313  
   314  func (s *KillSuite) TestKillCannotConnectToAPISucceeds(c *gc.C) {
   315  	s.apierror = errors.New("connection refused")
   316  	ctx, err := s.runKillCommand(c, "test1", "-y")
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	c.Check(coretesting.Stderr(ctx), jc.Contains, "Unable to open API: connection refused")
   319  	checkControllerRemovedFromStore(c, "test1", s.store)
   320  }
   321  
   322  func (s *KillSuite) TestKillWithAPIConnection(c *gc.C) {
   323  	_, err := s.runKillCommand(c, "test1", "-y")
   324  	c.Assert(err, jc.ErrorIsNil)
   325  	c.Assert(s.api.destroyAll, jc.IsTrue)
   326  	c.Assert(s.clientapi.destroycalled, jc.IsFalse)
   327  	checkControllerRemovedFromStore(c, "test1", s.store)
   328  }
   329  
   330  func (s *KillSuite) TestKillEnvironmentGetFailsWithoutAPIConnection(c *gc.C) {
   331  	s.apierror = errors.New("connection refused")
   332  	s.api.SetErrors(errors.NotFoundf(`controller "test3"`))
   333  	_, err := s.runKillCommand(c, "test3", "-y")
   334  	c.Assert(err, gc.ErrorMatches,
   335  		"getting controller environ: unable to get bootstrap information from client store or API",
   336  	)
   337  	checkControllerExistsInStore(c, "test3", s.store)
   338  }
   339  
   340  func (s *KillSuite) TestKillEnvironmentGetFailsWithAPIConnection(c *gc.C) {
   341  	s.api.SetErrors(errors.NotFoundf(`controller "test3"`))
   342  	_, err := s.runKillCommand(c, "test3", "-y")
   343  	c.Assert(err, gc.ErrorMatches,
   344  		"getting controller environ: getting model config from API: controller \"test3\" not found",
   345  	)
   346  	checkControllerExistsInStore(c, "test3", s.store)
   347  }
   348  
   349  func (s *KillSuite) TestKillDestroysControllerWithAPIError(c *gc.C) {
   350  	s.api.SetErrors(errors.New("some destroy error"))
   351  	ctx, err := s.runKillCommand(c, "test1", "-y")
   352  	c.Assert(err, jc.ErrorIsNil)
   353  	c.Check(coretesting.Stderr(ctx), jc.Contains, "Unable to destroy controller through the API: some destroy error\nDestroying through provider")
   354  	c.Assert(s.api.destroyAll, jc.IsTrue)
   355  	checkControllerRemovedFromStore(c, "test1", s.store)
   356  }
   357  
   358  func (s *KillSuite) TestKillCommandConfirmation(c *gc.C) {
   359  	var stdin, stdout bytes.Buffer
   360  	ctx, err := cmd.DefaultContext()
   361  	c.Assert(err, jc.ErrorIsNil)
   362  	ctx.Stdout = &stdout
   363  	ctx.Stdin = &stdin
   364  
   365  	// Ensure confirmation is requested if "-y" is not specified.
   366  	stdin.WriteString("n")
   367  	_, errc := cmdtesting.RunCommand(ctx, s.newKillCommand(), "test1")
   368  	select {
   369  	case err := <-errc:
   370  		c.Check(err, gc.ErrorMatches, "controller destruction aborted")
   371  	case <-time.After(coretesting.LongWait):
   372  		c.Fatalf("command took too long")
   373  	}
   374  	c.Check(coretesting.Stdout(ctx), gc.Matches, "WARNING!.*test1(.|\n)*")
   375  	checkControllerExistsInStore(c, "test1", s.store)
   376  }
   377  
   378  func (s *KillSuite) TestKillCommandControllerAlias(c *gc.C) {
   379  	_, err := coretesting.RunCommand(c, s.newKillCommand(), "test1", "-y")
   380  	c.Assert(err, jc.ErrorIsNil)
   381  	checkControllerRemovedFromStore(c, "test1:test1", s.store)
   382  }
   383  
   384  func (s *KillSuite) TestKillAPIPermErrFails(c *gc.C) {
   385  	testDialer := func(_ jujuclient.ClientStore, controllerName, modelName string) (api.Connection, error) {
   386  		return nil, common.ErrPerm
   387  	}
   388  	cmd, _ := controller.NewKillCommandForTest(nil, nil, s.store, nil, clock.WallClock, modelcmd.OpenFunc(testDialer))
   389  	_, err := coretesting.RunCommand(c, cmd, "test1", "-y")
   390  	c.Assert(err, gc.ErrorMatches, "cannot destroy controller: permission denied")
   391  	checkControllerExistsInStore(c, "test1", s.store)
   392  }
   393  
   394  func (s *KillSuite) TestKillEarlyAPIConnectionTimeout(c *gc.C) {
   395  	clock := &mockClock{}
   396  
   397  	stop := make(chan struct{})
   398  	defer close(stop)
   399  	testDialer := func(_ jujuclient.ClientStore, controllerName, modelName string) (api.Connection, error) {
   400  		<-stop
   401  		return nil, errors.New("kill command waited too long")
   402  	}
   403  
   404  	cmd, _ := controller.NewKillCommandForTest(nil, nil, s.store, nil, clock, modelcmd.OpenFunc(testDialer))
   405  	ctx, err := coretesting.RunCommand(c, cmd, "test1", "-y")
   406  	c.Check(err, jc.ErrorIsNil)
   407  	c.Check(coretesting.Stderr(ctx), jc.Contains, "Unable to open API: open connection timed out")
   408  	checkControllerRemovedFromStore(c, "test1", s.store)
   409  	// Check that we were actually told to wait for 10s.
   410  	c.Assert(clock.wait, gc.Equals, 10*time.Second)
   411  }
   412  
   413  // mockClock will panic if anything but After is called
   414  type mockClock struct {
   415  	clock.Clock
   416  	wait time.Duration
   417  }
   418  
   419  func (m *mockClock) After(duration time.Duration) <-chan time.Time {
   420  	m.wait = duration
   421  	return time.After(time.Millisecond)
   422  }
   423  
   424  func (s *KillSuite) TestControllerStatus(c *gc.C) {
   425  	s.api.allModels = []base.UserModel{
   426  		{Name: "admin",
   427  			UUID:  "123",
   428  			Owner: names.NewUserTag("admin").String(),
   429  		}, {Name: "env1",
   430  			UUID:  "456",
   431  			Owner: names.NewUserTag("bob").String(),
   432  		}, {Name: "env2",
   433  			UUID:  "789",
   434  			Owner: names.NewUserTag("jo").String(),
   435  		},
   436  	}
   437  
   438  	s.api.envStatus = make(map[string]base.ModelStatus)
   439  	for _, env := range s.api.allModels {
   440  		owner, err := names.ParseUserTag(env.Owner)
   441  		c.Assert(err, jc.ErrorIsNil)
   442  		s.api.envStatus[env.UUID] = base.ModelStatus{
   443  			UUID:               env.UUID,
   444  			Life:               string(params.Dying),
   445  			HostedMachineCount: 2,
   446  			ServiceCount:       1,
   447  			Owner:              owner.Canonical(),
   448  		}
   449  	}
   450  
   451  	ctrStatus, envsStatus, err := controller.NewData(s.api, "123")
   452  	c.Assert(err, jc.ErrorIsNil)
   453  	c.Assert(ctrStatus.HostedModelCount, gc.Equals, 2)
   454  	c.Assert(ctrStatus.HostedMachineCount, gc.Equals, 6)
   455  	c.Assert(ctrStatus.ServiceCount, gc.Equals, 3)
   456  	c.Assert(envsStatus, gc.HasLen, 2)
   457  
   458  	for i, expected := range []struct {
   459  		Owner              string
   460  		Name               string
   461  		Life               params.Life
   462  		HostedMachineCount int
   463  		ServiceCount       int
   464  	}{
   465  		{
   466  			Owner:              "bob@local",
   467  			Name:               "env1",
   468  			Life:               params.Dying,
   469  			HostedMachineCount: 2,
   470  			ServiceCount:       1,
   471  		}, {
   472  			Owner:              "jo@local",
   473  			Name:               "env2",
   474  			Life:               params.Dying,
   475  			HostedMachineCount: 2,
   476  			ServiceCount:       1,
   477  		},
   478  	} {
   479  		c.Assert(envsStatus[i].Owner, gc.Equals, expected.Owner)
   480  		c.Assert(envsStatus[i].Name, gc.Equals, expected.Name)
   481  		c.Assert(envsStatus[i].Life, gc.Equals, string(expected.Life))
   482  		c.Assert(envsStatus[i].HostedMachineCount, gc.Equals, expected.HostedMachineCount)
   483  		c.Assert(envsStatus[i].ServiceCount, gc.Equals, expected.ServiceCount)
   484  	}
   485  
   486  }
   487  
   488  func (s *KillSuite) TestFmtControllerStatus(c *gc.C) {
   489  	data := controller.CtrData{
   490  		"uuid",
   491  		string(params.Alive),
   492  		3,
   493  		20,
   494  		8,
   495  	}
   496  	out := controller.FmtCtrStatus(data)
   497  	c.Assert(out, gc.Equals, "Waiting on 3 models, 20 machines, 8 applications")
   498  }
   499  
   500  func (s *KillSuite) TestFmtEnvironStatus(c *gc.C) {
   501  	data := controller.ModelData{
   502  		"uuid",
   503  		"owner@local",
   504  		"envname",
   505  		string(params.Dying),
   506  		8,
   507  		1,
   508  	}
   509  
   510  	out := controller.FmtModelStatus(data)
   511  	c.Assert(out, gc.Equals, "\towner@local/envname (dying), 8 machines, 1 application")
   512  }