github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/cloudcredentials_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/names/v5"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"github.com/juju/juju/cloud"
    17  	"github.com/juju/juju/core/status"
    18  	"github.com/juju/juju/state"
    19  	statetesting "github.com/juju/juju/state/testing"
    20  	"github.com/juju/juju/testing/factory"
    21  )
    22  
    23  type CloudCredentialsSuite struct {
    24  	ConnSuite
    25  }
    26  
    27  var _ = gc.Suite(&CloudCredentialsSuite{})
    28  
    29  func (s *CloudCredentialsSuite) TestUpdateCloudCredentialNew(c *gc.C) {
    30  	err := s.State.AddCloud(cloud.Cloud{
    31  		Name:      "stratus",
    32  		Type:      "low",
    33  		AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType},
    34  	}, s.Owner.Name())
    35  	c.Assert(err, jc.ErrorIsNil)
    36  
    37  	cred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
    38  		"foo": "foo val",
    39  		"bar": "bar val",
    40  	})
    41  	tag := names.NewCloudCredentialTag("stratus/bob/foobar")
    42  	err = s.State.UpdateCloudCredential(tag, cred)
    43  	c.Assert(err, jc.ErrorIsNil)
    44  
    45  	out, err := s.State.CloudCredential(tag)
    46  	c.Assert(err, jc.ErrorIsNil)
    47  
    48  	expected := statetesting.CloudCredential(cloud.AccessKeyAuthType,
    49  		map[string]string{"bar": "bar val", "foo": "foo val"},
    50  	)
    51  	expected.DocID = "stratus#bob#foobar"
    52  	expected.Owner = "bob"
    53  	expected.Cloud = "stratus"
    54  	expected.Name = "foobar"
    55  	c.Assert(out, jc.DeepEquals, expected)
    56  }
    57  
    58  func (s *CloudCredentialsSuite) TestCreateInvalidCredential(c *gc.C) {
    59  	err := s.State.AddCloud(cloud.Cloud{
    60  		Name:      "stratus",
    61  		Type:      "low",
    62  		AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType},
    63  	}, s.Owner.Name())
    64  	c.Assert(err, jc.ErrorIsNil)
    65  
    66  	cred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
    67  		"foo": "foo val",
    68  		"bar": "bar val",
    69  	})
    70  	// Setting of these properties should have no effect when creating a new credential.
    71  	cred.Invalid = true
    72  	cred.InvalidReason = "because am testing you"
    73  	tag := names.NewCloudCredentialTag("stratus/bob/foobar")
    74  	err = s.State.UpdateCloudCredential(tag, cred)
    75  	c.Assert(err, gc.ErrorMatches, "creating cloud credential: adding invalid credential not supported")
    76  }
    77  
    78  func (s *CloudCredentialsSuite) TestUpdateCloudCredentialsExisting(c *gc.C) {
    79  	err := s.State.AddCloud(cloud.Cloud{
    80  		Name:      "stratus",
    81  		Type:      "low",
    82  		AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType},
    83  	}, s.Owner.Name())
    84  	c.Assert(err, jc.ErrorIsNil)
    85  
    86  	cred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
    87  		"foo": "foo val",
    88  		"bar": "bar val",
    89  	})
    90  	tag := names.NewCloudCredentialTag("stratus/bob/foobar")
    91  	err = s.State.UpdateCloudCredential(tag, cred)
    92  	c.Assert(err, jc.ErrorIsNil)
    93  
    94  	cred = cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
    95  		"user":     "bob's nephew",
    96  		"password": "simple",
    97  	})
    98  	cred.Revoked = true
    99  	err = s.State.UpdateCloudCredential(tag, cred)
   100  	c.Assert(err, jc.ErrorIsNil)
   101  
   102  	out, err := s.State.CloudCredential(tag)
   103  	c.Assert(err, jc.ErrorIsNil)
   104  
   105  	expected := statetesting.CloudCredential(cloud.UserPassAuthType, map[string]string{
   106  		"user":     "bob's nephew",
   107  		"password": "simple",
   108  	})
   109  	expected.DocID = "stratus#bob#foobar"
   110  	expected.Owner = "bob"
   111  	expected.Cloud = "stratus"
   112  	expected.Name = "foobar"
   113  	expected.Revoked = true
   114  
   115  	c.Assert(out, jc.DeepEquals, expected)
   116  }
   117  
   118  func assertCredentialCreated(c *gc.C, testSuite ConnSuite) (string, *state.User, names.CloudCredentialTag) {
   119  	owner := testSuite.Factory.MakeUser(c, &factory.UserParams{
   120  		Password: "secret",
   121  		Name:     "bob",
   122  	})
   123  
   124  	cloudName := "stratus"
   125  	err := testSuite.State.AddCloud(cloud.Cloud{
   126  		Name:      cloudName,
   127  		Type:      "low",
   128  		AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType},
   129  		Regions:   []cloud.Region{{Name: "dummy-region", Endpoint: "endpoint"}},
   130  	}, owner.Name())
   131  	c.Assert(err, jc.ErrorIsNil)
   132  
   133  	tag := createCredential(c, testSuite, cloudName, owner.Name(), "foobar")
   134  	return cloudName, owner, tag
   135  }
   136  
   137  func createCredential(c *gc.C, testSuite ConnSuite, cloudName, userName, credentialName string) names.CloudCredentialTag {
   138  	cred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   139  		"foo": "foo val",
   140  		"bar": "bar val",
   141  	})
   142  	tag := names.NewCloudCredentialTag(fmt.Sprintf("%v/%v/%v", cloudName, userName, credentialName))
   143  	err := testSuite.State.UpdateCloudCredential(tag, cred)
   144  	c.Assert(err, jc.ErrorIsNil)
   145  	return tag
   146  }
   147  
   148  func assertModelCreated(c *gc.C, testSuite ConnSuite, cloudName string, credentialTag names.CloudCredentialTag, owner names.Tag, modelName string) string {
   149  	// Test model needs to be on the test cloud for all validation to pass.
   150  	modelState := testSuite.Factory.MakeModel(c, &factory.ModelParams{
   151  		Name:            modelName,
   152  		CloudCredential: credentialTag,
   153  		Owner:           owner,
   154  		CloudName:       cloudName,
   155  	})
   156  	defer modelState.Close()
   157  	testModel, err := modelState.Model()
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	assertModelStatus(c, testSuite.StatePool, testModel.UUID(), status.Available)
   160  	return testModel.UUID()
   161  }
   162  
   163  func assertModelSuspended(c *gc.C, testSuite ConnSuite) (names.CloudCredentialTag, string) {
   164  	// 1. Create a credential
   165  	cloudName, credentialOwner, credentialTag := assertCredentialCreated(c, testSuite)
   166  
   167  	// 2. Create model on the test cloud with test credential
   168  	modelUUID := assertModelCreated(c, testSuite, cloudName, credentialTag, credentialOwner.Tag(), "model-for-cloud")
   169  
   170  	// 3. update credential to be invalid and check model is suspended
   171  	cred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   172  		"foo": "foo val",
   173  		"bar": "bar val",
   174  	})
   175  	cred.Invalid = true
   176  	cred.InvalidReason = "because it is really really invalid"
   177  	err := testSuite.State.UpdateCloudCredential(credentialTag, cred)
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	assertModelStatus(c, testSuite.StatePool, modelUUID, status.Suspended)
   180  
   181  	return credentialTag, modelUUID
   182  }
   183  
   184  func assertModelStatus(c *gc.C, pool *state.StatePool, testModelUUID string, expectedStatus status.Status) {
   185  	aModel, helper, err := pool.GetModel(testModelUUID)
   186  	c.Assert(err, jc.ErrorIsNil)
   187  	defer helper.Release()
   188  	modelStatus, err := aModel.Status()
   189  	c.Assert(err, jc.ErrorIsNil)
   190  	c.Assert(modelStatus.Status, gc.DeepEquals, expectedStatus)
   191  }
   192  
   193  func (s *CloudCredentialsSuite) TestUpdateCloudCredentialsTouchesCredentialModels(c *gc.C) {
   194  	// This test checks that models are affected when their credential validity is changed...
   195  	// 1. create a credential
   196  	// 2. set a model to use it
   197  	// 3. update credential to be invalid and check model is suspended
   198  	// 4. update credential bar its validity, check no changes in model state
   199  	// 5. mark credential as valid and check that model is unsuspended
   200  
   201  	// 1.2.3.
   202  	tag, testModelUUID := assertModelSuspended(c, s.ConnSuite)
   203  
   204  	// 4.
   205  	storedCred, err := s.State.CloudCredential(tag)
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	c.Assert(storedCred.IsValid(), jc.IsFalse)
   208  
   209  	cred := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   210  		"user":     "bob's nephew",
   211  		"password": "simple",
   212  	})
   213  	cred.Revoked = true
   214  	// all other credential attributes remain unchanged
   215  	cred.Invalid = storedCred.Invalid
   216  	cred.InvalidReason = storedCred.InvalidReason
   217  
   218  	err = s.State.UpdateCloudCredential(tag, cred)
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	assertModelStatus(c, s.StatePool, testModelUUID, status.Suspended)
   221  
   222  	// 5.
   223  	cred.Invalid = !storedCred.Invalid
   224  	cred.InvalidReason = ""
   225  	err = s.State.UpdateCloudCredential(tag, cred)
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	assertModelStatus(c, s.StatePool, testModelUUID, status.Available)
   228  }
   229  
   230  func (s *CloudCredentialsSuite) TestRemoveModelsCredential(c *gc.C) {
   231  	cloudName, credentialOwner, credentialTag := assertCredentialCreated(c, s.ConnSuite)
   232  	modelUUID := assertModelCreated(c, s.ConnSuite, cloudName, credentialTag, credentialOwner.Tag(), "model-for-cloud")
   233  
   234  	err := s.State.RemoveModelsCredential(credentialTag)
   235  	c.Assert(err, jc.ErrorIsNil)
   236  
   237  	aModel, helper, err := s.StatePool.GetModel(modelUUID)
   238  	c.Assert(err, jc.ErrorIsNil)
   239  	defer helper.Release()
   240  	_, isSet := aModel.CloudCredentialTag()
   241  	c.Assert(isSet, jc.IsFalse)
   242  	_, isSet, err = aModel.CloudCredential()
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	c.Assert(isSet, jc.IsFalse)
   245  }
   246  
   247  func (s *CloudCredentialsSuite) TestRemoveModelsCredentialConcurrentModelDelete(c *gc.C) {
   248  	logger := loggo.GetLogger("juju.state")
   249  	logger.SetLogLevel(loggo.TRACE)
   250  	cloudName, credentialOwner, credentialTag := assertCredentialCreated(c, s.ConnSuite)
   251  	modelUUID := assertModelCreated(c, s.ConnSuite, cloudName, credentialTag, credentialOwner.Tag(), "model-for-cloud")
   252  
   253  	deleteModel := func() {
   254  		aModel, helper, err := s.StatePool.GetModel(modelUUID)
   255  		c.Assert(err, jc.ErrorIsNil)
   256  		defer helper.Release()
   257  		err = aModel.SetDead()
   258  		c.Assert(err, jc.ErrorIsNil)
   259  		c.Assert(aModel.Refresh(), jc.ErrorIsNil)
   260  		c.Assert(aModel.Life(), gc.Equals, state.Dead)
   261  	}
   262  	defer state.SetBeforeHooks(c, s.State, deleteModel).Check()
   263  
   264  	err := s.State.RemoveModelsCredential(credentialTag)
   265  	c.Assert(err, jc.ErrorIsNil)
   266  
   267  	aModel, helper, err := s.StatePool.GetModel(modelUUID)
   268  	c.Assert(err, jc.ErrorIsNil)
   269  	defer helper.Release()
   270  	_, isSet := aModel.CloudCredentialTag()
   271  	// Since the model was marked 'dead' in the middle of 1st transaction attempt,
   272  	// and 2nd attempt would not have picked it up, the model credential would not actually be cleared.
   273  	c.Assert(isSet, jc.IsTrue)
   274  	_, isSet, err = aModel.CloudCredential()
   275  	c.Assert(err, jc.ErrorIsNil)
   276  	c.Assert(isSet, jc.IsTrue)
   277  	c.Assert(c.GetTestLog(), jc.Contains, "creating operations to remove models credential, attempt 1")
   278  }
   279  
   280  func (s *CloudCredentialsSuite) TestRemoveModelsCredentialNotUsed(c *gc.C) {
   281  	_, _, credentialTag := assertCredentialCreated(c, s.ConnSuite)
   282  	err := s.State.RemoveModelsCredential(credentialTag)
   283  	c.Assert(err, jc.ErrorIsNil)
   284  }
   285  
   286  func (s *CloudCredentialsSuite) assertCredentialInvalidated(c *gc.C, tag names.CloudCredentialTag) {
   287  	err := s.State.AddCloud(cloud.Cloud{
   288  		Name:      "stratus",
   289  		Type:      "low",
   290  		AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType},
   291  	}, s.Owner.Name())
   292  	c.Assert(err, jc.ErrorIsNil)
   293  
   294  	cred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   295  		"foo": "foo val",
   296  		"bar": "bar val",
   297  	})
   298  	err = s.State.UpdateCloudCredential(tag, cred)
   299  	c.Assert(err, jc.ErrorIsNil)
   300  
   301  	cred = cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   302  		"user":     "bob's nephew",
   303  		"password": "simple",
   304  	})
   305  	cred.Invalid = true
   306  	cred.InvalidReason = "because it is really really invalid"
   307  	err = s.State.UpdateCloudCredential(tag, cred)
   308  	c.Assert(err, jc.ErrorIsNil)
   309  
   310  	out, err := s.State.CloudCredential(tag)
   311  	c.Assert(err, jc.ErrorIsNil)
   312  
   313  	expected := statetesting.CloudCredential(cloud.UserPassAuthType, map[string]string{
   314  		"user":     "bob's nephew",
   315  		"password": "simple",
   316  	})
   317  	expected.DocID = strings.Replace(tag.Id(), "/", "#", -1)
   318  	expected.Owner = tag.Owner().Id()
   319  	expected.Cloud = tag.Cloud().Id()
   320  	expected.Name = tag.Name()
   321  	expected.Invalid = true
   322  	expected.InvalidReason = "because it is really really invalid"
   323  
   324  	c.Assert(out, jc.DeepEquals, expected)
   325  }
   326  
   327  func (s *CloudCredentialsSuite) TestInvalidateCredential(c *gc.C) {
   328  	s.assertCredentialInvalidated(c, names.NewCloudCredentialTag("stratus/bob/foobar"))
   329  }
   330  
   331  func (s *CloudCredentialsSuite) assertCredentialMarkedValid(c *gc.C, tag names.CloudCredentialTag, credential cloud.Credential) {
   332  	err := s.State.UpdateCloudCredential(tag, credential)
   333  	c.Assert(err, jc.ErrorIsNil)
   334  
   335  	out, err := s.State.CloudCredential(tag)
   336  	c.Assert(err, jc.ErrorIsNil)
   337  	c.Assert(out.IsValid(), jc.IsTrue)
   338  }
   339  
   340  func (s *CloudCredentialsSuite) TestMarkInvalidCredentialAsValidExplicitly(c *gc.C) {
   341  	tag := names.NewCloudCredentialTag("stratus/bob/foobar")
   342  	// This call will ensure that there is an invalid credential to test with.
   343  	s.assertCredentialInvalidated(c, tag)
   344  
   345  	cred := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   346  		"user":     "bob's nephew",
   347  		"password": "simple",
   348  	})
   349  	cred.Invalid = false
   350  	s.assertCredentialMarkedValid(c, tag, cred)
   351  }
   352  
   353  func (s *CloudCredentialsSuite) TestMarkInvalidCredentialAsValidImplicitly(c *gc.C) {
   354  	tag := names.NewCloudCredentialTag("stratus/bob/foobar")
   355  	// This call will ensure that there is an invalid credential to test with.
   356  	s.assertCredentialInvalidated(c, tag)
   357  
   358  	cred := cloud.NewCredential(cloud.UserPassAuthType, map[string]string{
   359  		"user":     "bob's nephew",
   360  		"password": "simple",
   361  	})
   362  	s.assertCredentialMarkedValid(c, tag, cred)
   363  }
   364  
   365  func (s *CloudCredentialsSuite) TestUpdateCloudCredentialInvalidAuthType(c *gc.C) {
   366  	err := s.State.AddCloud(cloud.Cloud{
   367  		Name:      "stratus",
   368  		Type:      "low",
   369  		AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType},
   370  	}, s.Owner.Name())
   371  	c.Assert(err, jc.ErrorIsNil)
   372  	tag := names.NewCloudCredentialTag("stratus/bob/foobar")
   373  	cred := cloud.NewCredential(cloud.UserPassAuthType, nil)
   374  	err = s.State.UpdateCloudCredential(tag, cred)
   375  	c.Assert(err, gc.ErrorMatches, `updating cloud credentials: validating credential "stratus/bob/foobar" for cloud "stratus": supported auth-types \["access-key"\], "userpass" not supported`)
   376  }
   377  
   378  func (s *CloudCredentialsSuite) TestCloudCredentialsEmpty(c *gc.C) {
   379  	creds, err := s.State.CloudCredentials(names.NewUserTag("bob"), "dummy")
   380  	c.Assert(err, jc.ErrorIsNil)
   381  	c.Assert(creds, gc.HasLen, 0)
   382  }
   383  
   384  func (s *CloudCredentialsSuite) TestCloudCredentials(c *gc.C) {
   385  	err := s.State.AddCloud(cloud.Cloud{
   386  		Name:      "stratus",
   387  		Type:      "low",
   388  		AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType},
   389  	}, s.Owner.Name())
   390  	c.Assert(err, jc.ErrorIsNil)
   391  	otherUser := s.Factory.MakeUser(c, nil).UserTag()
   392  
   393  	tag1 := names.NewCloudCredentialTag("stratus/bob/bobcred1")
   394  	cred1 := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   395  		"foo": "foo val",
   396  		"bar": "bar val",
   397  	})
   398  	err = s.State.UpdateCloudCredential(tag1, cred1)
   399  	c.Assert(err, jc.ErrorIsNil)
   400  
   401  	tag2 := names.NewCloudCredentialTag("stratus/" + otherUser.Id() + "/foobar")
   402  	tag3 := names.NewCloudCredentialTag("stratus/bob/bobcred2")
   403  	cred2 := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   404  		"baz": "baz val",
   405  		"qux": "qux val",
   406  	})
   407  	err = s.State.UpdateCloudCredential(tag2, cred2)
   408  	c.Assert(err, jc.ErrorIsNil)
   409  	err = s.State.UpdateCloudCredential(tag3, cred2)
   410  	c.Assert(err, jc.ErrorIsNil)
   411  
   412  	cred1.Label = "bobcred1"
   413  	cred2.Label = "bobcred2"
   414  
   415  	expected1 := statetesting.CloudCredential(cloud.AccessKeyAuthType, map[string]string{
   416  		"foo": "foo val",
   417  		"bar": "bar val",
   418  	})
   419  	expected1.DocID = "stratus#bob#bobcred1"
   420  	expected1.Owner = "bob"
   421  	expected1.Cloud = "stratus"
   422  	expected1.Name = "bobcred1"
   423  
   424  	expected2 := statetesting.CloudCredential(cloud.AccessKeyAuthType, map[string]string{
   425  		"baz": "baz val",
   426  		"qux": "qux val",
   427  	})
   428  	expected2.DocID = "stratus#bob#bobcred2"
   429  	expected2.Owner = "bob"
   430  	expected2.Cloud = "stratus"
   431  	expected2.Name = "bobcred2"
   432  
   433  	for _, userName := range []string{"bob", "bob"} {
   434  		creds, err := s.State.CloudCredentials(names.NewUserTag(userName), "stratus")
   435  		c.Assert(err, jc.ErrorIsNil)
   436  		c.Assert(creds, jc.DeepEquals, map[string]state.Credential{
   437  			tag1.Id(): expected1,
   438  			tag3.Id(): expected2,
   439  		})
   440  	}
   441  }
   442  
   443  func (s *CloudCredentialsSuite) TestRemoveCredentials(c *gc.C) {
   444  	// Create it.
   445  	err := s.State.AddCloud(cloud.Cloud{
   446  		Name:      "stratus",
   447  		Type:      "low",
   448  		AuthTypes: cloud.AuthTypes{cloud.AccessKeyAuthType, cloud.UserPassAuthType},
   449  	}, s.Owner.Name())
   450  	c.Assert(err, jc.ErrorIsNil)
   451  
   452  	tag := names.NewCloudCredentialTag("stratus/bob/bobcred1")
   453  	cred := cloud.NewCredential(cloud.AccessKeyAuthType, map[string]string{
   454  		"foo": "foo val",
   455  		"bar": "bar val",
   456  	})
   457  	err = s.State.UpdateCloudCredential(tag, cred)
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	_, err = s.State.CloudCredential(tag)
   460  	c.Assert(err, jc.ErrorIsNil)
   461  
   462  	// Remove it.
   463  	err = s.State.RemoveCloudCredential(tag)
   464  	c.Assert(err, jc.ErrorIsNil)
   465  
   466  	// Check it.
   467  	_, err = s.State.CloudCredential(tag)
   468  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   469  }
   470  
   471  func (s *CloudCredentialsSuite) createCredentialWatcher(c *gc.C, st *state.State, cred names.CloudCredentialTag) (
   472  	state.NotifyWatcher, statetesting.NotifyWatcherC,
   473  ) {
   474  	w := st.WatchCredential(cred)
   475  	s.AddCleanup(func(c *gc.C) { statetesting.AssertStop(c, w) })
   476  	return w, statetesting.NewNotifyWatcherC(c, w)
   477  }
   478  
   479  func (s *CloudCredentialsSuite) TestWatchCredential(c *gc.C) {
   480  	cred := names.NewCloudCredentialTag("dummy/fred/default")
   481  	w, wc := s.createCredentialWatcher(c, s.State, cred)
   482  	wc.AssertOneChange() // Initial event.
   483  
   484  	// Create
   485  	dummyCred := cloud.NewCredential(cloud.EmptyAuthType, nil)
   486  	err := s.State.UpdateCloudCredential(cred, dummyCred)
   487  	c.Assert(err, jc.ErrorIsNil)
   488  	wc.AssertOneChange()
   489  
   490  	// Revoke
   491  	dummyCred.Revoked = true
   492  	err = s.State.UpdateCloudCredential(cred, dummyCred)
   493  	c.Assert(err, jc.ErrorIsNil)
   494  	wc.AssertOneChange()
   495  
   496  	// Remove.
   497  	err = s.State.RemoveCloudCredential(cred)
   498  	c.Assert(err, jc.ErrorIsNil)
   499  	wc.AssertOneChange()
   500  
   501  	statetesting.AssertStop(c, w)
   502  	wc.AssertClosed()
   503  }
   504  
   505  func (s *CloudCredentialsSuite) TestWatchCredentialIgnoresOther(c *gc.C) {
   506  	cred := names.NewCloudCredentialTag("dummy/fred/default")
   507  	w, wc := s.createCredentialWatcher(c, s.State, cred)
   508  	wc.AssertOneChange() // Initial event.
   509  
   510  	anotherCred := names.NewCloudCredentialTag("dummy/mary/default")
   511  	dummyCred := cloud.NewCredential(cloud.EmptyAuthType, nil)
   512  	err := s.State.UpdateCloudCredential(anotherCred, dummyCred)
   513  	c.Assert(err, jc.ErrorIsNil)
   514  	wc.AssertNoChange()
   515  
   516  	statetesting.AssertStop(c, w)
   517  	wc.AssertClosed()
   518  }
   519  
   520  func (s *CloudCredentialsSuite) createCloudCredential(c *gc.C, cloudName, userName, credentialName string) (names.CloudCredentialTag, state.Credential) {
   521  	authType := cloud.AccessKeyAuthType
   522  	attributes := map[string]string{
   523  		"foo": "foo val",
   524  		"bar": "bar val",
   525  	}
   526  
   527  	err := s.State.AddCloud(cloud.Cloud{
   528  		Name:      cloudName,
   529  		Type:      "low",
   530  		AuthTypes: cloud.AuthTypes{authType, cloud.UserPassAuthType},
   531  	}, s.Owner.Name())
   532  	c.Assert(err, jc.ErrorIsNil)
   533  
   534  	cred := cloud.NewCredential(authType, attributes)
   535  
   536  	// Cloud credential tag to use when looking up this credential.
   537  	tag := names.NewCloudCredentialTag(fmt.Sprintf("%s/%s/%s", cloudName, userName, credentialName))
   538  	err = s.State.UpdateCloudCredential(tag, cred)
   539  	c.Assert(err, jc.ErrorIsNil)
   540  
   541  	// Credential data as stored in state.
   542  	expected := state.Credential{}
   543  	expected.DocID = fmt.Sprintf("%s#%s#%s", cloudName, userName, credentialName)
   544  	expected.Owner = userName
   545  	expected.Cloud = cloudName
   546  	expected.Name = credentialName
   547  	expected.AuthType = string(authType)
   548  	expected.Attributes = attributes
   549  
   550  	return tag, expected
   551  }
   552  
   553  func (s *CloudCredentialsSuite) TestAllCloudCredentialsNotFound(c *gc.C) {
   554  	out, err := s.State.AllCloudCredentials(names.NewUserTag("bob"))
   555  	c.Assert(err, gc.ErrorMatches, "cloud credentials for \"bob\" not found")
   556  	c.Assert(out, gc.IsNil)
   557  }
   558  
   559  func (s *CloudCredentialsSuite) TestAllCloudCredentials(c *gc.C) {
   560  	_, one := s.createCloudCredential(c, "cirrus", "bob", "foobar")
   561  	_, two := s.createCloudCredential(c, "stratus", "bob", "foobar")
   562  
   563  	// Added to make sure it is not returned.
   564  	s.createCloudCredential(c, "cumulus", "mary", "foobar")
   565  
   566  	out, err := s.State.AllCloudCredentials(names.NewUserTag("bob"))
   567  	c.Assert(err, jc.ErrorIsNil)
   568  	c.Assert(out, jc.DeepEquals, []state.Credential{one, two})
   569  }
   570  
   571  func (s *CloudCredentialsSuite) TestInvalidateCloudCredential(c *gc.C) {
   572  	oneTag, one := s.createCloudCredential(c, "cirrus", "bob", "foobar")
   573  	c.Assert(one.IsValid(), jc.IsTrue)
   574  
   575  	reason := "testing, testing 1,2,3"
   576  	err := s.State.InvalidateCloudCredential(oneTag, reason)
   577  	c.Assert(err, jc.ErrorIsNil)
   578  
   579  	updated, err := s.State.CloudCredential(oneTag)
   580  	c.Assert(err, jc.ErrorIsNil)
   581  	c.Assert(updated.IsValid(), jc.IsFalse)
   582  	c.Assert(updated.InvalidReason, gc.DeepEquals, reason)
   583  }
   584  
   585  func (s *CloudCredentialsSuite) TestInvalidateCloudCredentialNotFound(c *gc.C) {
   586  	tag := names.NewCloudCredentialTag("cloud/user/credential")
   587  	err := s.State.InvalidateCloudCredential(tag, "just does not matter")
   588  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   589  }