github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/secretsdrainworker/worker_test.go (about)

     1  // Copyright 2023 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package secretsdrainworker_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/worker/v3/workertest"
    14  	"go.uber.org/mock/gomock"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/api/common/secretsdrain"
    18  	coresecrets "github.com/juju/juju/core/secrets"
    19  	"github.com/juju/juju/core/watcher/watchertest"
    20  	jujusecrets "github.com/juju/juju/secrets"
    21  	"github.com/juju/juju/secrets/provider"
    22  	coretesting "github.com/juju/juju/testing"
    23  	"github.com/juju/juju/worker/secretsdrainworker"
    24  	"github.com/juju/juju/worker/secretsdrainworker/mocks"
    25  )
    26  
    27  type workerSuite struct {
    28  	testing.IsolationSuite
    29  	logger loggo.Logger
    30  
    31  	facade        *mocks.MockSecretsDrainFacade
    32  	backendClient *mocks.MockBackendsClient
    33  
    34  	done                   chan struct{}
    35  	notifyBackendChangedCh chan struct{}
    36  }
    37  
    38  var _ = gc.Suite(&workerSuite{})
    39  
    40  func (s *workerSuite) getWorkerNewer(c *gc.C, calls ...*gomock.Call) (func(string), *gomock.Controller) {
    41  	ctrl := gomock.NewController(c)
    42  	s.logger = loggo.GetLogger("test")
    43  	s.facade = mocks.NewMockSecretsDrainFacade(ctrl)
    44  	s.backendClient = mocks.NewMockBackendsClient(ctrl)
    45  
    46  	s.notifyBackendChangedCh = make(chan struct{}, 1)
    47  	s.done = make(chan struct{})
    48  	s.facade.EXPECT().WatchSecretBackendChanged().Return(watchertest.NewMockNotifyWatcher(s.notifyBackendChangedCh), nil)
    49  
    50  	start := func(expectedErr string) {
    51  		w, err := secretsdrainworker.NewWorker(secretsdrainworker.Config{
    52  			Logger:             s.logger,
    53  			SecretsDrainFacade: s.facade,
    54  			SecretsBackendGetter: func() (jujusecrets.BackendsClient, error) {
    55  				return s.backendClient, nil
    56  			},
    57  		})
    58  		c.Assert(err, jc.ErrorIsNil)
    59  		c.Assert(w, gc.NotNil)
    60  		s.AddCleanup(func(c *gc.C) {
    61  			if expectedErr == "" {
    62  				workertest.CleanKill(c, w)
    63  			} else {
    64  				err := workertest.CheckKilled(c, w)
    65  				c.Assert(err, gc.ErrorMatches, expectedErr)
    66  			}
    67  		})
    68  		s.waitDone(c)
    69  	}
    70  	return start, ctrl
    71  }
    72  
    73  func (s *workerSuite) waitDone(c *gc.C) {
    74  	select {
    75  	case <-s.done:
    76  	case <-time.After(coretesting.ShortWait):
    77  		c.Errorf("timed out waiting for worker")
    78  	}
    79  }
    80  
    81  func (s *workerSuite) TestNothingToDrain(c *gc.C) {
    82  	start, ctrl := s.getWorkerNewer(c)
    83  	defer ctrl.Finish()
    84  
    85  	s.notifyBackendChangedCh <- struct{}{}
    86  	gomock.InOrder(
    87  		s.facade.EXPECT().GetSecretsToDrain().DoAndReturn(func() ([]coresecrets.SecretMetadataForDrain, error) {
    88  			close(s.done)
    89  			return []coresecrets.SecretMetadataForDrain{}, nil
    90  		}),
    91  	)
    92  	start("")
    93  }
    94  
    95  func (s *workerSuite) TestDrainNoOPS(c *gc.C) {
    96  	start, ctrl := s.getWorkerNewer(c)
    97  	defer ctrl.Finish()
    98  
    99  	uri := coresecrets.NewURI()
   100  	s.notifyBackendChangedCh <- struct{}{}
   101  	gomock.InOrder(
   102  		s.facade.EXPECT().GetSecretsToDrain().Return([]coresecrets.SecretMetadataForDrain{
   103  			{
   104  				Metadata: coresecrets.SecretMetadata{URI: uri},
   105  				Revisions: []coresecrets.SecretRevisionMetadata{
   106  					{
   107  						Revision: 1,
   108  						ValueRef: &coresecrets.ValueRef{BackendID: "backend-1", RevisionID: "revision-1"},
   109  					},
   110  				},
   111  			},
   112  		}, nil),
   113  		s.backendClient.EXPECT().GetBackend(nil, true).DoAndReturn(func(*string, bool) (*provider.SecretsBackend, string, error) {
   114  			close(s.done)
   115  			return nil, "backend-1", nil
   116  		}),
   117  	)
   118  	start("")
   119  }
   120  
   121  func (s *workerSuite) TestDrainBetweenExternalBackends(c *gc.C) {
   122  	start, ctrl := s.getWorkerNewer(c)
   123  	defer ctrl.Finish()
   124  
   125  	uri := coresecrets.NewURI()
   126  	s.notifyBackendChangedCh <- struct{}{}
   127  	secretValue := coresecrets.NewSecretValue(map[string]string{"foo": "bar"})
   128  
   129  	oldBackend := mocks.NewMockSecretsBackend(ctrl)
   130  	activeBackend := mocks.NewMockSecretsBackend(ctrl)
   131  
   132  	gomock.InOrder(
   133  		s.facade.EXPECT().GetSecretsToDrain().Return([]coresecrets.SecretMetadataForDrain{
   134  			{
   135  				Metadata: coresecrets.SecretMetadata{URI: uri},
   136  				Revisions: []coresecrets.SecretRevisionMetadata{
   137  					{
   138  						Revision: 1,
   139  						ValueRef: &coresecrets.ValueRef{BackendID: "backend-1", RevisionID: "revision-1"},
   140  					},
   141  				},
   142  			},
   143  		}, nil),
   144  		s.backendClient.EXPECT().GetBackend(nil, true).Return(activeBackend, "backend-2", nil),
   145  		s.backendClient.EXPECT().GetRevisionContent(uri, 1).Return(secretValue, nil),
   146  		activeBackend.EXPECT().SaveContent(gomock.Any(), uri, 1, secretValue).Return("revision-1", nil),
   147  		s.backendClient.EXPECT().GetBackend(ptr("backend-1"), true).Return(oldBackend, "", nil),
   148  		s.facade.EXPECT().ChangeSecretBackend(
   149  			[]secretsdrain.ChangeSecretBackendArg{
   150  				{
   151  					URI:      uri,
   152  					Revision: 1,
   153  					ValueRef: &coresecrets.ValueRef{
   154  						BackendID:  "backend-2",
   155  						RevisionID: "revision-1",
   156  					},
   157  				},
   158  			},
   159  		).Return(secretsdrain.ChangeSecretBackendResult{Results: []error{nil}}, nil),
   160  		oldBackend.EXPECT().DeleteContent(gomock.Any(), "revision-1").DoAndReturn(func(_ any, _ string) error {
   161  			close(s.done)
   162  			return nil
   163  		}),
   164  	)
   165  	start("")
   166  }
   167  
   168  func (s *workerSuite) TestDrainFromInternalToExternal(c *gc.C) {
   169  	start, ctrl := s.getWorkerNewer(c)
   170  	defer ctrl.Finish()
   171  
   172  	uri := coresecrets.NewURI()
   173  	s.notifyBackendChangedCh <- struct{}{}
   174  	secretValue := coresecrets.NewSecretValue(map[string]string{"foo": "bar"})
   175  
   176  	activeBackend := mocks.NewMockSecretsBackend(ctrl)
   177  
   178  	gomock.InOrder(
   179  		s.facade.EXPECT().GetSecretsToDrain().Return([]coresecrets.SecretMetadataForDrain{
   180  			{
   181  				Metadata:  coresecrets.SecretMetadata{URI: uri},
   182  				Revisions: []coresecrets.SecretRevisionMetadata{{Revision: 1}},
   183  			},
   184  		}, nil),
   185  		s.backendClient.EXPECT().GetBackend(nil, true).Return(activeBackend, "backend-2", nil),
   186  		s.backendClient.EXPECT().GetRevisionContent(uri, 1).Return(secretValue, nil),
   187  		activeBackend.EXPECT().SaveContent(gomock.Any(), uri, 1, secretValue).Return("revision-1", nil),
   188  		s.facade.EXPECT().ChangeSecretBackend(
   189  			[]secretsdrain.ChangeSecretBackendArg{
   190  				{
   191  					URI:      uri,
   192  					Revision: 1,
   193  					ValueRef: &coresecrets.ValueRef{
   194  						BackendID:  "backend-2",
   195  						RevisionID: "revision-1",
   196  					},
   197  				},
   198  			},
   199  		).DoAndReturn(func([]secretsdrain.ChangeSecretBackendArg) (secretsdrain.ChangeSecretBackendResult, error) {
   200  			close(s.done)
   201  			return secretsdrain.ChangeSecretBackendResult{Results: []error{nil}}, nil
   202  		}),
   203  	)
   204  	start("")
   205  }
   206  
   207  func (s *workerSuite) TestDrainFromExternalToInternal(c *gc.C) {
   208  	start, ctrl := s.getWorkerNewer(c)
   209  	defer ctrl.Finish()
   210  
   211  	uri := coresecrets.NewURI()
   212  	s.notifyBackendChangedCh <- struct{}{}
   213  	secretValue := coresecrets.NewSecretValue(map[string]string{"foo": "bar"})
   214  
   215  	oldBackend := mocks.NewMockSecretsBackend(ctrl)
   216  	activeBackend := mocks.NewMockSecretsBackend(ctrl)
   217  	gomock.InOrder(
   218  		s.facade.EXPECT().GetSecretsToDrain().Return([]coresecrets.SecretMetadataForDrain{
   219  			{
   220  				Metadata: coresecrets.SecretMetadata{URI: uri},
   221  				Revisions: []coresecrets.SecretRevisionMetadata{
   222  					{
   223  						Revision: 1,
   224  						ValueRef: &coresecrets.ValueRef{BackendID: "backend-1", RevisionID: "revision-1"},
   225  					},
   226  				},
   227  			},
   228  		}, nil),
   229  		s.backendClient.EXPECT().GetBackend(nil, true).Return(activeBackend, "backend-2", nil),
   230  		s.backendClient.EXPECT().GetRevisionContent(uri, 1).Return(secretValue, nil),
   231  		activeBackend.EXPECT().SaveContent(gomock.Any(), uri, 1, secretValue).Return("", errors.NotSupportedf("")),
   232  		s.backendClient.EXPECT().GetBackend(ptr("backend-1"), true).Return(oldBackend, "", nil),
   233  		s.facade.EXPECT().ChangeSecretBackend(
   234  			[]secretsdrain.ChangeSecretBackendArg{
   235  				{
   236  					URI:      uri,
   237  					Revision: 1,
   238  					Data:     secretValue.EncodedValues(),
   239  				},
   240  			},
   241  		).Return(secretsdrain.ChangeSecretBackendResult{Results: []error{nil}}, nil),
   242  		oldBackend.EXPECT().DeleteContent(gomock.Any(), "revision-1").DoAndReturn(func(_ any, _ string) error {
   243  			close(s.done)
   244  			return nil
   245  		}),
   246  	)
   247  	start("")
   248  }
   249  
   250  func (s *workerSuite) TestDrainPartiallyFailed(c *gc.C) {
   251  	// If the drain fails for one revision, it should continue to drain the rest.
   252  	// But the agent should be restarted to retry.
   253  	start, ctrl := s.getWorkerNewer(c)
   254  	defer ctrl.Finish()
   255  
   256  	uri := coresecrets.NewURI()
   257  	s.notifyBackendChangedCh <- struct{}{}
   258  	secretValue := coresecrets.NewSecretValue(map[string]string{"foo": "bar"})
   259  
   260  	oldBackend := mocks.NewMockSecretsBackend(ctrl)
   261  	activeBackend := mocks.NewMockSecretsBackend(ctrl)
   262  	gomock.InOrder(
   263  		s.facade.EXPECT().GetSecretsToDrain().Return([]coresecrets.SecretMetadataForDrain{
   264  			{
   265  				Metadata: coresecrets.SecretMetadata{URI: uri},
   266  				Revisions: []coresecrets.SecretRevisionMetadata{
   267  					{
   268  						Revision: 1,
   269  						ValueRef: &coresecrets.ValueRef{BackendID: "backend-1", RevisionID: "revision-1"},
   270  					},
   271  					{
   272  						Revision: 2,
   273  						ValueRef: &coresecrets.ValueRef{BackendID: "backend-2", RevisionID: "revision-2"},
   274  					},
   275  				},
   276  			},
   277  		}, nil),
   278  
   279  		// revision 1
   280  		s.backendClient.EXPECT().GetBackend(nil, true).Return(activeBackend, "backend-3", nil),
   281  		s.backendClient.EXPECT().GetRevisionContent(uri, 1).Return(secretValue, nil),
   282  		activeBackend.EXPECT().SaveContent(gomock.Any(), uri, 1, secretValue).Return("", errors.NotSupportedf("")),
   283  		s.backendClient.EXPECT().GetBackend(ptr("backend-1"), true).Return(oldBackend, "", nil),
   284  
   285  		// revision 2
   286  		s.backendClient.EXPECT().GetBackend(nil, true).Return(activeBackend, "backend-3", nil),
   287  		s.backendClient.EXPECT().GetRevisionContent(uri, 2).Return(secretValue, nil),
   288  		activeBackend.EXPECT().SaveContent(gomock.Any(), uri, 2, secretValue).Return("", errors.NotSupportedf("")),
   289  		s.backendClient.EXPECT().GetBackend(ptr("backend-2"), true).Return(oldBackend, "", nil),
   290  
   291  		s.facade.EXPECT().ChangeSecretBackend(
   292  			[]secretsdrain.ChangeSecretBackendArg{
   293  				{
   294  					URI:      uri,
   295  					Revision: 1,
   296  					Data:     secretValue.EncodedValues(),
   297  				},
   298  				{
   299  					URI:      uri,
   300  					Revision: 2,
   301  					Data:     secretValue.EncodedValues(),
   302  				},
   303  			},
   304  		).Return(secretsdrain.ChangeSecretBackendResult{Results: []error{
   305  			nil,
   306  			errors.New("failed"), // 2nd one failed.
   307  		}}, nil),
   308  		// We only delete for the 1st revision.
   309  		oldBackend.EXPECT().DeleteContent(gomock.Any(), "revision-1").DoAndReturn(func(_ any, _ string) error {
   310  			close(s.done)
   311  			return nil
   312  		}),
   313  	)
   314  	start(`failed to drain secret revisions for "secret:.*" to the active backend`)
   315  }
   316  
   317  func ptr[T any](v T) *T {
   318  	return &v
   319  }