github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/system/destroy_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package system_test 5 6 import ( 7 "bytes" 8 "time" 9 10 "github.com/juju/cmd" 11 "github.com/juju/errors" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/cmd/juju/system" 17 cmdtesting "github.com/juju/juju/cmd/testing" 18 "github.com/juju/juju/environs/config" 19 "github.com/juju/juju/environs/configstore" 20 _ "github.com/juju/juju/provider/dummy" 21 "github.com/juju/juju/testing" 22 ) 23 24 type DestroySuite struct { 25 testing.FakeJujuHomeSuite 26 api *fakeDestroyAPI 27 clientapi *fakeDestroyAPIClient 28 store configstore.Storage 29 apierror error 30 } 31 32 var _ = gc.Suite(&DestroySuite{}) 33 34 // fakeDestroyAPI mocks out the systemmanager API 35 type fakeDestroyAPI struct { 36 err error 37 env map[string]interface{} 38 destroyAll bool 39 ignoreBlocks bool 40 blocks []params.EnvironmentBlockInfo 41 blocksErr error 42 } 43 44 func (f *fakeDestroyAPI) Close() error { return nil } 45 46 func (f *fakeDestroyAPI) EnvironmentConfig() (map[string]interface{}, error) { 47 if f.err != nil { 48 return nil, f.err 49 } 50 return f.env, nil 51 } 52 53 func (f *fakeDestroyAPI) DestroySystem(destroyAll bool, ignoreBlocks bool) error { 54 f.destroyAll = destroyAll 55 f.ignoreBlocks = ignoreBlocks 56 return f.err 57 } 58 59 func (f *fakeDestroyAPI) ListBlockedEnvironments() ([]params.EnvironmentBlockInfo, error) { 60 return f.blocks, f.blocksErr 61 } 62 63 // fakeDestroyAPIClient mocks out the client API 64 type fakeDestroyAPIClient struct { 65 err error 66 env map[string]interface{} 67 envgetcalled bool 68 destroycalled bool 69 } 70 71 func (f *fakeDestroyAPIClient) Close() error { return nil } 72 73 func (f *fakeDestroyAPIClient) EnvironmentGet() (map[string]interface{}, error) { 74 f.envgetcalled = true 75 if f.err != nil { 76 return nil, f.err 77 } 78 return f.env, nil 79 } 80 81 func (f *fakeDestroyAPIClient) DestroyEnvironment() error { 82 f.destroycalled = true 83 return f.err 84 } 85 86 func createBootstrapInfo(c *gc.C, name string) map[string]interface{} { 87 cfg, err := config.New(config.UseDefaults, map[string]interface{}{ 88 "type": "dummy", 89 "name": name, 90 "state-server": "true", 91 "state-id": "1", 92 }) 93 c.Assert(err, jc.ErrorIsNil) 94 return cfg.AllAttrs() 95 } 96 97 func (s *DestroySuite) SetUpTest(c *gc.C) { 98 s.FakeJujuHomeSuite.SetUpTest(c) 99 s.clientapi = &fakeDestroyAPIClient{} 100 s.api = &fakeDestroyAPI{} 101 s.apierror = nil 102 103 var err error 104 s.store, err = configstore.Default() 105 c.Assert(err, jc.ErrorIsNil) 106 107 var envList = []struct { 108 name string 109 serverUUID string 110 envUUID string 111 bootstrapCfg map[string]interface{} 112 }{ 113 { 114 name: "test1", 115 serverUUID: "test1-uuid", 116 envUUID: "test1-uuid", 117 bootstrapCfg: createBootstrapInfo(c, "test1"), 118 }, { 119 name: "test2", 120 serverUUID: "test1-uuid", 121 envUUID: "test2-uuid", 122 }, { 123 name: "test3", 124 envUUID: "test3-uuid", 125 }, 126 } 127 for _, env := range envList { 128 info := s.store.CreateInfo(env.name) 129 info.SetAPIEndpoint(configstore.APIEndpoint{ 130 Addresses: []string{"localhost"}, 131 CACert: testing.CACert, 132 EnvironUUID: env.envUUID, 133 ServerUUID: env.serverUUID, 134 }) 135 136 if env.bootstrapCfg != nil { 137 info.SetBootstrapConfig(env.bootstrapCfg) 138 } 139 err := info.Write() 140 c.Assert(err, jc.ErrorIsNil) 141 } 142 } 143 144 func (s *DestroySuite) runDestroyCommand(c *gc.C, args ...string) (*cmd.Context, error) { 145 cmd := system.NewDestroyCommand(s.api, s.clientapi, s.apierror) 146 return testing.RunCommand(c, cmd, args...) 147 } 148 149 func (s *DestroySuite) newDestroyCommand() *system.DestroyCommand { 150 return system.NewDestroyCommand(s.api, s.clientapi, s.apierror) 151 } 152 153 func checkSystemExistsInStore(c *gc.C, name string, store configstore.Storage) { 154 _, err := store.ReadInfo(name) 155 c.Check(err, jc.ErrorIsNil) 156 } 157 158 func checkSystemRemovedFromStore(c *gc.C, name string, store configstore.Storage) { 159 _, err := store.ReadInfo(name) 160 c.Check(err, jc.Satisfies, errors.IsNotFound) 161 } 162 163 func (s *DestroySuite) TestDestroyNoSystemNameError(c *gc.C) { 164 _, err := s.runDestroyCommand(c) 165 c.Assert(err, gc.ErrorMatches, "no system specified") 166 } 167 168 func (s *DestroySuite) TestDestroyBadFlags(c *gc.C) { 169 _, err := s.runDestroyCommand(c, "-n") 170 c.Assert(err, gc.ErrorMatches, "flag provided but not defined: -n") 171 } 172 173 func (s *DestroySuite) TestDestroyUnknownArgument(c *gc.C) { 174 _, err := s.runDestroyCommand(c, "environment", "whoops") 175 c.Assert(err, gc.ErrorMatches, `unrecognized args: \["whoops"\]`) 176 } 177 178 func (s *DestroySuite) TestDestroyUnknownSystem(c *gc.C) { 179 _, err := s.runDestroyCommand(c, "foo") 180 c.Assert(err, gc.ErrorMatches, `cannot read system info: environment "foo" not found`) 181 } 182 183 func (s *DestroySuite) TestDestroyNonSystemEnvFails(c *gc.C) { 184 _, err := s.runDestroyCommand(c, "test2") 185 c.Assert(err, gc.ErrorMatches, "\"test2\" is not a system; use juju environment destroy to destroy it") 186 } 187 188 func (s *DestroySuite) TestDestroySystemNotFoundNotRemovedFromStore(c *gc.C) { 189 s.apierror = errors.NotFoundf("test1") 190 _, err := s.runDestroyCommand(c, "test1", "-y") 191 c.Assert(err, gc.ErrorMatches, "cannot connect to API: test1 not found") 192 c.Check(c.GetTestLog(), jc.Contains, "If the system is unusable") 193 checkSystemExistsInStore(c, "test1", s.store) 194 } 195 196 func (s *DestroySuite) TestDestroyCannotConnectToAPI(c *gc.C) { 197 s.apierror = errors.New("connection refused") 198 _, err := s.runDestroyCommand(c, "test1", "-y") 199 c.Assert(err, gc.ErrorMatches, "cannot connect to API: connection refused") 200 c.Check(c.GetTestLog(), jc.Contains, "If the system is unusable") 201 checkSystemExistsInStore(c, "test1", s.store) 202 } 203 204 func (s *DestroySuite) TestDestroy(c *gc.C) { 205 _, err := s.runDestroyCommand(c, "test1", "-y") 206 c.Assert(err, jc.ErrorIsNil) 207 c.Assert(s.api.ignoreBlocks, jc.IsFalse) 208 c.Assert(s.api.destroyAll, jc.IsFalse) 209 c.Assert(s.clientapi.destroycalled, jc.IsFalse) 210 checkSystemRemovedFromStore(c, "test1", s.store) 211 } 212 213 func (s *DestroySuite) TestDestroyWithDestroyAllEnvsFlag(c *gc.C) { 214 _, err := s.runDestroyCommand(c, "test1", "-y", "--destroy-all-environments") 215 c.Assert(err, jc.ErrorIsNil) 216 c.Assert(s.api.ignoreBlocks, jc.IsFalse) 217 c.Assert(s.api.destroyAll, jc.IsTrue) 218 checkSystemRemovedFromStore(c, "test1", s.store) 219 } 220 221 func (s *DestroySuite) TestDestroyEnvironmentGetFails(c *gc.C) { 222 s.api.err = errors.NotFoundf(`system "test3"`) 223 _, err := s.runDestroyCommand(c, "test3", "-y") 224 c.Assert(err, gc.ErrorMatches, "cannot obtain bootstrap information: system \"test3\" not found") 225 checkSystemExistsInStore(c, "test3", s.store) 226 } 227 228 func (s *DestroySuite) TestDestroyFallsBackToClient(c *gc.C) { 229 s.api.err = ¶ms.Error{"DestroyEnvironment", params.CodeNotImplemented} 230 _, err := s.runDestroyCommand(c, "test1", "-y") 231 c.Assert(err, jc.ErrorIsNil) 232 c.Assert(s.clientapi.destroycalled, jc.IsTrue) 233 checkSystemRemovedFromStore(c, "test1", s.store) 234 } 235 236 func (s *DestroySuite) TestEnvironmentGetFallsBackToClient(c *gc.C) { 237 s.api.err = ¶ms.Error{"EnvironmentGet", params.CodeNotImplemented} 238 s.clientapi.env = createBootstrapInfo(c, "test3") 239 _, err := s.runDestroyCommand(c, "test3", "-y") 240 c.Assert(err, jc.ErrorIsNil) 241 c.Assert(s.clientapi.envgetcalled, jc.IsTrue) 242 c.Assert(s.clientapi.destroycalled, jc.IsTrue) 243 checkSystemRemovedFromStore(c, "test3", s.store) 244 } 245 246 func (s *DestroySuite) TestFailedDestroyEnvironment(c *gc.C) { 247 s.api.err = errors.New("permission denied") 248 _, err := s.runDestroyCommand(c, "test1", "-y") 249 c.Assert(err, gc.ErrorMatches, "cannot destroy system: permission denied") 250 c.Assert(s.api.ignoreBlocks, jc.IsFalse) 251 c.Assert(s.api.destroyAll, jc.IsFalse) 252 checkSystemExistsInStore(c, "test1", s.store) 253 } 254 255 func (s *DestroySuite) resetSystem(c *gc.C) { 256 info := s.store.CreateInfo("test1") 257 info.SetAPIEndpoint(configstore.APIEndpoint{ 258 Addresses: []string{"localhost"}, 259 CACert: testing.CACert, 260 EnvironUUID: "test1-uuid", 261 ServerUUID: "test1-uuid", 262 }) 263 info.SetBootstrapConfig(createBootstrapInfo(c, "test1")) 264 err := info.Write() 265 c.Assert(err, jc.ErrorIsNil) 266 } 267 268 func (s *DestroySuite) TestDestroyCommandConfirmation(c *gc.C) { 269 var stdin, stdout bytes.Buffer 270 ctx, err := cmd.DefaultContext() 271 c.Assert(err, jc.ErrorIsNil) 272 ctx.Stdout = &stdout 273 ctx.Stdin = &stdin 274 275 // Ensure confirmation is requested if "-y" is not specified. 276 stdin.WriteString("n") 277 _, errc := cmdtesting.RunCommand(ctx, s.newDestroyCommand(), "test1") 278 select { 279 case err := <-errc: 280 c.Check(err, gc.ErrorMatches, "system destruction aborted") 281 case <-time.After(testing.LongWait): 282 c.Fatalf("command took too long") 283 } 284 c.Check(testing.Stdout(ctx), gc.Matches, "WARNING!.*test1(.|\n)*") 285 checkSystemExistsInStore(c, "test1", s.store) 286 287 // EOF on stdin: equivalent to answering no. 288 stdin.Reset() 289 stdout.Reset() 290 _, errc = cmdtesting.RunCommand(ctx, s.newDestroyCommand(), "test1") 291 select { 292 case err := <-errc: 293 c.Check(err, gc.ErrorMatches, "system destruction aborted") 294 case <-time.After(testing.LongWait): 295 c.Fatalf("command took too long") 296 } 297 c.Check(testing.Stdout(ctx), gc.Matches, "WARNING!.*test1(.|\n)*") 298 checkSystemExistsInStore(c, "test1", s.store) 299 300 for _, answer := range []string{"y", "Y", "yes", "YES"} { 301 stdin.Reset() 302 stdout.Reset() 303 stdin.WriteString(answer) 304 _, errc = cmdtesting.RunCommand(ctx, s.newDestroyCommand(), "test1") 305 select { 306 case err := <-errc: 307 c.Check(err, jc.ErrorIsNil) 308 case <-time.After(testing.LongWait): 309 c.Fatalf("command took too long") 310 } 311 checkSystemRemovedFromStore(c, "test1", s.store) 312 313 // Add the test1 system back into the store for the next test 314 s.resetSystem(c) 315 } 316 } 317 318 func (s *DestroySuite) TestBlockedDestroy(c *gc.C) { 319 s.api.err = ¶ms.Error{Code: params.CodeOperationBlocked} 320 s.runDestroyCommand(c, "test1", "-y") 321 testLog := c.GetTestLog() 322 c.Check(testLog, jc.Contains, "To remove all blocks in the system, please run:") 323 c.Check(testLog, jc.Contains, "juju system remove-blocks") 324 } 325 326 func (s *DestroySuite) TestDestroyListBlocksError(c *gc.C) { 327 s.api.err = ¶ms.Error{Code: params.CodeOperationBlocked} 328 s.api.blocksErr = errors.New("unexpected api error") 329 s.runDestroyCommand(c, "test1", "-y") 330 testLog := c.GetTestLog() 331 c.Check(testLog, jc.Contains, "To remove all blocks in the system, please run:") 332 c.Check(testLog, jc.Contains, "juju system remove-blocks") 333 c.Check(testLog, jc.Contains, "Unable to list blocked environments: unexpected api error") 334 } 335 336 func (s *DestroySuite) TestDestroyReturnsBlocks(c *gc.C) { 337 s.api.err = ¶ms.Error{Code: params.CodeOperationBlocked} 338 s.api.blocks = []params.EnvironmentBlockInfo{ 339 params.EnvironmentBlockInfo{ 340 Name: "test1", 341 UUID: "test1-uuid", 342 OwnerTag: "cheryl@local", 343 Blocks: []string{ 344 "BlockDestroy", 345 }, 346 }, 347 params.EnvironmentBlockInfo{ 348 Name: "test2", 349 UUID: "test2-uuid", 350 OwnerTag: "bob@local", 351 Blocks: []string{ 352 "BlockDestroy", 353 "BlockChange", 354 }, 355 }, 356 } 357 ctx, _ := s.runDestroyCommand(c, "test1", "-y", "--destroy-all-environments") 358 c.Assert(testing.Stderr(ctx), gc.Equals, ""+ 359 "NAME ENVIRONMENT UUID OWNER BLOCKS\n"+ 360 "test1 test1-uuid cheryl@local destroy-environment\n"+ 361 "test2 test2-uuid bob@local destroy-environment,all-changes\n") 362 }