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