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 }