github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/modeldestroy_test.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common_test 5 6 import ( 7 "time" 8 9 "github.com/juju/errors" 10 "github.com/juju/names/v5" 11 jtesting "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 15 "github.com/juju/juju/apiserver/common" 16 "github.com/juju/juju/apiserver/facades/agent/metricsender" 17 "github.com/juju/juju/core/status" 18 "github.com/juju/juju/state" 19 stateerrors "github.com/juju/juju/state/errors" 20 "github.com/juju/juju/testing" 21 ) 22 23 type destroyModelSuite struct { 24 jtesting.IsolationSuite 25 26 modelManager *mockModelManager 27 metricSender *testMetricSender 28 } 29 30 var _ = gc.Suite(&destroyModelSuite{}) 31 32 func (s *destroyModelSuite) SetUpTest(c *gc.C) { 33 s.IsolationSuite.SetUpTest(c) 34 35 otherModelTag := names.NewModelTag("deadbeef-0bad-400d-8000-4b1d0d06f33d") 36 s.modelManager = &mockModelManager{ 37 models: []*mockModel{ 38 {tag: testing.ModelTag, currentStatus: status.StatusInfo{Status: status.Available}}, 39 {tag: otherModelTag, currentStatus: status.StatusInfo{Status: status.Available}}, 40 }, 41 } 42 s.metricSender = &testMetricSender{} 43 s.PatchValue(common.SendMetrics, s.metricSender.SendMetrics) 44 } 45 46 func (s *destroyModelSuite) TestDestroyModelSendsMetrics(c *gc.C) { 47 err := common.DestroyModel(s.modelManager, nil, nil, nil, nil) 48 c.Assert(err, jc.ErrorIsNil) 49 s.metricSender.CheckCalls(c, []jtesting.StubCall{ 50 {"SendMetrics", []interface{}{s.modelManager}}, 51 }) 52 } 53 54 func (s *destroyModelSuite) TestDestroyModel(c *gc.C) { 55 true_ := true 56 false_ := false 57 zero := time.Second * 0 58 one := time.Second 59 s.testDestroyModel(c, nil, nil, nil, nil) 60 s.testDestroyModel(c, nil, &false_, nil, nil) 61 s.testDestroyModel(c, nil, &false_, &zero, nil) 62 s.testDestroyModel(c, nil, &true_, nil, nil) 63 s.testDestroyModel(c, nil, &true_, &zero, nil) 64 s.testDestroyModel(c, &true_, nil, nil, nil) 65 s.testDestroyModel(c, &true_, &false_, nil, nil) 66 s.testDestroyModel(c, &true_, &false_, &zero, nil) 67 s.testDestroyModel(c, &true_, &true_, nil, nil) 68 s.testDestroyModel(c, &true_, &true_, &zero, nil) 69 s.testDestroyModel(c, &false_, nil, nil, nil) 70 s.testDestroyModel(c, &false_, &false_, nil, nil) 71 s.testDestroyModel(c, &false_, &false_, &zero, nil) 72 s.testDestroyModel(c, &false_, &true_, nil, nil) 73 s.testDestroyModel(c, &false_, &true_, &zero, nil) 74 s.testDestroyModel(c, &false_, &true_, &zero, &one) 75 } 76 77 func (s *destroyModelSuite) testDestroyModel(c *gc.C, destroyStorage, force *bool, maxWait, timeout *time.Duration) { 78 s.modelManager.ResetCalls() 79 s.modelManager.models[0].ResetCalls() 80 81 err := common.DestroyModel(s.modelManager, destroyStorage, force, maxWait, timeout) 82 c.Assert(err, jc.ErrorIsNil) 83 84 s.modelManager.CheckCalls(c, []jtesting.StubCall{ 85 {"GetBlockForType", []interface{}{state.DestroyBlock}}, 86 {"GetBlockForType", []interface{}{state.RemoveBlock}}, 87 {"GetBlockForType", []interface{}{state.ChangeBlock}}, 88 {"Model", nil}, 89 }) 90 91 expectedModelCalls := []jtesting.StubCall{{"Destroy", []interface{}{state.DestroyModelParams{ 92 DestroyStorage: destroyStorage, 93 Force: force, 94 MaxWait: common.MaxWait(maxWait), 95 Timeout: timeout, 96 }}}} 97 notForcing := force == nil || !*force 98 if notForcing { 99 // We expect to check model status. 100 expectedModelCalls = append([]jtesting.StubCall{{"Status", nil}}, expectedModelCalls...) 101 } 102 s.modelManager.models[0].CheckCalls(c, expectedModelCalls) 103 } 104 105 func (s *destroyModelSuite) TestDestroyModelBlocked(c *gc.C) { 106 s.modelManager.SetErrors(errors.New("nope")) 107 108 err := common.DestroyModel(s.modelManager, nil, nil, nil, nil) 109 c.Assert(err, gc.ErrorMatches, "nope") 110 111 s.modelManager.CheckCallNames(c, "GetBlockForType") 112 s.modelManager.models[0].CheckNoCalls(c) 113 } 114 115 func (s *destroyModelSuite) TestDestroyModelIgnoresErrorsWithForce(c *gc.C) { 116 s.modelManager.models[0].SetErrors( 117 errors.New("nope"), 118 ) 119 120 true_ := true 121 err := common.DestroyModel(s.modelManager, nil, &true_, nil, nil) 122 c.Assert(err, jc.ErrorIsNil) 123 124 s.modelManager.CheckCallNames(c, "GetBlockForType", "GetBlockForType", "GetBlockForType", "Model") 125 s.modelManager.models[0].CheckCallNames(c, "Destroy") 126 } 127 128 func (s *destroyModelSuite) TestDestroyModelNotIgnoreErrorsrWithForce(c *gc.C) { 129 s.modelManager.models[0].SetErrors( 130 stateerrors.PersistentStorageError, 131 ) 132 true_ := true 133 err := common.DestroyModel(s.modelManager, nil, &true_, nil, nil) 134 c.Assert(errors.Is(err, stateerrors.PersistentStorageError), jc.IsTrue) 135 136 s.modelManager.CheckCallNames(c, "GetBlockForType", "GetBlockForType", "GetBlockForType", "Model") 137 s.modelManager.models[0].CheckCallNames(c, "Destroy") 138 } 139 140 func (s *destroyModelSuite) TestDestroyControllerNonControllerModel(c *gc.C) { 141 s.modelManager.models[0].tag = s.modelManager.models[1].tag 142 err := common.DestroyController(s.modelManager, false, nil, nil, nil, nil) 143 c.Assert(err, gc.ErrorMatches, `expected state for controller model UUID deadbeef-0bad-400d-8000-4b1d0d06f33d, got deadbeef-0bad-400d-8000-4b1d0d06f00d`) 144 } 145 146 func (s *destroyModelSuite) TestDestroyController(c *gc.C) { 147 err := common.DestroyController(s.modelManager, false, nil, nil, nil, nil) 148 c.Assert(err, jc.ErrorIsNil) 149 150 s.modelManager.CheckCalls(c, []jtesting.StubCall{ 151 {"ControllerModelTag", nil}, 152 {"GetBlockForType", []interface{}{state.DestroyBlock}}, 153 {"GetBlockForType", []interface{}{state.RemoveBlock}}, 154 {"GetBlockForType", []interface{}{state.ChangeBlock}}, 155 {"Model", nil}, 156 }) 157 s.modelManager.models[0].CheckCalls(c, []jtesting.StubCall{ 158 {"Status", nil}, 159 {"Destroy", []interface{}{state.DestroyModelParams{ 160 MaxWait: common.MaxWait(nil), 161 }}}, 162 }) 163 } 164 165 func (s *destroyModelSuite) TestDestroyControllerReleaseStorage(c *gc.C) { 166 destroyStorage := false 167 err := common.DestroyController(s.modelManager, false, &destroyStorage, nil, nil, nil) 168 c.Assert(err, jc.ErrorIsNil) 169 170 s.modelManager.CheckCalls(c, []jtesting.StubCall{ 171 {"ControllerModelTag", nil}, 172 {"GetBlockForType", []interface{}{state.DestroyBlock}}, 173 {"GetBlockForType", []interface{}{state.RemoveBlock}}, 174 {"GetBlockForType", []interface{}{state.ChangeBlock}}, 175 {"Model", nil}, 176 }) 177 s.modelManager.models[0].CheckCalls(c, []jtesting.StubCall{ 178 {"Status", nil}, 179 {"Destroy", []interface{}{state.DestroyModelParams{ 180 DestroyStorage: &destroyStorage, 181 MaxWait: common.MaxWait(nil), 182 }}}, 183 }) 184 } 185 186 func (s *destroyModelSuite) TestDestroyControllerForce(c *gc.C) { 187 force := true 188 timeout := time.Hour 189 maxWait := time.Second 190 err := common.DestroyController(s.modelManager, false, nil, &force, &maxWait, &timeout) 191 c.Assert(err, jc.ErrorIsNil) 192 193 s.modelManager.CheckCalls(c, []jtesting.StubCall{ 194 {"ControllerModelTag", nil}, 195 {"GetBlockForType", []interface{}{state.DestroyBlock}}, 196 {"GetBlockForType", []interface{}{state.RemoveBlock}}, 197 {"GetBlockForType", []interface{}{state.ChangeBlock}}, 198 {"Model", nil}, 199 }) 200 s.modelManager.models[0].CheckCalls(c, []jtesting.StubCall{ 201 {"Destroy", []interface{}{state.DestroyModelParams{ 202 Force: &force, 203 Timeout: &timeout, 204 MaxWait: maxWait, 205 }}}, 206 }) 207 } 208 209 func (s *destroyModelSuite) TestDestroyControllerDestroyHostedModels(c *gc.C) { 210 err := common.DestroyController(s.modelManager, true, nil, nil, nil, nil) 211 c.Assert(err, jc.ErrorIsNil) 212 213 s.modelManager.CheckCalls(c, []jtesting.StubCall{ 214 {"ControllerModelTag", nil}, 215 {"AllModelUUIDs", nil}, 216 217 {"GetBackend", []interface{}{s.modelManager.models[0].tag.Id()}}, 218 {"GetBlockForType", []interface{}{state.DestroyBlock}}, 219 {"GetBlockForType", []interface{}{state.RemoveBlock}}, 220 {"GetBlockForType", []interface{}{state.ChangeBlock}}, 221 222 {"GetBackend", []interface{}{s.modelManager.models[1].tag.Id()}}, 223 {"GetBlockForType", []interface{}{state.DestroyBlock}}, 224 {"GetBlockForType", []interface{}{state.RemoveBlock}}, 225 {"GetBlockForType", []interface{}{state.ChangeBlock}}, 226 227 {"GetBlockForType", []interface{}{state.DestroyBlock}}, 228 {"GetBlockForType", []interface{}{state.RemoveBlock}}, 229 {"GetBlockForType", []interface{}{state.ChangeBlock}}, 230 {"Model", nil}, 231 }) 232 s.modelManager.models[0].CheckCalls(c, []jtesting.StubCall{ 233 {"Status", nil}, 234 {"Destroy", []interface{}{state.DestroyModelParams{ 235 DestroyHostedModels: true, 236 MaxWait: common.MaxWait(nil), 237 }}}, 238 }) 239 s.metricSender.CheckCalls(c, []jtesting.StubCall{ 240 // One call per hosted model, and one for the controller model. 241 {"SendMetrics", []interface{}{s.modelManager}}, 242 {"SendMetrics", []interface{}{s.modelManager}}, 243 {"SendMetrics", []interface{}{s.modelManager}}, 244 }) 245 } 246 247 func (s *destroyModelSuite) TestDestroyControllerModelErrs(c *gc.C) { 248 // This is similar to what we'd see if a model was destroyed 249 // but there are still some connections to it lingering. 250 s.modelManager.SetErrors( 251 nil, // for GetBackend, 1st model 252 nil, // for GetBlockForType, 1st model 253 nil, // for GetBlockForType, 1st model 254 nil, // for GetBlockForType, 1st model 255 errors.NotFoundf("pretend I am not here"), // for GetBackend, 2nd model 256 ) 257 err := common.DestroyController(s.modelManager, true, nil, nil, nil, nil) 258 // Processing continued despite one model erring out. 259 c.Assert(err, jc.ErrorIsNil) 260 261 s.modelManager.SetErrors( 262 nil, // for GetBackend, 1st model 263 nil, // for GetBlockForType, 1st model 264 nil, // for GetBlockForType, 1st model 265 nil, // for GetBlockForType, 1st model 266 errors.New("I have a problem"), // for GetBackend, 2nd model 267 ) 268 err = common.DestroyController(s.modelManager, true, nil, nil, nil, nil) 269 // Processing erred out since a model seriously failed. 270 c.Assert(err, gc.ErrorMatches, "I have a problem") 271 } 272 273 func (s *destroyModelSuite) TestDestroyModelWithInvalidCredentialWithoutForce(c *gc.C) { 274 s.modelManager.models[0].currentStatus = status.StatusInfo{Status: status.Suspended} 275 276 err := common.DestroyModel(s.modelManager, nil, nil, nil, nil) 277 c.Assert(err, gc.ErrorMatches, "invalid cloud credential, use --force") 278 } 279 280 func (s *destroyModelSuite) TestDestroyModelWithInvalidCredentialWithForce(c *gc.C) { 281 s.modelManager.models[0].currentStatus = status.StatusInfo{Status: status.Suspended} 282 true_ := true 283 s.testDestroyModel(c, nil, &true_, nil, nil) 284 } 285 286 type testMetricSender struct { 287 jtesting.Stub 288 } 289 290 func (t *testMetricSender) SendMetrics(st metricsender.ModelBackend) error { 291 t.MethodCall(t, "SendMetrics", st) 292 return t.NextErr() 293 } 294 295 type mockModelManager struct { 296 common.ModelManagerBackend 297 jtesting.Stub 298 299 models []*mockModel 300 } 301 302 func (m *mockModelManager) ControllerModelUUID() string { 303 m.MethodCall(m, "ControllerModelUUID") 304 return m.models[0].UUID() 305 } 306 307 func (m *mockModelManager) ControllerModelTag() names.ModelTag { 308 m.MethodCall(m, "ControllerModelTag") 309 return m.models[0].ModelTag() 310 } 311 312 func (m *mockModelManager) ModelTag() names.ModelTag { 313 return testing.ModelTag 314 } 315 316 func (m *mockModelManager) GetBlockForType(t state.BlockType) (state.Block, bool, error) { 317 m.MethodCall(m, "GetBlockForType", t) 318 return nil, false, m.NextErr() 319 } 320 321 func (m *mockModelManager) AllModelUUIDs() ([]string, error) { 322 m.MethodCall(m, "AllModelUUIDs") 323 var out []string 324 for _, model := range m.models { 325 out = append(out, model.UUID()) 326 } 327 return out, nil 328 } 329 330 func (m *mockModelManager) Model() (common.Model, error) { 331 m.MethodCall(m, "Model") 332 return m.models[0], m.NextErr() 333 } 334 335 func (m *mockModelManager) GetBackend(uuid string) (common.ModelManagerBackend, func() bool, error) { 336 m.MethodCall(m, "GetBackend", uuid) 337 return m, func() bool { return true }, m.NextErr() 338 } 339 340 func (m *mockModelManager) Close() error { 341 m.MethodCall(m, "Close") 342 return m.NextErr() 343 } 344 345 type mockModel struct { 346 common.Model 347 jtesting.Stub 348 tag names.ModelTag 349 currentStatus status.StatusInfo 350 } 351 352 func (m *mockModel) ModelTag() names.ModelTag { 353 return m.tag 354 } 355 356 func (m *mockModel) UUID() string { 357 return m.tag.Id() 358 } 359 360 func (m *mockModel) Destroy(args state.DestroyModelParams) error { 361 m.MethodCall(m, "Destroy", args) 362 return m.NextErr() 363 } 364 365 func (m *mockModel) Status() (status.StatusInfo, error) { 366 m.MethodCall(m, "Status") 367 return m.currentStatus, m.NextErr() 368 }