github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/featuretests/cmd_juju_controller_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package featuretests
     5  
     6  import (
     7  	"os"
     8  	"reflect"
     9  	"time"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/loggo"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/names.v2"
    17  	"gopkg.in/yaml.v2"
    18  
    19  	"github.com/juju/juju/api"
    20  	"github.com/juju/juju/api/base"
    21  	"github.com/juju/juju/api/modelmanager"
    22  	"github.com/juju/juju/cmd/juju/commands"
    23  	"github.com/juju/juju/instance"
    24  	"github.com/juju/juju/juju"
    25  	jujutesting "github.com/juju/juju/juju/testing"
    26  	"github.com/juju/juju/jujuclient"
    27  	"github.com/juju/juju/provider/dummy"
    28  	"github.com/juju/juju/state"
    29  	"github.com/juju/juju/status"
    30  	"github.com/juju/juju/testing"
    31  	"github.com/juju/juju/testing/factory"
    32  )
    33  
    34  type cmdControllerSuite struct {
    35  	jujutesting.JujuConnSuite
    36  }
    37  
    38  func (s *cmdControllerSuite) run(c *gc.C, args ...string) *cmd.Context {
    39  	context := testing.Context(c)
    40  	command := commands.NewJujuCommand(context)
    41  	c.Assert(testing.InitCommand(command, args), jc.ErrorIsNil)
    42  	c.Assert(command.Run(context), jc.ErrorIsNil)
    43  	loggo.RemoveWriter("warning")
    44  	return context
    45  }
    46  
    47  func (s *cmdControllerSuite) createModelAdminUser(c *gc.C, modelname string, isServer bool) base.ModelInfo {
    48  	modelManager := modelmanager.NewClient(s.OpenControllerAPI(c))
    49  	defer modelManager.Close()
    50  	model, err := modelManager.CreateModel(
    51  		modelname, s.AdminUserTag(c).Id(), "", "", names.CloudCredentialTag{}, map[string]interface{}{
    52  			"controller": isServer,
    53  		},
    54  	)
    55  	c.Assert(err, jc.ErrorIsNil)
    56  	return model
    57  }
    58  
    59  func (s *cmdControllerSuite) createModelNormalUser(c *gc.C, modelname string, isServer bool) {
    60  	s.run(c, "add-user", "test")
    61  	modelManager := modelmanager.NewClient(s.OpenControllerAPI(c))
    62  	defer modelManager.Close()
    63  	_, err := modelManager.CreateModel(
    64  		modelname, names.NewLocalUserTag("test").Id(), "", "", names.CloudCredentialTag{}, map[string]interface{}{
    65  			"authorized-keys": "ssh-key",
    66  			"controller":      isServer,
    67  		},
    68  	)
    69  	c.Assert(err, jc.ErrorIsNil)
    70  }
    71  
    72  func (s *cmdControllerSuite) TestControllerListCommand(c *gc.C) {
    73  	context := s.run(c, "list-controllers")
    74  	expectedOutput := `
    75  Use --refresh to see the latest information.
    76  
    77  Controller  Model       User   Access     Cloud/Region        Models  Machines  HA  Version
    78  kontroll*   controller  admin  superuser  dummy/dummy-region       -         -   -  (unknown)  
    79  
    80  `[1:]
    81  	c.Assert(testing.Stdout(context), gc.Equals, expectedOutput)
    82  }
    83  
    84  func (s *cmdControllerSuite) TestCreateModelAdminUser(c *gc.C) {
    85  	s.createModelAdminUser(c, "new-model", false)
    86  	context := s.run(c, "list-models")
    87  	c.Assert(testing.Stdout(context), gc.Equals, ""+
    88  		"Controller: kontroll\n"+
    89  		"\n"+
    90  		"Model        Owner  Status     Access  Last connection\n"+
    91  		"controller*  admin  available  admin   just now\n"+
    92  		"new-model    admin  available  admin   never connected\n"+
    93  		"\n")
    94  }
    95  
    96  func (s *cmdControllerSuite) TestAddModelNormalUser(c *gc.C) {
    97  	s.createModelNormalUser(c, "new-model", false)
    98  	context := s.run(c, "list-models", "--all")
    99  	c.Assert(testing.Stdout(context), gc.Equals, ""+
   100  		"Controller: kontroll\n"+
   101  		"\n"+
   102  		"Model              Owner  Status     Access  Last connection\n"+
   103  		"admin/controller*  admin  available  admin   just now\n"+
   104  		"test/new-model     test   available          never connected\n"+
   105  		"\n")
   106  }
   107  
   108  func (s *cmdControllerSuite) TestListModelsYAML(c *gc.C) {
   109  	s.Factory.MakeMachine(c, nil)
   110  	two := uint64(2)
   111  	s.Factory.MakeMachine(c, &factory.MachineParams{Characteristics: &instance.HardwareCharacteristics{CpuCores: &two}})
   112  	context := s.run(c, "list-models", "--format=yaml")
   113  	c.Assert(testing.Stdout(context), gc.Matches, `
   114  models:
   115  - name: controller
   116    model-uuid: deadbeef-0bad-400d-8000-4b1d0d06f00d
   117    controller-uuid: deadbeef-1bad-500d-9000-4b1d0d06f00d
   118    controller-name: kontroll
   119    owner: admin
   120    cloud: dummy
   121    region: dummy-region
   122    type: dummy
   123    life: alive
   124    status:
   125      current: available
   126      since: .*
   127    users:
   128      admin:
   129        display-name: admin
   130        access: admin
   131        last-connection: just now
   132    machines:
   133      "0":
   134        cores: 0
   135      "1":
   136        cores: 2
   137  current-model: controller
   138  `[1:])
   139  }
   140  
   141  func (s *cmdControllerSuite) TestListDeadModels(c *gc.C) {
   142  	modelInfo := s.createModelAdminUser(c, "new-model", false)
   143  	st, err := s.State.ForModel(names.NewModelTag(modelInfo.UUID))
   144  	c.Assert(err, jc.ErrorIsNil)
   145  	defer st.Close()
   146  	m, err := st.Model()
   147  	c.Assert(err, jc.ErrorIsNil)
   148  	err = m.Destroy()
   149  	c.Assert(err, jc.ErrorIsNil)
   150  	now := time.Now()
   151  	sInfo := status.StatusInfo{
   152  		Status:  status.Destroying,
   153  		Message: "",
   154  		Since:   &now,
   155  	}
   156  	err = m.SetStatus(sInfo)
   157  	c.Assert(err, jc.ErrorIsNil)
   158  
   159  	// Dead models still show up in the list. It's a lie to pretend they
   160  	// don't exist, and they will go away quickly.
   161  	context := s.run(c, "list-models")
   162  	c.Assert(testing.Stdout(context), gc.Equals, ""+
   163  		"Controller: kontroll\n"+
   164  		"\n"+
   165  		"Model        Owner  Status      Access  Last connection\n"+
   166  		"controller*  admin  available   admin   just now\n"+
   167  		"new-model    admin  destroying  admin   never connected\n"+
   168  		"\n")
   169  }
   170  
   171  func (s *cmdControllerSuite) TestAddModel(c *gc.C) {
   172  	s.testAddModel(c)
   173  }
   174  
   175  func (s *cmdControllerSuite) TestAddModelWithCloudAndRegion(c *gc.C) {
   176  	s.testAddModel(c, "dummy/dummy-region")
   177  }
   178  
   179  func (s *cmdControllerSuite) testAddModel(c *gc.C, args ...string) {
   180  	// The JujuConnSuite doesn't set up an ssh key in the fake home dir,
   181  	// so fake one on the command line.  The dummy provider also expects
   182  	// a config value for 'controller'.
   183  	args = append([]string{"add-model", "new-model"}, args...)
   184  	args = append(args,
   185  		"--config", "authorized-keys=fake-key",
   186  		"--config", "controller=false",
   187  	)
   188  	context := s.run(c, args...)
   189  	c.Check(testing.Stdout(context), gc.Equals, "")
   190  	c.Check(testing.Stderr(context), gc.Equals, `
   191  Added 'new-model' model on dummy/dummy-region with credential 'cred' for user 'admin'
   192  `[1:])
   193  
   194  	// Make sure that the saved server details are sufficient to connect
   195  	// to the api server.
   196  	accountDetails, err := s.ControllerStore.AccountDetails("kontroll")
   197  	c.Assert(err, jc.ErrorIsNil)
   198  	modelDetails, err := s.ControllerStore.ModelByName("kontroll", "admin/new-model")
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	api, err := juju.NewAPIConnection(juju.NewAPIConnectionParams{
   201  		Store:          s.ControllerStore,
   202  		ControllerName: "kontroll",
   203  		AccountDetails: accountDetails,
   204  		ModelUUID:      modelDetails.ModelUUID,
   205  		DialOpts:       api.DefaultDialOpts(),
   206  		OpenAPI:        api.Open,
   207  	})
   208  	c.Assert(err, jc.ErrorIsNil)
   209  	api.Close()
   210  }
   211  
   212  func (s *cmdControllerSuite) TestControllerDestroy(c *gc.C) {
   213  	s.testControllerDestroy(c, false)
   214  }
   215  
   216  func (s *cmdControllerSuite) TestControllerDestroyUsingAPI(c *gc.C) {
   217  	s.testControllerDestroy(c, true)
   218  }
   219  
   220  func (s *cmdControllerSuite) testControllerDestroy(c *gc.C, forceAPI bool) {
   221  	st := s.Factory.MakeModel(c, &factory.ModelParams{
   222  		Name:        "just-a-controller",
   223  		ConfigAttrs: testing.Attrs{"controller": true},
   224  		CloudRegion: "dummy-region",
   225  	})
   226  	defer st.Close()
   227  	factory.NewFactory(st).MakeApplication(c, nil)
   228  
   229  	stop := make(chan struct{})
   230  	done := make(chan struct{})
   231  	// In order for the destroy controller command to complete we need to run
   232  	// the code that the cleaner and undertaker workers would be running in
   233  	// the agent in order to progress the lifecycle of the hosted model,
   234  	// and cleanup the documents.
   235  	go func() {
   236  		defer close(done)
   237  		a := testing.LongAttempt.Start()
   238  		for a.Next() {
   239  			err := s.State.Cleanup()
   240  			c.Check(err, jc.ErrorIsNil)
   241  			err = st.Cleanup()
   242  			c.Check(err, jc.ErrorIsNil)
   243  			err = st.ProcessDyingModel()
   244  			if errors.Cause(err) != state.ErrModelNotDying {
   245  				c.Check(err, jc.ErrorIsNil)
   246  				if err == nil {
   247  					// success!
   248  					return
   249  				}
   250  			}
   251  			select {
   252  			case <-stop:
   253  				return
   254  			default:
   255  				// retry
   256  			}
   257  		}
   258  	}()
   259  
   260  	if forceAPI {
   261  		// Remove bootstrap config from the client store,
   262  		// forcing the command to use the API.
   263  		err := os.Remove(jujuclient.JujuBootstrapConfigPath())
   264  		c.Assert(err, jc.ErrorIsNil)
   265  	}
   266  
   267  	ops := make(chan dummy.Operation, 1)
   268  	dummy.Listen(ops)
   269  
   270  	s.run(c, "destroy-controller", "kontroll", "-y", "--destroy-all-models", "--debug")
   271  	close(stop)
   272  	<-done
   273  
   274  	destroyOp := (<-ops).(dummy.OpDestroy)
   275  	c.Assert(destroyOp.Env, gc.Equals, "controller")
   276  	c.Assert(destroyOp.Cloud, gc.Equals, "dummy")
   277  	c.Assert(destroyOp.CloudRegion, gc.Equals, "dummy-region")
   278  
   279  	store := jujuclient.NewFileClientStore()
   280  	_, err := store.ControllerByName("kontroll")
   281  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   282  }
   283  
   284  func (s *cmdControllerSuite) TestEnableDestroyController(c *gc.C) {
   285  	s.State.SwitchBlockOn(state.DestroyBlock, "TestBlockDestroyModel")
   286  	s.State.SwitchBlockOn(state.ChangeBlock, "TestChangeBlock")
   287  
   288  	s.run(c, "enable-destroy-controller")
   289  
   290  	blocks, err := s.State.AllBlocksForController()
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	c.Assert(blocks, gc.HasLen, 0)
   293  }
   294  
   295  func (s *cmdControllerSuite) TestControllerKill(c *gc.C) {
   296  	st := s.Factory.MakeModel(c, &factory.ModelParams{
   297  		Name:        "foo",
   298  		CloudRegion: "dummy-region",
   299  	})
   300  
   301  	st.SwitchBlockOn(state.DestroyBlock, "TestBlockDestroyModel")
   302  	st.Close()
   303  
   304  	s.run(c, "kill-controller", "kontroll", "-y")
   305  
   306  	store := jujuclient.NewFileClientStore()
   307  	_, err := store.ControllerByName("kontroll")
   308  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   309  }
   310  
   311  func (s *cmdControllerSuite) TestSystemKillCallsEnvironDestroyOnHostedEnviron(c *gc.C) {
   312  	st := s.Factory.MakeModel(c, &factory.ModelParams{
   313  		Name: "foo",
   314  	})
   315  	defer st.Close()
   316  
   317  	st.SwitchBlockOn(state.DestroyBlock, "TestBlockDestroyModel")
   318  	st.Close()
   319  
   320  	opc := make(chan dummy.Operation, 200)
   321  	dummy.Listen(opc)
   322  
   323  	store := jujuclient.NewFileClientStore()
   324  	_, err := store.ControllerByName("kontroll")
   325  	c.Assert(err, jc.ErrorIsNil)
   326  
   327  	s.run(c, "kill-controller", "kontroll", "-y")
   328  
   329  	// Ensure that Destroy was called on the hosted environ ...
   330  	// TODO(fwereade): how do we know it's the hosted environ?
   331  	// what actual interactions made it ok to destroy any environ
   332  	// here? (there used to be an undertaker that didn't work...)
   333  	opRecvTimeout(c, st, opc, dummy.OpDestroy{})
   334  
   335  	// ... and that the details were removed removed from
   336  	// the client store.
   337  	_, err = store.ControllerByName("kontroll")
   338  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   339  }
   340  
   341  // opRecvTimeout waits for any of the given kinds of operation to
   342  // be received from ops, and times out if not.
   343  func opRecvTimeout(c *gc.C, st *state.State, opc <-chan dummy.Operation, kinds ...dummy.Operation) dummy.Operation {
   344  	st.StartSync()
   345  	for {
   346  		select {
   347  		case op := <-opc:
   348  			for _, k := range kinds {
   349  				if reflect.TypeOf(op) == reflect.TypeOf(k) {
   350  					return op
   351  				}
   352  			}
   353  			c.Logf("discarding unknown event %#v", op)
   354  		case <-time.After(testing.LongWait):
   355  			c.Fatalf("time out wating for operation")
   356  		}
   357  	}
   358  }
   359  
   360  func (s *cmdControllerSuite) TestGetControllerConfigYAML(c *gc.C) {
   361  	context := s.run(c, "controller-config", "--format=yaml")
   362  	controllerCfg, err := s.State.ControllerConfig()
   363  	c.Assert(err, jc.ErrorIsNil)
   364  	cfgYaml, err := yaml.Marshal(controllerCfg)
   365  	c.Assert(err, jc.ErrorIsNil)
   366  	c.Assert(testing.Stdout(context), gc.Equals, string(cfgYaml))
   367  }