go.temporal.io/server@v1.23.0/common/searchattribute/manager_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 searchattribute 26 27 import ( 28 "context" 29 "errors" 30 "sync" 31 "testing" 32 "time" 33 34 "github.com/golang/mock/gomock" 35 "github.com/stretchr/testify/require" 36 "github.com/stretchr/testify/suite" 37 enumspb "go.temporal.io/api/enums/v1" 38 "go.temporal.io/api/serviceerror" 39 40 persistencespb "go.temporal.io/server/api/persistence/v1" 41 "go.temporal.io/server/common/clock" 42 "go.temporal.io/server/common/log" 43 "go.temporal.io/server/common/persistence" 44 ) 45 46 type ( 47 searchAttributesManagerSuite struct { 48 suite.Suite 49 *require.Assertions 50 51 controller *gomock.Controller 52 53 logger log.Logger 54 timeSource *clock.EventTimeSource 55 mockClusterMetadataManager *persistence.MockClusterMetadataManager 56 manager *managerImpl 57 forceCacheRefresh bool 58 } 59 ) 60 61 func TestSearchAttributesManagerSuite(t *testing.T) { 62 suite.Run(t, &searchAttributesManagerSuite{}) 63 } 64 65 func (s *searchAttributesManagerSuite) SetupSuite() { 66 } 67 68 func (s *searchAttributesManagerSuite) TearDownSuite() { 69 70 } 71 72 func (s *searchAttributesManagerSuite) SetupTest() { 73 s.Assertions = require.New(s.T()) 74 s.controller = gomock.NewController(s.T()) 75 s.logger = log.NewTestLogger() 76 s.timeSource = clock.NewEventTimeSource() 77 s.mockClusterMetadataManager = persistence.NewMockClusterMetadataManager(s.controller) 78 s.manager = NewManager( 79 s.timeSource, 80 s.mockClusterMetadataManager, 81 func() bool { 82 return s.forceCacheRefresh 83 }, 84 ) 85 } 86 87 func (s *searchAttributesManagerSuite) TearDownTest() { 88 s.controller.Finish() 89 } 90 91 func (s *searchAttributesManagerSuite) TestGetSearchAttributesCache() { 92 s.timeSource.Update(time.Date(2020, 8, 22, 1, 0, 0, 0, time.UTC)) 93 // Initial call 94 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 95 ClusterMetadata: &persistencespb.ClusterMetadata{ 96 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 97 "index-name": { 98 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 99 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 100 }}}, 101 }, 102 Version: 1, 103 }, nil) 104 // Second call, no changes in DB (version is the same) 105 s.timeSource.Update(time.Date(2020, 8, 22, 1, 0, 10, 0, time.UTC)) 106 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 107 ClusterMetadata: &persistencespb.ClusterMetadata{ 108 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 109 "index-name": { 110 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 111 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 112 }}}, 113 }, 114 Version: 1, 115 }, nil) 116 // Third call, DB changed 117 s.timeSource.Update(time.Date(2020, 8, 22, 1, 0, 20, 0, time.UTC)) 118 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 119 ClusterMetadata: &persistencespb.ClusterMetadata{ 120 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 121 "index-name": { 122 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 123 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 124 }}}, 125 }, 126 Version: 2, 127 }, nil) 128 129 wg := sync.WaitGroup{} 130 wg.Add(10) 131 for goroutine := 0; goroutine < 10; goroutine++ { 132 go func(goroutine int) { 133 defer wg.Done() 134 for i := 1; i < 1500; i++ { 135 searchAttributes, err := s.manager.GetSearchAttributes("index-name", false) 136 s.NoError(err) 137 s.Len(searchAttributes.Custom(), 1) 138 t, err := searchAttributes.GetType("OrderId") 139 s.NoError(err) 140 s.Equal(enumspb.INDEXED_VALUE_TYPE_KEYWORD, t) 141 if i%500 == 0 && goroutine == 5 { 142 // This moves time two times. 143 s.timeSource.Update(s.timeSource.Now().Add(cacheRefreshInterval).Add(time.Second)) 144 } 145 } 146 }(goroutine) 147 } 148 wg.Wait() 149 } 150 151 func (s *searchAttributesManagerSuite) TestGetSearchAttributesCache_Error() { 152 s.timeSource.Update(time.Date(2020, 8, 22, 1, 0, 0, 0, time.UTC)) 153 // Initial call 154 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(nil, errors.New("random error")) 155 searchAttributes, err := s.manager.GetSearchAttributes("index-name", false) 156 s.Error(err) 157 s.Len(searchAttributes.Custom(), 0) 158 } 159 160 func (s *searchAttributesManagerSuite) TestGetSearchAttributesCache_NotFoundError() { 161 s.timeSource.Update(time.Date(2020, 8, 22, 1, 0, 0, 0, time.UTC)) 162 163 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(nil, serviceerror.NewNotFound("not found")) 164 searchAttributes, err := s.manager.GetSearchAttributes("index-name", false) 165 s.NoError(err) 166 s.Len(searchAttributes.Custom(), 0) 167 168 // GetClusterMetadata() shouldn't be called, because results are cached. 169 searchAttributes, err = s.manager.GetSearchAttributes("index-name", false) 170 s.NoError(err) 171 s.Len(searchAttributes.Custom(), 0) 172 } 173 174 func (s *searchAttributesManagerSuite) TestGetSearchAttributesCache_UnavailableError() { 175 s.timeSource.Update(time.Date(2020, 8, 22, 1, 0, 0, 0, time.UTC)) 176 177 // First call populates cache. 178 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 179 ClusterMetadata: &persistencespb.ClusterMetadata{ 180 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 181 "index-name": { 182 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 183 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 184 }}}, 185 }, 186 Version: 1, 187 }, nil) 188 searchAttributes, err := s.manager.GetSearchAttributes("index-name", false) 189 s.NoError(err) 190 s.Len(searchAttributes.Custom(), 1) 191 192 // Expire cache. 193 s.timeSource.Update(time.Date(2020, 8, 22, 2, 0, 0, 0, time.UTC)) 194 195 // Second call, cache is expired, DB is down, but cache data is returned. 196 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(nil, serviceerror.NewUnavailable("db is down")) 197 searchAttributes, err = s.manager.GetSearchAttributes("index-name", false) 198 s.NoError(err) 199 s.Len(searchAttributes.Custom(), 1) 200 201 // Next cache refresh in cacheRefreshIfUnavailableInterval. 202 c := s.manager.cache.Load().(cache) 203 s.Equal(time.Date(2020, 8, 22, 2, 0, 20, 0, time.UTC), c.expireOn) 204 } 205 206 func (s *searchAttributesManagerSuite) TestGetSearchAttributesCache_EmptyIndex() { 207 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 208 ClusterMetadata: &persistencespb.ClusterMetadata{ 209 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 210 "": { 211 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 212 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 213 }}}, 214 }, 215 Version: 1, 216 }, nil) 217 218 searchAttributes, err := s.manager.GetSearchAttributes("", false) 219 s.NoError(err) 220 s.Len(searchAttributes.Custom(), 1) 221 } 222 223 func (s *searchAttributesManagerSuite) TestGetSearchAttributesCache_RefreshIfAbsent() { 224 s.timeSource.Update(time.Date(2020, 8, 22, 1, 0, 0, 0, time.UTC)) 225 226 // First call populates cache. 227 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 228 ClusterMetadata: &persistencespb.ClusterMetadata{ 229 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{}, 230 }, 231 Version: 1, 232 }, nil) 233 234 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 235 ClusterMetadata: &persistencespb.ClusterMetadata{ 236 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 237 "index-name": { 238 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 239 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 240 }}}, 241 }, 242 Version: 2, 243 }, nil) 244 245 searchAttributes, err := s.manager.GetSearchAttributes("index-name", false) 246 s.NoError(err) 247 s.Len(searchAttributes.Custom(), 0) 248 249 s.timeSource.Update(time.Date(2020, 8, 22, 1, 0, 1, 0, time.UTC)) 250 251 searchAttributes, err = s.manager.GetSearchAttributes("index-name", false) 252 s.NoError(err) 253 s.Len(searchAttributes.Custom(), 0) 254 255 s.forceCacheRefresh = true 256 searchAttributes, err = s.manager.GetSearchAttributes("index-name", false) 257 s.NoError(err) 258 s.Len(searchAttributes.Custom(), 1) 259 } 260 261 func (s *searchAttributesManagerSuite) TestSaveSearchAttributes_UpdateIndex() { 262 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 263 ClusterMetadata: &persistencespb.ClusterMetadata{ 264 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 265 "index-name": { 266 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 267 "OrderIdOld": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 268 }}}, 269 }, 270 Version: 1, 271 }, nil) 272 273 s.mockClusterMetadataManager.EXPECT().SaveClusterMetadata(gomock.Any(), &persistence.SaveClusterMetadataRequest{ 274 ClusterMetadata: &persistencespb.ClusterMetadata{ 275 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 276 "index-name": { 277 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 278 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 279 }}}, 280 }, 281 Version: 1, 282 }).Return(false, nil) 283 284 err := s.manager.SaveSearchAttributes(context.Background(), "index-name", map[string]enumspb.IndexedValueType{ 285 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 286 }) 287 s.NoError(err) 288 } 289 func (s *searchAttributesManagerSuite) TestSaveSearchAttributes_NewIndex() { 290 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 291 ClusterMetadata: &persistencespb.ClusterMetadata{ 292 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 293 "index-name-2": { 294 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 295 "OrderId2": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 296 }}}, 297 }, 298 Version: 1, 299 }, nil) 300 301 s.mockClusterMetadataManager.EXPECT().SaveClusterMetadata(gomock.Any(), &persistence.SaveClusterMetadataRequest{ 302 ClusterMetadata: &persistencespb.ClusterMetadata{ 303 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 304 "index-name-2": { 305 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 306 "OrderId2": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 307 }}, 308 "index-name": { 309 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 310 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 311 }}}, 312 }, 313 Version: 1, 314 }).Return(false, nil) 315 316 err := s.manager.SaveSearchAttributes(context.Background(), "index-name", map[string]enumspb.IndexedValueType{ 317 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 318 }) 319 s.NoError(err) 320 } 321 322 func (s *searchAttributesManagerSuite) TestSaveSearchAttributesCache_EmptyIndex() { 323 s.mockClusterMetadataManager.EXPECT().GetCurrentClusterMetadata(gomock.Any()).Return(&persistence.GetClusterMetadataResponse{ 324 ClusterMetadata: &persistencespb.ClusterMetadata{ 325 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 326 "index-name-2": { 327 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 328 "OrderId2": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 329 }}}, 330 }, 331 Version: 1, 332 }, nil) 333 334 s.mockClusterMetadataManager.EXPECT().SaveClusterMetadata(gomock.Any(), &persistence.SaveClusterMetadataRequest{ 335 ClusterMetadata: &persistencespb.ClusterMetadata{ 336 IndexSearchAttributes: map[string]*persistencespb.IndexSearchAttributes{ 337 "index-name-2": { 338 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 339 "OrderId2": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 340 }}, 341 "": { 342 CustomSearchAttributes: map[string]enumspb.IndexedValueType{ 343 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 344 }}}, 345 }, 346 Version: 1, 347 }).Return(false, nil) 348 349 err := s.manager.SaveSearchAttributes(context.Background(), "", map[string]enumspb.IndexedValueType{ 350 "OrderId": enumspb.INDEXED_VALUE_TYPE_KEYWORD, 351 }) 352 s.NoError(err) 353 }