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 }