github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/modelcredential_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state_test 5 6 import ( 7 "fmt" 8 9 "github.com/juju/names/v5" 10 jc "github.com/juju/testing/checkers" 11 "github.com/juju/utils/v3" 12 gc "gopkg.in/check.v1" 13 14 "github.com/juju/juju/cloud" 15 "github.com/juju/juju/core/status" 16 "github.com/juju/juju/state" 17 statetesting "github.com/juju/juju/state/testing" 18 "github.com/juju/juju/storage" 19 "github.com/juju/juju/testing" 20 "github.com/juju/juju/testing/factory" 21 ) 22 23 type ModelCredentialSuite struct { 24 ConnSuite 25 26 credentialTag names.CloudCredentialTag 27 } 28 29 var _ = gc.Suite(&ModelCredentialSuite{}) 30 31 func (s *ModelCredentialSuite) SetUpTest(c *gc.C) { 32 s.ConnSuite.SetUpTest(c) 33 34 s.credentialTag = s.createCloudCredential(c, "foobar") 35 } 36 37 func (s *ModelCredentialSuite) TestInvalidateModelCredentialNone(c *gc.C) { 38 // The model created in ConnSuite does not have a credential. 39 m, err := s.State.Model() 40 c.Assert(err, jc.ErrorIsNil) 41 _, exists := m.CloudCredentialTag() 42 c.Assert(exists, jc.IsFalse) 43 _, exists, err = m.CloudCredential() 44 c.Assert(err, jc.ErrorIsNil) 45 c.Assert(exists, jc.IsFalse) 46 47 reason := "special invalidation" 48 err = s.State.InvalidateModelCredential(reason) 49 c.Assert(err, jc.ErrorIsNil) 50 } 51 52 func (s *ModelCredentialSuite) TestInvalidateModelCredential(c *gc.C) { 53 st := s.addModel(c, "abcmodel", s.credentialTag) 54 defer st.Close() 55 credential, err := s.State.CloudCredential(s.credentialTag) 56 c.Assert(err, jc.ErrorIsNil) 57 c.Assert(credential.IsValid(), jc.IsTrue) 58 59 reason := "special invalidation" 60 err = st.InvalidateModelCredential(reason) 61 c.Assert(err, jc.ErrorIsNil) 62 63 invalidated, err := s.State.CloudCredential(s.credentialTag) 64 c.Assert(err, jc.ErrorIsNil) 65 c.Assert(invalidated.IsValid(), jc.IsFalse) 66 c.Assert(invalidated.InvalidReason, gc.DeepEquals, reason) 67 68 m, err := st.Model() 69 c.Assert(err, jc.ErrorIsNil) 70 info, err := m.Status() 71 c.Assert(err, jc.ErrorIsNil) 72 c.Assert(info, jc.DeepEquals, status.StatusInfo{ 73 Status: "suspended", 74 Message: "suspended since cloud credential is not valid", 75 Data: map[string]interface{}{"reason": "special invalidation"}, 76 }) 77 } 78 79 func (s *ModelCredentialSuite) TestValidateCloudCredentialWrongCloud(c *gc.C) { 80 m, err := s.State.Model() 81 c.Assert(err, jc.ErrorIsNil) 82 tag := names.NewCloudCredentialTag("stratus/bob/foobar") 83 cred := cloud.NewCredential(cloud.UserPassAuthType, nil) 84 err = m.ValidateCloudCredential(tag, cred) 85 c.Assert(err, gc.ErrorMatches, `validating credential "stratus/bob/foobar" for cloud "dummy": cloud "stratus" not valid`) 86 } 87 88 func (s *ModelCredentialSuite) TestValidateCloudCredentialWrongAuthType(c *gc.C) { 89 m, err := s.State.Model() 90 c.Assert(err, jc.ErrorIsNil) 91 tag := names.NewCloudCredentialTag("dummy/bob/foobar") 92 cred := cloud.NewCredential(cloud.AccessKeyAuthType, nil) 93 err = m.ValidateCloudCredential(tag, cred) 94 c.Assert(err, gc.ErrorMatches, `validating credential "dummy/bob/foobar" for cloud "dummy": supported auth-types \["empty"\], "access-key" not supported`) 95 } 96 97 func (s *ModelCredentialSuite) TestValidateCloudCredentialModel(c *gc.C) { 98 m, err := s.State.Model() 99 c.Assert(err, jc.ErrorIsNil) 100 tag := names.NewCloudCredentialTag("dummy/bob/foobar") 101 cred := cloud.NewCredential(cloud.EmptyAuthType, nil) 102 err = m.ValidateCloudCredential(tag, cred) 103 c.Assert(err, jc.ErrorIsNil) 104 } 105 106 func (s *ModelCredentialSuite) TestSetCloudCredential(c *gc.C) { 107 s.assertSetCloudCredential(c, 108 names.NewCloudCredentialTag("dummy/bob/foobar"), 109 cloud.NewCredential(cloud.EmptyAuthType, nil), 110 ) 111 } 112 113 func (s *ModelCredentialSuite) TestSetCloudCredentialNoUpdate(c *gc.C) { 114 tag := names.NewCloudCredentialTag("dummy/bob/foobar") 115 m := s.assertSetCloudCredential(c, 116 tag, 117 cloud.NewCredential(cloud.EmptyAuthType, nil), 118 ) 119 120 set, err := m.SetCloudCredential(tag) 121 c.Assert(err, jc.ErrorIsNil) 122 // This should be false as cloud credential change was an no-op. 123 c.Assert(set, jc.IsFalse) 124 125 // Check credential is still set. 126 credentialTag, credentialSet := m.CloudCredentialTag() 127 c.Assert(credentialTag, gc.DeepEquals, tag) 128 c.Assert(credentialSet, jc.IsTrue) 129 cred, credentialSet, err := m.CloudCredential() 130 c.Assert(err, jc.ErrorIsNil) 131 c.Assert(credentialSet, jc.IsTrue) 132 stateCred, err := s.State.CloudCredential(credentialTag) 133 c.Assert(err, jc.ErrorIsNil) 134 c.Assert(cred, jc.DeepEquals, stateCred) 135 } 136 137 func (s *ModelCredentialSuite) TestSetCloudCredentialInvalidCredentialContent(c *gc.C) { 138 tag := names.NewCloudCredentialTag("dummy/bob/foobar") 139 credential := cloud.NewCredential(cloud.EmptyAuthType, nil) 140 err := s.State.UpdateCloudCredential(tag, credential) 141 c.Assert(err, jc.ErrorIsNil) 142 err = s.State.InvalidateCloudCredential(tag, "test") 143 c.Assert(err, jc.ErrorIsNil) 144 145 m, err := s.State.Model() 146 c.Assert(err, jc.ErrorIsNil) 147 set, err := m.SetCloudCredential(tag) 148 c.Assert(err, gc.ErrorMatches, `credential "dummy/bob/foobar" not valid`) 149 c.Assert(set, jc.IsFalse) 150 151 credentialTag, credentialSet := m.CloudCredentialTag() 152 // Make sure no credential is set. 153 c.Assert(credentialTag, gc.DeepEquals, names.CloudCredentialTag{}) 154 c.Assert(credentialSet, jc.IsFalse) 155 _, credentialSet, err = m.CloudCredential() 156 c.Assert(err, jc.ErrorIsNil) 157 c.Assert(credentialSet, jc.IsFalse) 158 } 159 160 func (s *ModelCredentialSuite) TestSetCloudCredentialInvalidCredentialForModel(c *gc.C) { 161 err := s.State.AddCloud(lowCloud, s.Owner.Name()) 162 c.Assert(err, jc.ErrorIsNil) 163 tag := names.NewCloudCredentialTag("stratus/bob/foobar") 164 credential := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{ 165 "access-key": "someverysecretaccesskey", 166 "secret-key": "someverysercretplainkey", 167 }) 168 err = s.State.UpdateCloudCredential(tag, credential) 169 c.Assert(err, jc.ErrorIsNil) 170 171 m, err := s.State.Model() 172 c.Assert(err, jc.ErrorIsNil) 173 set, err := m.SetCloudCredential(tag) 174 c.Assert(err, gc.ErrorMatches, `cloud "stratus" not valid`) 175 c.Assert(set, jc.IsFalse) 176 177 credentialTag, credentialSet := m.CloudCredentialTag() 178 // Make sure no credential is set. 179 c.Assert(credentialTag, gc.DeepEquals, names.CloudCredentialTag{}) 180 c.Assert(credentialSet, jc.IsFalse) 181 _, credentialSet, err = m.CloudCredential() 182 c.Assert(err, jc.ErrorIsNil) 183 c.Assert(credentialSet, jc.IsFalse) 184 } 185 186 func (s *ModelCredentialSuite) TestWatchModelCredential(c *gc.C) { 187 // Credential to use in this test. 188 tag := names.NewCloudCredentialTag("dummy/bob/foobar") 189 credential := cloud.NewCredential(cloud.EmptyAuthType, nil) 190 err := s.State.UpdateCloudCredential(tag, credential) 191 c.Assert(err, jc.ErrorIsNil) 192 193 // Model with credential watcher for this test. 194 m, err := s.State.Model() 195 c.Assert(err, jc.ErrorIsNil) 196 w := m.WatchModelCredential() 197 defer statetesting.AssertStop(c, w) 198 wc := statetesting.NewNotifyWatcherC(c, w) 199 200 // Initial event. 201 wc.AssertOneChange() 202 203 // Check the watcher reacts to credential reference changes. 204 set, err := m.SetCloudCredential(tag) 205 c.Assert(err, jc.ErrorIsNil) 206 c.Assert(set, jc.IsTrue) 207 wc.AssertOneChange() 208 209 // Check the watcher does not react to other changes on this model. 210 err = m.SetDead() 211 c.Assert(err, jc.ErrorIsNil) 212 wc.AssertNoChange() 213 214 // Check that changes on another model do not affect this watcher. 215 st := s.addModel(c, "abcmodel", s.credentialTag) 216 defer st.Close() 217 anotherM, err := st.Model() 218 c.Assert(err, jc.ErrorIsNil) 219 set, err = anotherM.SetCloudCredential(tag) 220 c.Assert(err, jc.ErrorIsNil) 221 c.Assert(set, jc.IsTrue) 222 wc.AssertNoChange() 223 } 224 225 func (s *ModelCredentialSuite) assertSetCloudCredential(c *gc.C, tag names.CloudCredentialTag, credential cloud.Credential) *state.Model { 226 m, err := s.State.Model() 227 c.Assert(err, jc.ErrorIsNil) 228 credentialTag, credentialSet := m.CloudCredentialTag() 229 // Make sure no credential is set. 230 c.Assert(credentialTag, gc.DeepEquals, names.CloudCredentialTag{}) 231 c.Assert(credentialSet, jc.IsFalse) 232 _, credentialSet, err = m.CloudCredential() 233 c.Assert(err, jc.ErrorIsNil) 234 c.Assert(credentialSet, jc.IsFalse) 235 236 err = s.State.UpdateCloudCredential(tag, credential) 237 c.Assert(err, jc.ErrorIsNil) 238 239 set, err := m.SetCloudCredential(tag) 240 c.Assert(err, jc.ErrorIsNil) 241 c.Assert(set, jc.IsTrue) 242 243 // Check credential is set. 244 credentialTag, credentialSet = m.CloudCredentialTag() 245 c.Assert(credentialTag, gc.DeepEquals, tag) 246 c.Assert(credentialSet, jc.IsTrue) 247 cred, credentialSet, err := m.CloudCredential() 248 c.Assert(err, jc.ErrorIsNil) 249 c.Assert(credentialSet, jc.IsTrue) 250 stateCred, err := s.State.CloudCredential(credentialTag) 251 c.Assert(err, jc.ErrorIsNil) 252 c.Assert(cred, jc.DeepEquals, stateCred) 253 return m 254 } 255 256 func (s *ModelCredentialSuite) createCloudCredential(c *gc.C, credentialName string) names.CloudCredentialTag { 257 // Cloud name is always "dummy" as deep within the testing infrastructure, 258 // we create a testing controller on a cloud "dummy". 259 // Test cloud "dummy" only allows credentials with an empty auth type. 260 tag := names.NewCloudCredentialTag(fmt.Sprintf("%s/%s/%s", "dummy", s.Owner.Id(), credentialName)) 261 err := s.State.UpdateCloudCredential(tag, cloud.NewEmptyCredential()) 262 c.Assert(err, jc.ErrorIsNil) 263 return tag 264 } 265 266 func (s *ModelCredentialSuite) addModel(c *gc.C, modelName string, tag names.CloudCredentialTag) *state.State { 267 uuid, err := utils.NewUUID() 268 c.Assert(err, jc.ErrorIsNil) 269 cfg := testing.CustomModelConfig(c, testing.Attrs{ 270 "name": modelName, 271 "uuid": uuid.String(), 272 }) 273 _, st, err := s.Controller.NewModel(state.ModelArgs{ 274 Type: state.ModelTypeIAAS, 275 CloudName: "dummy", 276 CloudRegion: "dummy-region", 277 Config: cfg, 278 Owner: tag.Owner(), 279 CloudCredential: tag, 280 StorageProviderRegistry: storage.StaticProviderRegistry{}, 281 }) 282 c.Assert(err, jc.ErrorIsNil) 283 return st 284 } 285 286 func (s *ModelCredentialSuite) TestInvalidateModelCredentialTouchesAllCredentialModels(c *gc.C) { 287 // This test checks that all models are affected when one of them invalidates a credential they all use... 288 289 // 1. create a credential 290 cloudName, credentialOwner, credentialTag := assertCredentialCreated(c, s.ConnSuite) 291 292 // 2. create some models to use it 293 modelUUIDs := make([]string, 5) 294 for i := 0; i < 5; i++ { 295 modelUUIDs[i] = assertModelCreated(c, s.ConnSuite, cloudName, credentialTag, credentialOwner.Tag(), fmt.Sprintf("model-for-cloud%v", i)) 296 } 297 298 // 3. invalidate credential 299 oneModelState, helper, err := s.StatePool.GetModel(modelUUIDs[0]) 300 c.Assert(err, jc.ErrorIsNil) 301 defer helper.Release() 302 c.Assert(oneModelState.State().InvalidateModelCredential("testing invalidate for all credential models"), jc.ErrorIsNil) 303 304 // 4. check all models are suspended 305 for _, uuid := range modelUUIDs { 306 assertModelStatus(c, s.StatePool, uuid, status.Suspended) 307 assertModelHistories(c, s.StatePool, uuid, status.Suspended, status.Available) 308 } 309 } 310 311 func (s *ModelCredentialSuite) TestSetCredentialRevertsModelStatus(c *gc.C) { 312 // 1. create a credential 313 cloudName, credentialOwner, credentialTag := assertCredentialCreated(c, s.ConnSuite) 314 315 // 2. create some models to use it 316 validModelStatuses := []status.Status{ 317 status.Available, 318 status.Busy, 319 status.Destroying, 320 status.Error, 321 } 322 desiredNumber := len(validModelStatuses) 323 324 modelUUIDs := make([]string, desiredNumber) 325 for i := 0; i < desiredNumber; i++ { 326 modelUUIDs[i] = assertModelCreated(c, s.ConnSuite, cloudName, credentialTag, credentialOwner.Tag(), fmt.Sprintf("model-for-cloud%v", i)) 327 oneModelState, helper, err := s.StatePool.GetModel(modelUUIDs[i]) 328 c.Assert(err, jc.ErrorIsNil) 329 defer helper.Release() 330 if validModelStatuses[i] != status.Available { 331 // any model would be in 'available' status on setup. 332 err = oneModelState.SetStatus(status.StatusInfo{Status: validModelStatuses[i]}) 333 c.Assert(err, jc.ErrorIsNil) 334 c.Assert(oneModelState.Refresh(), jc.ErrorIsNil) 335 } 336 if i == desiredNumber-1 { 337 // 3. invalidate credential on last model 338 c.Assert(oneModelState.State().InvalidateModelCredential("testing"), jc.ErrorIsNil) 339 } 340 } 341 342 // 4. check model is suspended 343 for i := 0; i < desiredNumber; i++ { 344 assertModelStatus(c, s.StatePool, modelUUIDs[i], status.Suspended) 345 if validModelStatuses[i] == status.Available { 346 assertModelHistories(c, s.StatePool, modelUUIDs[i], status.Suspended, status.Available) 347 } else { 348 assertModelHistories(c, s.StatePool, modelUUIDs[i], status.Suspended, validModelStatuses[i], status.Available) 349 } 350 } 351 352 // 5. create another credential on the same cloud 353 owner := s.Factory.MakeUser(c, &factory.UserParams{ 354 Password: "secret", 355 Name: "uncle", 356 }) 357 anotherCredentialTag := createCredential(c, s.ConnSuite, cloudName, owner.Name(), "barfoo") 358 359 for i := 0; i < desiredNumber; i++ { 360 oneModelState, helper, err := s.StatePool.GetModel(modelUUIDs[i]) 361 c.Assert(err, jc.ErrorIsNil) 362 defer helper.Release() 363 364 isSet, err := oneModelState.SetCloudCredential(anotherCredentialTag) 365 c.Assert(err, jc.ErrorIsNil) 366 c.Assert(isSet, jc.IsTrue) 367 368 // 5. Check model status is reverted 369 if validModelStatuses[i] == status.Available { 370 assertModelStatus(c, s.StatePool, modelUUIDs[i], status.Available) 371 assertModelHistories(c, s.StatePool, modelUUIDs[i], status.Available, status.Suspended, status.Available) 372 } else { 373 assertModelStatus(c, s.StatePool, modelUUIDs[i], validModelStatuses[i]) 374 assertModelHistories(c, s.StatePool, modelUUIDs[i], validModelStatuses[i], status.Suspended, validModelStatuses[i], status.Available) 375 } 376 } 377 } 378 379 func assertModelHistories(c *gc.C, pool *state.StatePool, testModelUUID string, expected ...status.Status) []status.StatusInfo { 380 aModel, helper, err := pool.GetModel(testModelUUID) 381 c.Assert(err, jc.ErrorIsNil) 382 defer helper.Release() 383 statusHistories, err := aModel.StatusHistory(status.StatusHistoryFilter{Size: 100}) 384 c.Assert(err, jc.ErrorIsNil) 385 c.Assert(statusHistories, gc.HasLen, len(expected)) 386 for i, one := range expected { 387 c.Assert(statusHistories[i].Status, gc.Equals, one) 388 } 389 return statusHistories 390 }