go.temporal.io/server@v1.23.0/common/archiver/gcloud/connector/client_test.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package connector_test 26 27 import ( 28 "context" 29 "encoding/json" 30 "errors" 31 "io" 32 "os" 33 "strings" 34 "testing" 35 36 "cloud.google.com/go/storage" 37 "github.com/golang/mock/gomock" 38 "github.com/stretchr/testify/require" 39 "github.com/stretchr/testify/suite" 40 "google.golang.org/api/iterator" 41 42 "go.temporal.io/server/common/archiver" 43 "go.temporal.io/server/common/archiver/gcloud/connector" 44 "go.temporal.io/server/common/config" 45 ) 46 47 func (s *clientSuite) SetupTest() { 48 s.Assertions = require.New(s.T()) 49 s.controller = gomock.NewController(s.T()) 50 file, _ := json.MarshalIndent(&fakeData{data: "example"}, "", " ") 51 52 path := "/tmp/temporal_archival/development" 53 if err := os.MkdirAll(path, os.ModePerm); err != nil { 54 s.FailNowf("Failed to create directory %s: %v", path, err) 55 } 56 s.Require().NoError(os.WriteFile("/tmp/temporal_archival/development/myfile.history", file, 0644)) 57 } 58 59 func (s *clientSuite) TearDownTest() { 60 s.controller.Finish() 61 name := "/tmp/temporal_archival/development/myfile.history" 62 if err := os.Remove(name); err != nil { 63 s.Failf("Failed to remove file %s: %v", name, err) 64 } 65 } 66 67 func TestClientSuite(t *testing.T) { 68 suite.Run(t, new(clientSuite)) 69 } 70 71 type clientSuite struct { 72 *require.Assertions 73 suite.Suite 74 controller *gomock.Controller 75 } 76 77 type fakeData struct { 78 data string 79 } 80 81 func (s *clientSuite) TestUpload() { 82 ctx := context.Background() 83 84 mockStorageClient := connector.NewMockGcloudStorageClient(s.controller) 85 mockBucketHandleClient := connector.NewMockBucketHandleWrapper(s.controller) 86 mockObjectHandler := connector.NewMockObjectHandleWrapper(s.controller) 87 mockWriter := connector.NewMockWriterWrapper(s.controller) 88 89 storageWrapper, _ := connector.NewClientWithParams(mockStorageClient) 90 91 mockStorageClient.EXPECT().Bucket("my-bucket-cad").Return(mockBucketHandleClient) 92 mockBucketHandleClient.EXPECT().Object("temporal_archival/development/myfile.history").Return(mockObjectHandler) 93 mockObjectHandler.EXPECT().NewWriter(ctx).Return(mockWriter) 94 mockWriter.EXPECT().Write(gomock.Any()).Return(2, nil) 95 mockWriter.EXPECT().Close().Return(nil) 96 97 URI, err := archiver.NewURI("gs://my-bucket-cad/temporal_archival/development") 98 s.Require().NoError(err) 99 err = storageWrapper.Upload(ctx, URI, "myfile.history", []byte("{}")) 100 s.Require().NoError(err) 101 } 102 103 func (s *clientSuite) TestUploadWriterCloseError() { 104 ctx := context.Background() 105 106 mockStorageClient := connector.NewMockGcloudStorageClient(s.controller) 107 mockBucketHandleClient := connector.NewMockBucketHandleWrapper(s.controller) 108 mockObjectHandler := connector.NewMockObjectHandleWrapper(s.controller) 109 mockWriter := connector.NewMockWriterWrapper(s.controller) 110 111 storageWrapper, _ := connector.NewClientWithParams(mockStorageClient) 112 113 mockStorageClient.EXPECT().Bucket("my-bucket-cad").Return(mockBucketHandleClient) 114 mockBucketHandleClient.EXPECT().Object("temporal_archival/development/myfile.history").Return(mockObjectHandler) 115 mockObjectHandler.EXPECT().NewWriter(ctx).Return(mockWriter) 116 mockWriter.EXPECT().Write(gomock.Any()).Return(2, nil) 117 mockWriter.EXPECT().Close().Return(errors.New("Not Found")) 118 119 URI, err := archiver.NewURI("gs://my-bucket-cad/temporal_archival/development") 120 s.Require().NoError(err) 121 err = storageWrapper.Upload(ctx, URI, "myfile.history", []byte("{}")) 122 s.Require().EqualError(err, "Not Found") 123 } 124 125 func (s *clientSuite) TestExist() { 126 ctx := context.Background() 127 testCases := []struct { 128 callContext context.Context 129 URI string 130 fileName string 131 bucketName string 132 bucketExists bool 133 objectExists bool 134 bucketExpectedError error 135 objectExpectedError error 136 }{ 137 { 138 callContext: ctx, 139 URI: "gs://my-bucket-cad/temporal_archival/development", 140 fileName: "", 141 bucketName: "my-bucket-cad", 142 bucketExists: true, 143 objectExists: false, 144 bucketExpectedError: nil, 145 objectExpectedError: nil, 146 }, 147 { 148 callContext: ctx, 149 URI: "gs://my-bucket-cad/temporal_archival/development", 150 fileName: "", 151 bucketName: "my-bucket-cad", 152 bucketExists: false, 153 objectExists: false, 154 bucketExpectedError: errors.New("bucket not found"), 155 objectExpectedError: nil, 156 }, 157 { 158 callContext: ctx, 159 URI: "gs://my-bucket-cad/temporal_archival/development", 160 fileName: "myfile.history", 161 bucketName: "my-bucket-cad", 162 bucketExists: true, 163 objectExists: true, 164 bucketExpectedError: nil, 165 objectExpectedError: nil, 166 }, 167 { 168 callContext: ctx, 169 URI: "gs://my-bucket-cad/temporal_archival/development", 170 fileName: "myfile.history", 171 bucketName: "my-bucket-cad", 172 bucketExists: true, 173 objectExists: false, 174 bucketExpectedError: nil, 175 objectExpectedError: errors.New("object not found"), 176 }, 177 } 178 179 for _, tc := range testCases { 180 181 mockStorageClient := connector.NewMockGcloudStorageClient(s.controller) 182 mockBucketHandleClient := connector.NewMockBucketHandleWrapper(s.controller) 183 mockObjectHandler := connector.NewMockObjectHandleWrapper(s.controller) 184 185 mockStorageClient.EXPECT().Bucket(tc.bucketName).Return(mockBucketHandleClient).MaxTimes(1) 186 mockBucketHandleClient.EXPECT().Attrs(tc.callContext).Return(nil, tc.bucketExpectedError).MaxTimes(1) 187 mockBucketHandleClient.EXPECT().Object(tc.fileName).Return(mockObjectHandler).MaxTimes(1) 188 mockObjectHandler.EXPECT().Attrs(tc.callContext).Return(nil, tc.objectExpectedError).MaxTimes(1) 189 URI, _ := archiver.NewURI(tc.URI) 190 storageWrapper, _ := connector.NewClientWithParams(mockStorageClient) 191 exists, err := storageWrapper.Exist(tc.callContext, URI, tc.fileName) 192 if tc.bucketExists && tc.fileName == "" { 193 s.Require().NoError(err) 194 s.True(exists) 195 } else if !tc.bucketExists { 196 s.Require().Error(err) 197 s.Require().EqualError(err, "bucket not found") 198 s.False(exists) 199 } else if tc.bucketExists && tc.fileName != "" && tc.objectExists { 200 s.Require().NoError(err) 201 s.True(exists) 202 } else if tc.bucketExists && tc.fileName != "" && !tc.objectExists { 203 s.Require().Error(err) 204 s.Require().EqualError(err, "object not found") 205 s.False(exists) 206 } 207 } 208 209 } 210 211 func (s *clientSuite) TestGet() { 212 ctx := context.Background() 213 mockStorageClient := connector.NewMockGcloudStorageClient(s.controller) 214 mockBucketHandleClient := connector.NewMockBucketHandleWrapper(s.controller) 215 mockObjectHandler := connector.NewMockObjectHandleWrapper(s.controller) 216 mockReader := connector.NewMockReaderWrapper(s.controller) 217 218 storageWrapper, _ := connector.NewClientWithParams(mockStorageClient) 219 220 mockStorageClient.EXPECT().Bucket("my-bucket-cad").Return(mockBucketHandleClient) 221 mockBucketHandleClient.EXPECT().Object("temporal_archival/development/myfile.history").Return(mockObjectHandler) 222 mockObjectHandler.EXPECT().NewReader(ctx).Return(mockReader, nil) 223 mockReader.EXPECT().Read(gomock.Any()).Return(2, io.EOF) 224 mockReader.EXPECT().Close().Return(nil) 225 226 URI, err := archiver.NewURI("gs://my-bucket-cad/temporal_archival/development") 227 s.Require().NoError(err) 228 _, err = storageWrapper.Get(ctx, URI, "myfile.history") 229 s.Require().NoError(err) 230 } 231 232 func (s *clientSuite) TestWrongGoogleCredentialsPath() { 233 ctx := context.Background() 234 s.T().Setenv("GOOGLE_APPLICATION_CREDENTIALS", "/Wrong/path") 235 _, err := connector.NewClient(ctx, &config.GstorageArchiver{}) 236 s.Require().Error(err) 237 } 238 239 func (s *clientSuite) TestQuery() { 240 241 ctx := context.Background() 242 mockBucketHandleClient := connector.NewMockBucketHandleWrapper(s.controller) 243 mockStorageClient := connector.NewMockGcloudStorageClient(s.controller) 244 mockObjectIterator := connector.NewMockObjectIteratorWrapper(s.controller) 245 storageWrapper, _ := connector.NewClientWithParams(mockStorageClient) 246 247 attr := new(storage.ObjectAttrs) 248 attr.Name = "fileName_01" 249 250 mockStorageClient.EXPECT().Bucket("my-bucket-cad").Return(mockBucketHandleClient) 251 mockBucketHandleClient.EXPECT().Objects(ctx, gomock.Any()).Return(mockObjectIterator) 252 mockIterator := 0 253 mockObjectIterator.EXPECT().Next().DoAndReturn(func() (*storage.ObjectAttrs, error) { 254 mockIterator++ 255 if mockIterator == 1 { 256 return attr, nil 257 } 258 return nil, iterator.Done 259 260 }).Times(2) 261 262 var fileNames []string 263 URI, err := archiver.NewURI("gs://my-bucket-cad/temporal_archival/development") 264 s.Require().NoError(err) 265 fileNames, err = storageWrapper.Query(ctx, URI, "7478875943689868082123907395549832634615673687049942026838") 266 s.Require().NoError(err) 267 s.Equal(strings.Join(fileNames, ", "), "fileName_01") 268 } 269 270 func (s *clientSuite) TestQueryWithFilter() { 271 272 ctx := context.Background() 273 mockBucketHandleClient := connector.NewMockBucketHandleWrapper(s.controller) 274 mockStorageClient := connector.NewMockGcloudStorageClient(s.controller) 275 mockObjectIterator := connector.NewMockObjectIteratorWrapper(s.controller) 276 storageWrapper, _ := connector.NewClientWithParams(mockStorageClient) 277 278 attr := new(storage.ObjectAttrs) 279 attr.Name = "closeTimeout_2020-02-27T09:42:28Z_12851121011173788097_4418294404690464320_15619178330501475177.visibility" 280 attrInvalid := new(storage.ObjectAttrs) 281 attrInvalid.Name = "closeTimeout_2020-02-27T09:42:28Z_12851121011173788097_4418294404690464321_15619178330501475177.visibility" 282 283 mockStorageClient.EXPECT().Bucket("my-bucket-cad").Return(mockBucketHandleClient) 284 mockBucketHandleClient.EXPECT().Objects(ctx, gomock.Any()).Return(mockObjectIterator) 285 mockIterator := 0 286 mockObjectIterator.EXPECT().Next().DoAndReturn(func() (*storage.ObjectAttrs, error) { 287 mockIterator++ 288 switch mockIterator { 289 case 1: 290 return attr, nil 291 case 2: 292 return attrInvalid, nil 293 default: 294 return nil, iterator.Done 295 } 296 297 }).Times(3) 298 299 var fileNames []string 300 URI, err := archiver.NewURI("gs://my-bucket-cad/temporal_archival/development") 301 s.Require().NoError(err) 302 fileNames, _, _, err = storageWrapper.QueryWithFilters(ctx, URI, "closeTimeout_2020-02-27T09:42:28Z", 0, 0, []connector.Precondition{newWorkflowIDPrecondition("4418294404690464320")}) 303 304 s.Require().NoError(err) 305 s.Equal(strings.Join(fileNames, ", "), "closeTimeout_2020-02-27T09:42:28Z_12851121011173788097_4418294404690464320_15619178330501475177.visibility") 306 } 307 308 func newWorkflowIDPrecondition(workflowID string) connector.Precondition { 309 return func(subject interface{}) bool { 310 311 if workflowID == "" { 312 return true 313 } 314 315 fileName, ok := subject.(string) 316 if !ok { 317 return false 318 } 319 320 if strings.Contains(fileName, workflowID) { 321 fileNameParts := strings.Split(fileName, "_") 322 if len(fileNameParts) != 5 { 323 return true 324 } 325 return strings.Contains(fileName, fileNameParts[3]) 326 } 327 328 return false 329 } 330 }