github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/usersavedqueries/usqueries.go (about) 1 /* 2 Copyright 2023. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package usersavedqueries 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "errors" 23 "os" 24 "strconv" 25 "strings" 26 "sync" 27 28 "github.com/imdario/mergo" 29 30 jsoniter "github.com/json-iterator/go" 31 "github.com/siglens/siglens/pkg/blob" 32 "github.com/siglens/siglens/pkg/config" 33 "github.com/siglens/siglens/pkg/utils" 34 log "github.com/sirupsen/logrus" 35 "github.com/valyala/fasthttp" 36 ) 37 38 var usqBaseFilename string 39 40 // map of orgid => usq lock 41 var usqLock map[uint64]*sync.Mutex 42 var usqLastReadTime map[uint64]uint64 43 var localUSQInfoLock *sync.RWMutex = &sync.RWMutex{} 44 var externalUSQInfoLock *sync.RWMutex = &sync.RWMutex{} 45 46 // map of "orgid" => queryName" ==> fieldname => fieldvalue 47 // e.g. "123456789" => mysave1" => {"searchText":"...", "indexName": "..."} 48 var localUSQInfo map[uint64]map[string]map[string]interface{} = make(map[uint64]map[string]map[string]interface{}) 49 var externalUSQInfo map[uint64]map[string]map[string]interface{} = make(map[uint64]map[string]map[string]interface{}) 50 51 func InitUsq() error { 52 var sb strings.Builder 53 sb.WriteString(config.GetDataPath() + "querynodes/" + config.GetHostID() + "/usersavedqueries") 54 baseDir := sb.String() 55 usqBaseFilename = baseDir + "/usqinfo" 56 err := os.MkdirAll(baseDir, 0764) 57 if err != nil { 58 log.Errorf("InitUsq: failed to create basedir=%v, err=%v", baseDir, err) 59 return err 60 } 61 usqLock = make(map[uint64]*sync.Mutex) 62 usqLastReadTime = make(map[uint64]uint64) 63 64 acquireOrCreateLock(0) 65 err = readSavedQueries(0) 66 if err != nil { 67 releaseLock(0) 68 log.Errorf("InitUsq: failed to read saved queries, err=%v", err) 69 return err 70 } 71 releaseLock(0) 72 return nil 73 } 74 75 func acquireOrCreateLock(orgid uint64) { 76 if _, ok := usqLock[orgid]; !ok { 77 usqLock[orgid] = &sync.Mutex{} 78 } 79 usqLock[orgid].Lock() 80 } 81 82 func releaseLock(orgid uint64) { 83 usqLock[orgid].Unlock() 84 } 85 86 func writeUsq(qname string, uq map[string]interface{}, orgid uint64) error { 87 88 if qname == "" { 89 log.Errorf("writeUsq: failed to save query data, with empty query name") 90 return errors.New("writeUsq: failed to save query data, with empty query name") 91 } 92 93 acquireOrCreateLock(orgid) 94 err := readSavedQueries(orgid) 95 if err != nil { 96 releaseLock(orgid) 97 log.Errorf("writeUsq: failed to read sdata, err=%v", err) 98 return errors.New("Internal server error, failed to read sdata") 99 } 100 releaseLock(orgid) 101 localUSQInfoLock.Lock() 102 if _, ok := localUSQInfo[orgid]; !ok { 103 localUSQInfo[orgid] = make(map[string]map[string]interface{}) 104 } 105 localUSQInfo[orgid][qname] = uq 106 localUSQInfoLock.Unlock() 107 108 err = writeSavedQueries(orgid) 109 if err != nil { 110 log.Errorf("writeUsq: failed to stat file, err=%v", err) 111 return errors.New("Internal server error, cant write data") 112 } 113 return nil 114 } 115 116 /* 117 takes the queryname 118 returns: 119 bool : if found true else false 120 map[string]interface: the savequerydata 121 error: any error 122 */ 123 func getUsqOne(qname string, orgid uint64) (bool, map[string]map[string]interface{}, error) { 124 var allUSQInfo map[string]map[string]interface{} = make(map[string]map[string]interface{}) 125 retval := make(map[string]map[string]interface{}) 126 found := false 127 acquireOrCreateLock(orgid) 128 err := readSavedQueries(orgid) 129 if err != nil { 130 releaseLock(orgid) 131 log.Errorf("GetUsqOne: failed to read, err=%v", err) 132 return false, nil, err 133 } 134 releaseLock(orgid) 135 136 localUSQInfoLock.RLock() 137 defer localUSQInfoLock.RUnlock() 138 if orgMap, ok := localUSQInfo[orgid]; ok { 139 err = mergo.Merge(&allUSQInfo, &orgMap) 140 if err != nil { 141 log.Errorf("getUsqOne: failed to merge local saved queries, err=%v", err) 142 return false, localUSQInfo[orgid], err 143 } 144 } 145 146 externalUSQInfoLock.RLock() 147 defer externalUSQInfoLock.RUnlock() 148 if orgExternalMap, ok := externalUSQInfo[orgid]; ok { 149 err = mergo.Merge(&allUSQInfo, &orgExternalMap) 150 if err != nil { 151 log.Errorf("getUsqOne: failed to merge external saved queries, err=%v", err) 152 return false, localUSQInfo[orgid], err 153 } 154 } 155 for k, v := range allUSQInfo { 156 i := strings.Index(k, qname) 157 if i != -1 { 158 retval[k] = v 159 found = true 160 } 161 } 162 return found, retval, nil 163 } 164 165 func getUsqAll(orgid uint64) (bool, map[string]map[string]interface{}, error) { 166 var allUSQInfo map[string]map[string]interface{} = make(map[string]map[string]interface{}) 167 168 acquireOrCreateLock(orgid) 169 err := readSavedQueries(orgid) 170 if err != nil { 171 releaseLock(orgid) 172 log.Errorf("GetUsqAll: failed to read, err=%v", err) 173 return false, nil, err 174 } 175 releaseLock(orgid) 176 177 localUSQInfoLock.RLock() 178 defer localUSQInfoLock.RUnlock() 179 if orgMap, ok := localUSQInfo[orgid]; ok { 180 err = mergo.Merge(&allUSQInfo, &orgMap) 181 if err != nil { 182 log.Errorf("GetUsqAll: failed to merge local saved queries, err=%v", err) 183 return false, localUSQInfo[orgid], err 184 } 185 } 186 187 externalUSQInfoLock.RLock() 188 defer externalUSQInfoLock.RUnlock() 189 if orgExternalMap, ok := externalUSQInfo[orgid]; ok { 190 err = mergo.Merge(&allUSQInfo, &orgExternalMap) 191 if err != nil { 192 log.Errorf("GetUsqAll: failed to merge external saved queries, err=%v", err) 193 return false, localUSQInfo[orgid], err 194 } 195 } 196 197 // todo should we make a deep copy here 198 return true, allUSQInfo, nil 199 } 200 201 /* 202 takes the orgid whose usq are to be deleted 203 returns: 204 error: any error 205 */ 206 func deleteAllUsq(orgid uint64) error { 207 acquireOrCreateLock(orgid) 208 err := readSavedQueries(orgid) 209 if err != nil { 210 releaseLock(orgid) 211 log.Errorf("deleteAllUsq: failed to read, err=%v", err) 212 return err 213 } 214 releaseLock(orgid) 215 localUSQInfoLock.RLock() 216 if _, ok := localUSQInfo[orgid]; !ok { 217 localUSQInfoLock.RUnlock() 218 return nil 219 220 } 221 localUSQInfoLock.RUnlock() 222 223 localUSQInfoLock.Lock() 224 delete(localUSQInfo, orgid) 225 localUSQInfoLock.Unlock() 226 227 userSavedQueriesFilename := getUsqFileName(orgid) 228 err = os.Remove(userSavedQueriesFilename) 229 if err != nil { 230 log.Errorf("deleteAllUsq: failed to delete usersavedqueries file: %v", userSavedQueriesFilename) 231 return err 232 } 233 234 return nil 235 } 236 237 /* 238 takes the queryname to be deleted 239 returns: 240 bool : if deleted true else false 241 error: any error 242 */ 243 func deleteUsq(qname string, orgid uint64) (bool, error) { 244 245 acquireOrCreateLock(orgid) 246 err := readSavedQueries(orgid) 247 if err != nil { 248 releaseLock(orgid) 249 log.Errorf("DeleteUsq: failed to read, err=%v", err) 250 return false, err 251 } 252 releaseLock(orgid) 253 localUSQInfoLock.RLock() 254 if _, ok := localUSQInfo[orgid]; !ok { 255 localUSQInfoLock.RUnlock() 256 return false, nil 257 258 } 259 _, ok := localUSQInfo[orgid][qname] 260 if !ok { 261 localUSQInfoLock.RUnlock() 262 return false, nil 263 } 264 localUSQInfoLock.RUnlock() 265 localUSQInfoLock.Lock() 266 delete(localUSQInfo[orgid], qname) 267 localUSQInfoLock.Unlock() 268 269 err = writeSavedQueries(orgid) 270 if err != nil { 271 log.Errorf("DeleteUsq: failed to write file, err=%v", err) 272 return false, errors.New("Internal server error, cant write data") 273 } 274 275 return true, nil 276 } 277 278 /* 279 Caller must call this via a lock 280 */ 281 func readSavedQueries(orgid uint64) error { 282 283 // first see if on disk is newer than memory 284 usqFilename := getUsqFileName(orgid) 285 fileInfo, err := os.Stat(usqFilename) 286 if err != nil { 287 if !errors.Is(err, os.ErrNotExist) { 288 log.Errorf("readSavedQueries: failed to stat file, err=%v", err) 289 return errors.New("Internal server error, can't read mtime") 290 } 291 return nil // if doesnt exist then cant really do anything 292 } 293 294 modifiedTime := uint64(fileInfo.ModTime().UTC().Unix() * 1000) 295 lastReadTime, ok := usqLastReadTime[orgid] 296 if ok && modifiedTime <= lastReadTime { 297 return nil 298 } 299 300 rdata, err := os.ReadFile(usqFilename) 301 if err != nil { 302 if errors.Is(err, os.ErrNotExist) { 303 return nil 304 } 305 log.Errorf("readSavedQueries: Failed to read usersavdeequries file fname=%v, err=%v", usqFilename, err) 306 return err 307 } 308 309 var orgSavedQueriesMap map[string]map[string]interface{} 310 err = json.Unmarshal(rdata, &orgSavedQueriesMap) 311 if err != nil { 312 log.Errorf("readSavedQueries: Failed to unmarshall usqFilename=%v, err=%v", usqFilename, err) 313 return err 314 } 315 localUSQInfoLock.Lock() 316 localUSQInfo[orgid] = orgSavedQueriesMap 317 localUSQInfoLock.Unlock() 318 usqLastReadTime[orgid] = utils.GetCurrentTimeInMs() 319 320 return nil 321 } 322 323 func getUsqFileName(orgid uint64) string { 324 if orgid != 0 { 325 usqFilename := usqBaseFilename + "-" + strconv.FormatUint(orgid, 10) + ".bin" 326 return usqFilename 327 } else { 328 return usqBaseFilename + ".bin" 329 } 330 } 331 332 /* 333 Caller must call this via a lock 334 */ 335 func writeSavedQueries(orgid uint64) error { 336 337 usqFilename := getUsqFileName(orgid) 338 localUSQInfoLock.RLock() 339 orgMap := localUSQInfo[orgid] 340 localUSQInfoLock.RUnlock() 341 jdata, err := json.Marshal(orgMap) 342 if err != nil { 343 log.Errorf("writeSavedQueries: Failed to marshall err=%v", err) 344 return err 345 } 346 347 err = os.WriteFile(usqFilename, jdata, 0644) 348 if err != nil { 349 log.Errorf("writeSavedQueries: Failed to writefile fullname=%v, err=%v", usqFilename, err) 350 return err 351 } 352 err = blob.UploadQueryNodeDir() 353 if err != nil { 354 log.Errorf("DeleteUserSavedQuery: Failed to upload query nodes dir err=%v", err) 355 return err 356 } 357 return nil 358 } 359 360 func DeleteAllUserSavedQueries(orgid uint64) error { 361 err := deleteAllUsq(orgid) 362 if err != nil { 363 log.Errorf("DeleteAllUserSavedQueries: Failed to delete user saved queries for orgid %d, err=%v", orgid, err) 364 return err 365 } 366 log.Infof("DeleteAllUserSavedQueries: Successfully deleted user saved queries for orgid: %d", orgid) 367 err = blob.UploadQueryNodeDir() 368 if err != nil { 369 log.Errorf("DeleteAllUserSavedQueries: Failed to upload query nodes dir, err=%v", err) 370 return err 371 } 372 373 return nil 374 } 375 376 func DeleteUserSavedQuery(ctx *fasthttp.RequestCtx) { 377 queryName := utils.ExtractParamAsString(ctx.UserValue("qname")) 378 deleted, err := deleteUsq(queryName, 0) 379 if err != nil { 380 log.Errorf("DeleteUserSavedQuery: Failed to delete user saved query %v, err=%v", queryName, err) 381 utils.SetBadMsg(ctx, "") 382 return 383 } 384 if !deleted { 385 utils.SetBadMsg(ctx, "") 386 return 387 } 388 log.Infof("DeleteUserSavedQuery: Successfully deleted user saved query %v", queryName) 389 err = blob.UploadQueryNodeDir() 390 if err != nil { 391 log.Errorf("DeleteUserSavedQuery: Failed to upload query nodes dir err=%v", err) 392 return 393 } 394 ctx.SetStatusCode(fasthttp.StatusOK) 395 } 396 397 func GetUserSavedQueriesAll(ctx *fasthttp.RequestCtx) { 398 httpResp := make(utils.AllSavedQueries) 399 found, savedQueriesAll, err := getUsqAll(0) 400 if err != nil { 401 log.Errorf("GetUserSavedQueriesAll: Failed to read user saved queries, err=%v", err) 402 utils.SetBadMsg(ctx, "") 403 return 404 } 405 if !found { 406 return 407 } 408 for name, usquery := range savedQueriesAll { 409 httpResp[name] = usquery 410 } 411 utils.WriteJsonResponse(ctx, httpResp) 412 ctx.SetStatusCode(fasthttp.StatusOK) 413 } 414 415 func SaveUserQueries(ctx *fasthttp.RequestCtx) { 416 rawJSON := ctx.PostBody() 417 if rawJSON == nil { 418 log.Errorf("SaveUserQueries: received empty user query") 419 utils.SetBadMsg(ctx, "") 420 return 421 } 422 423 readJSON := make(map[string]interface{}) 424 var jsonc = jsoniter.ConfigCompatibleWithStandardLibrary 425 decoder := jsonc.NewDecoder(bytes.NewReader(rawJSON)) 426 decoder.UseNumber() 427 err := decoder.Decode(&readJSON) 428 if err != nil { 429 ctx.SetStatusCode(fasthttp.StatusBadRequest) 430 _, err = ctx.WriteString(err.Error()) 431 if err != nil { 432 log.Errorf("SaveUserQueries: could not write error message err=%v", err) 433 } 434 log.Errorf("SaveUserQueries: failed to decode user query body! Err=%+v", err) 435 } 436 437 var qname string 438 usQueryMap := make(map[string]interface{}) 439 for key, value := range readJSON { 440 switch valtype := value.(type) { 441 case string: 442 switch key { 443 case "queryName": 444 { 445 qname = valtype 446 } 447 case "queryDescription": 448 { 449 usQueryMap["description"] = valtype 450 } 451 case "searchText": 452 { 453 usQueryMap["searchText"] = valtype 454 } 455 case "indexName": 456 { 457 usQueryMap["indexName"] = valtype 458 } 459 case "filterTab": 460 { 461 usQueryMap["filterTab"] = valtype 462 } 463 case "queryLanguage": 464 { 465 usQueryMap["queryLanguage"] = valtype 466 } 467 } 468 default: 469 log.Errorf("SaveUserQueries: Invalid save query key=[%v]", key) 470 utils.SetBadMsg(ctx, "") 471 return 472 } 473 } 474 err = writeUsq(qname, usQueryMap, 0) 475 if err != nil { 476 log.Errorf("SaveUserQueries: could not write query to file err=%v", err) 477 utils.SetBadMsg(ctx, "") 478 return 479 } 480 log.Infof("SaveUserQueries: successfully written query %v to file ", qname) 481 ctx.SetStatusCode(fasthttp.StatusOK) 482 } 483 484 func ReadExternalUSQInfo(fName string, orgid uint64) error { 485 var tempUSQInfo map[string]map[string]interface{} = make(map[string]map[string]interface{}) 486 content, err := os.ReadFile(fName) 487 if err != nil { 488 if errors.Is(err, os.ErrNotExist) { 489 return nil 490 } 491 log.Errorf("ReadExternalUSQInfo: error in reading fname=%v, err=%v", fName, err) 492 return err 493 } 494 495 err = json.Unmarshal(content, &tempUSQInfo) 496 if err != nil { 497 log.Errorf("ReadExternalUSQInfo: json unmarshall failed fname=%v, err=%v", fName, err) 498 return err 499 } 500 externalUSQInfoLock.Lock() 501 if _, ok := externalUSQInfo[orgid]; !ok { 502 externalUSQInfo[orgid] = make(map[string]map[string]interface{}) 503 } 504 for usQuery, usQueryInfo := range tempUSQInfo { 505 externalUSQInfo[orgid][usQuery] = usQueryInfo 506 } 507 externalUSQInfoLock.Unlock() 508 return nil 509 } 510 511 func SearchUserSavedQuery(ctx *fasthttp.RequestCtx) { 512 queryName := utils.ExtractParamAsString(ctx.UserValue("qname")) 513 514 found, usqInfo, err := getUsqOne(queryName, 0) 515 516 log.Infof("SearchUserSavedQuery: Found=%v, usqInfo=%v", found, usqInfo) 517 if err != nil { 518 log.Errorf("SearchUserSavedQuery: Failed to get user saved query %v, err=%v", queryName, err) 519 utils.SetBadMsg(ctx, "") 520 return 521 } 522 if !found { 523 response := "Query not found" 524 utils.WriteJsonResponse(ctx, response) 525 ctx.SetStatusCode(fasthttp.StatusNotFound) 526 return 527 } 528 529 utils.WriteJsonResponse(ctx, usqInfo) 530 ctx.SetStatusCode(fasthttp.StatusOK) 531 }