github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/es/writer/esBulkHandler.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 writer
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"errors"
    23  	"strings"
    24  	"time"
    25  
    26  	jp "github.com/buger/jsonparser"
    27  	"github.com/google/uuid"
    28  	jsoniter "github.com/json-iterator/go"
    29  	"github.com/siglens/siglens/pkg/config"
    30  	segment "github.com/siglens/siglens/pkg/segment/utils"
    31  
    32  	"github.com/siglens/siglens/pkg/segment/writer"
    33  	"github.com/siglens/siglens/pkg/usageStats"
    34  	"github.com/siglens/siglens/pkg/utils"
    35  
    36  	// segstructs "github.com/siglens/siglens/pkg/segment/structs"
    37  
    38  	vtable "github.com/siglens/siglens/pkg/virtualtable"
    39  	log "github.com/sirupsen/logrus"
    40  	"github.com/valyala/fasthttp"
    41  )
    42  
    43  const (
    44  	INDEX = iota
    45  	CREATE
    46  	UPDATE
    47  	DELETE
    48  )
    49  
    50  const INDEX_TOP_STR string = "index"
    51  const CREATE_TOP_STR string = "create"
    52  const UPDATE_TOP_STR string = "update"
    53  const INDEX_UNDER_STR string = "_index"
    54  
    55  type kibanaIngHandlerFnDef func(
    56  	ctx *fasthttp.RequestCtx, request map[string]interface{},
    57  	indexNameConverted string, updateArg bool, idVal string, tsNow uint64, myid uint64) error
    58  
    59  func ProcessBulkRequest(ctx *fasthttp.RequestCtx, myid uint64, kibanaIngHandlerFn kibanaIngHandlerFnDef) {
    60  
    61  	processedCount, response, err := HandleBulkBody(ctx.PostBody(), ctx, myid, kibanaIngHandlerFn)
    62  	if err != nil {
    63  		PostBulkErrorResponse(ctx)
    64  		return
    65  	}
    66  
    67  	//request body empty
    68  	if processedCount == 0 {
    69  		PostBulkErrorResponse(ctx)
    70  	} else {
    71  		utils.WriteJsonResponse(ctx, response)
    72  	}
    73  }
    74  
    75  func HandleBulkBody(postBody []byte, ctx *fasthttp.RequestCtx, myid uint64, kibanaIngHandlerFn kibanaIngHandlerFnDef) (int, map[string]interface{}, error) {
    76  
    77  	r := bytes.NewReader(postBody)
    78  
    79  	response := make(map[string]interface{})
    80  	//to have a check if there are any errors in the request
    81  	var overallError bool
    82  	//to check for status : 200 or 400
    83  	var success bool
    84  	//to check if json is greater than MAX_RECORD_SIZE
    85  	var maxRecordSizeExceeded bool
    86  	startTime := time.Now().UnixNano()
    87  	var inCount int = 0
    88  	var processedCount int = 0
    89  	tsNow := utils.GetCurrentTimeInMs()
    90  	scanner := bufio.NewScanner(r)
    91  	scanner.Split(bufio.ScanLines)
    92  
    93  	var bytesReceived int
    94  	// store all request index
    95  	var items = make([]interface{}, 0)
    96  	atleastOneSuccess := false
    97  	localIndexMap := make(map[string]string)
    98  	for scanner.Scan() {
    99  		inCount++
   100  		esAction, indexName, idVal := extractIndexAndValidateAction(scanner.Bytes())
   101  		switch esAction {
   102  
   103  		case INDEX, CREATE:
   104  			scanner.Scan()
   105  			rawJson := scanner.Bytes()
   106  			numBytes := len(rawJson)
   107  			bytesReceived += numBytes
   108  			//update only if body is less than MAX_RECORD_SIZE
   109  			if numBytes < segment.MAX_RECORD_SIZE {
   110  				processedCount++
   111  				success = true
   112  				if strings.Contains(indexName, ".kibana") {
   113  					indexNameConverted := AddAndGetRealIndexName(indexName, localIndexMap, myid)
   114  					if idVal == "" {
   115  						idVal = uuid.New().String()
   116  					}
   117  					request := make(map[string]interface{})
   118  					var json = jsoniter.ConfigCompatibleWithStandardLibrary
   119  					decoder := json.NewDecoder(bytes.NewReader(rawJson))
   120  					decoder.UseNumber()
   121  					err := decoder.Decode(&request)
   122  					if err != nil {
   123  						success = false
   124  					}
   125  					err = kibanaIngHandlerFn(ctx, request, indexNameConverted, false, idVal, tsNow, myid)
   126  					if err != nil {
   127  						success = false
   128  					}
   129  				} else {
   130  					err := ProcessIndexRequest(rawJson, tsNow, indexName, uint64(numBytes), false, localIndexMap, myid)
   131  					if err != nil {
   132  						success = false
   133  					}
   134  				}
   135  			} else {
   136  				success = false
   137  				maxRecordSizeExceeded = true
   138  			}
   139  
   140  		case UPDATE:
   141  			success = false
   142  			scanner.Scan()
   143  		default:
   144  			success = false
   145  		}
   146  
   147  		responsebody := make(map[string]interface{})
   148  		if !success {
   149  			if maxRecordSizeExceeded {
   150  				error_response := utils.BulkErrorResponse{
   151  					ErrorResponse: *utils.NewBulkErrorResponseInfo("request entity too large", "request_entity_exception"),
   152  				}
   153  				responsebody["index"] = error_response
   154  				responsebody["status"] = 413
   155  				items = append(items, responsebody)
   156  			} else {
   157  				overallError = true
   158  				error_response := utils.BulkErrorResponse{
   159  					ErrorResponse: *utils.NewBulkErrorResponseInfo("indexing request failed", "mapper_parse_exception"),
   160  				}
   161  				responsebody["index"] = error_response
   162  				responsebody["status"] = 400
   163  				items = append(items, responsebody)
   164  			}
   165  		} else {
   166  			atleastOneSuccess = true
   167  			statusbody := make(map[string]interface{})
   168  			statusbody["status"] = 201
   169  			responsebody["index"] = statusbody
   170  			items = append(items, responsebody)
   171  		}
   172  	}
   173  	usageStats.UpdateStats(uint64(bytesReceived), uint64(inCount), myid)
   174  	timeTook := time.Now().UnixNano() - (startTime)
   175  	response["took"] = timeTook / 1000
   176  	response["error"] = overallError
   177  	response["items"] = items
   178  
   179  	if atleastOneSuccess {
   180  		return processedCount, response, nil
   181  	} else {
   182  		return processedCount, response, errors.New("all bulk requests failed")
   183  	}
   184  }
   185  
   186  func extractIndexAndValidateAction(rawJson []byte) (int, string, string) {
   187  	val, dType, _, err := jp.Get(rawJson, INDEX_TOP_STR)
   188  	if err == nil && dType == jp.Object {
   189  		idVal, err := jp.GetString(val, "_id")
   190  		if err != nil {
   191  			idVal = ""
   192  		}
   193  
   194  		idxVal, err := jp.GetString(val, INDEX_UNDER_STR)
   195  		if err != nil {
   196  			idxVal = ""
   197  		}
   198  		return INDEX, idxVal, idVal
   199  	}
   200  
   201  	val, dType, _, err = jp.Get(rawJson, CREATE_TOP_STR)
   202  	if err == nil && dType == jp.Object {
   203  		idVal, err := jp.GetString(val, "_id")
   204  		if err != nil {
   205  			idVal = ""
   206  		}
   207  
   208  		idxVal, err := jp.GetString(val, INDEX_UNDER_STR)
   209  		if err != nil {
   210  			idxVal = ""
   211  		}
   212  		return CREATE, idxVal, idVal
   213  	}
   214  	val, dType, _, err = jp.Get(rawJson, UPDATE_TOP_STR)
   215  	if err == nil && dType == jp.Object {
   216  		idVal, err := jp.GetString(val, "_id")
   217  		if err != nil {
   218  			idVal = ""
   219  		}
   220  
   221  		idxVal, err := jp.GetString(val, INDEX_UNDER_STR)
   222  		if err != nil {
   223  			idxVal = ""
   224  		}
   225  		return UPDATE, idxVal, idVal
   226  	}
   227  	return DELETE, "eventType", ""
   228  }
   229  
   230  func AddAndGetRealIndexName(indexNameIn string, localIndexMap map[string]string, myid uint64) string {
   231  
   232  	// first check localCopy of map, if it exists then avoid the lock inside vtables.
   233  	// note that this map gets reset on every bulk request
   234  	lVal, ok := localIndexMap[indexNameIn]
   235  	if ok {
   236  		return lVal
   237  	}
   238  
   239  	var indexNameConverted string
   240  	if pres, idxName := vtable.IsAlias(indexNameIn, myid); pres {
   241  		indexNameConverted = idxName
   242  	} else {
   243  		indexNameConverted = indexNameIn
   244  	}
   245  
   246  	localIndexMap[indexNameIn] = indexNameConverted
   247  
   248  	err := vtable.AddVirtualTable(&indexNameConverted, myid)
   249  	if err != nil {
   250  		log.Errorf("AddAndGetRealIndexName: failed to add virtual table, err=%v", err)
   251  	}
   252  	return indexNameConverted
   253  }
   254  
   255  func ProcessIndexRequest(rawJson []byte, tsNow uint64, indexNameIn string,
   256  	bytesReceived uint64, flush bool, localIndexMap map[string]string, myid uint64) error {
   257  
   258  	indexNameConverted := AddAndGetRealIndexName(indexNameIn, localIndexMap, myid)
   259  	cfgkey := config.GetTimeStampKey()
   260  
   261  	var docType segment.SIGNAL_TYPE
   262  	if strings.HasPrefix(indexNameConverted, "jaeger-") {
   263  		docType = segment.SIGNAL_JAEGER_TRACES
   264  		cfgkey = "startTimeMillis"
   265  	} else {
   266  		docType = segment.SIGNAL_EVENTS
   267  	}
   268  
   269  	ts_millis := utils.ExtractTimeStamp(rawJson, &cfgkey)
   270  	if ts_millis == 0 {
   271  		ts_millis = tsNow
   272  	}
   273  	streamid := utils.CreateStreamId(indexNameConverted, myid)
   274  
   275  	// TODO: we used to add _index in the json_source doc, since it is needed during
   276  	// json-rsponse formation during query-resp. We should either add it in this AddEntryToInMemBuf
   277  	// OR in json-resp creation we add it in the resp using the vtable name
   278  
   279  	err := writer.AddEntryToInMemBuf(streamid, rawJson, ts_millis, indexNameConverted, bytesReceived, flush,
   280  		docType, myid)
   281  	if err != nil {
   282  		log.Errorf("ProcessIndexRequest: failed to add entry to in mem buffer, err=%v", err)
   283  		return err
   284  	}
   285  	return nil
   286  }
   287  
   288  func ProcessPutIndex(ctx *fasthttp.RequestCtx, myid uint64) {
   289  
   290  	r := string(ctx.PostBody())
   291  	indexName := ctx.UserValue("indexName").(string)
   292  
   293  	log.Infof("ProcessPutIndex: adding index and mapping: indexName=%v", indexName)
   294  
   295  	err := vtable.AddVirtualTableAndMapping(&indexName, &r, myid)
   296  	if err != nil {
   297  		ctx.SetStatusCode(fasthttp.StatusInternalServerError)
   298  		_, err = ctx.Write([]byte("Failed to put index/mapping"))
   299  		if err != nil {
   300  			log.Errorf("ProcessPutIndex: failed to write response, err=%v", err)
   301  		}
   302  		ctx.SetContentType(utils.ContentJson)
   303  		return
   304  	}
   305  
   306  	ctx.SetStatusCode(fasthttp.StatusOK)
   307  }
   308  
   309  func PostBulkErrorResponse(ctx *fasthttp.RequestCtx) {
   310  
   311  	ctx.SetStatusCode(fasthttp.StatusBadRequest)
   312  	responsebody := make(map[string]interface{})
   313  	error_response := utils.BulkErrorResponse{
   314  		ErrorResponse: *utils.NewBulkErrorResponseInfo("request body is required", "parse_exception"),
   315  	}
   316  	responsebody["index"] = error_response
   317  	responsebody["status"] = 400
   318  	utils.WriteJsonResponse(ctx, responsebody)
   319  }