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