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  }