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 }