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 }