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  }