github.com/siglens/siglens@v0.0.0-20240328180423-f7ce9ae441ed/pkg/virtualtable/virtualtable.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 virtualtable 18 19 import ( 20 "bufio" 21 "encoding/json" 22 "errors" 23 "os" 24 "regexp" 25 "sort" 26 "strconv" 27 "strings" 28 "sync" 29 "time" 30 31 "github.com/siglens/siglens/pkg/config" 32 log "github.com/sirupsen/logrus" 33 ) 34 35 var VTableBaseDir string 36 var VTableMappingsDir string 37 var VTableTemplatesDir string 38 var VTableAliasesDir string 39 40 const VIRTUAL_TAB_FILENAME = "/virtualtablenames" 41 const VIRTUAL_TAB_FILE_EXT = ".txt" 42 43 var vTableBaseFileName string 44 45 var globalTableAccessLock sync.RWMutex = sync.RWMutex{} 46 var vTableRawFileAccessLock sync.RWMutex = sync.RWMutex{} 47 48 var aliasToIndexNames map[uint64]map[string]map[string]bool = make(map[uint64]map[string]map[string]bool) 49 50 // holds all the tables for orgid -> tname -> bool 51 var allVirtualTables map[uint64]map[string]bool 52 53 var excludedInternalIndices = [...]string{"traces", "red-traces", "service-dependency"} 54 55 func InitVTable() error { 56 allVirtualTables = make(map[uint64]map[string]bool) 57 var sb strings.Builder 58 sb.WriteString(config.GetDataPath() + "ingestnodes/" + config.GetHostID() + "/vtabledata") 59 VTableBaseDir = sb.String() 60 VTableMappingsDir = VTableBaseDir + "/mappings/" 61 VTableTemplatesDir = VTableBaseDir + "/templates/" 62 VTableAliasesDir = VTableBaseDir + "/aliases/" 63 64 var sb1 strings.Builder 65 sb1.WriteString(VTableBaseDir) 66 sb1.WriteString(VIRTUAL_TAB_FILENAME) 67 68 vTableBaseFileName = sb1.String() 69 70 err := CreateVirtTableBaseDirs(VTableBaseDir, VTableMappingsDir, VTableTemplatesDir, VTableAliasesDir) 71 if err != nil { 72 return err 73 } 74 err = initializeAliasToIndexMap() 75 if err != nil { 76 log.Errorf("InitVTable: Failed to initialize alias to index map, err=%v", err) 77 return err 78 } 79 80 go refreshInMemoryTable() 81 return nil 82 } 83 84 func getVirtualTableFileName(orgid uint64) string { 85 var vTableFileName string 86 if orgid == 0 { 87 vTableFileName = vTableBaseFileName + VIRTUAL_TAB_FILE_EXT 88 } else { 89 vTableFileName = vTableBaseFileName + "-" + strconv.FormatUint(orgid, 10) + VIRTUAL_TAB_FILE_EXT 90 } 91 return vTableFileName 92 } 93 94 func refreshInMemoryTable() { 95 for { 96 allReadTables, err := GetVirtualTableNames(0) 97 if err != nil { 98 log.Errorf("refreshInMemoryTable: Failed to get virtual table names! err=%v", err) 99 } else { 100 globalTableAccessLock.Lock() 101 allVirtualTables[uint64(0)] = allReadTables 102 globalTableAccessLock.Unlock() 103 } 104 time.Sleep(1 * time.Minute) 105 } 106 } 107 108 func GetFilePathForRemoteNode(node string, orgid uint64) string { 109 var vFile strings.Builder 110 vFile.WriteString(config.GetDataPath() + "ingestnodes/" + node + "/vtabledata") 111 vFile.WriteString(VIRTUAL_TAB_FILENAME) 112 if orgid != 0 { 113 vFile.WriteString("-" + strconv.FormatUint(orgid, 10) + VIRTUAL_TAB_FILE_EXT) 114 } else { 115 vFile.WriteString(VIRTUAL_TAB_FILE_EXT) 116 } 117 vfname := vFile.String() 118 return vfname 119 } 120 121 func CreateVirtTableBaseDirs(vTableBaseDir string, vTableMappingsDir string, 122 vTableTemplatesDir string, vTableAliasesDir string) error { 123 124 err := os.MkdirAll(vTableBaseDir, 0764) 125 if err != nil { 126 log.Errorf("createVirtTableBaseDir: failed to create vTableBaseDir=%v, err=%v", vTableBaseDir, err) 127 return err 128 } 129 130 err = os.MkdirAll(vTableMappingsDir, 0764) 131 if err != nil { 132 log.Errorf("createVirtTableBaseDir: failed to create vTableMappingsDir=%v, err=%v", vTableMappingsDir, err) 133 return err 134 } 135 136 err = os.MkdirAll(vTableTemplatesDir, 0764) 137 if err != nil { 138 log.Errorf("createVirtTableBaseDir: failed to create vTableTemplatesDir=%v, err=%v", vTableTemplatesDir, err) 139 return err 140 } 141 142 err = os.MkdirAll(vTableAliasesDir, 0764) 143 if err != nil { 144 log.Errorf("createVirtTableBaseDir: failed to create vTableAliasesDir=%v, err=%v", vTableAliasesDir, err) 145 return err 146 } 147 148 return nil 149 } 150 151 func addVirtualTableHelper(tname *string, orgid uint64) error { 152 var tableExists bool 153 globalTableAccessLock.RLock() 154 _, tableExists = allVirtualTables[orgid][*tname] 155 globalTableAccessLock.RUnlock() 156 if tableExists { 157 return nil 158 } 159 160 globalTableAccessLock.Lock() 161 if _, orgExists := allVirtualTables[orgid]; !orgExists { 162 allVirtualTables[orgid] = make(map[string]bool) 163 } 164 allVirtualTables[orgid][*tname] = true 165 globalTableAccessLock.Unlock() 166 167 vTableFileName := getVirtualTableFileName(orgid) 168 fd, err := os.OpenFile(vTableFileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) 169 if err != nil { 170 log.Errorf("AddVirtualTable: Failed to open virtual tablename=%v, in file=%v, err=%v", *tname, vTableFileName, err) 171 return err 172 } 173 defer fd.Close() 174 if _, err := fd.WriteString(*tname); err != nil { 175 log.Errorf("AddVirtualTable: Failed to write virtual tablename=%v, in file=%v, err=%v", *tname, vTableFileName, err) 176 177 return err 178 } 179 if _, err := fd.WriteString("\n"); err != nil { 180 log.Errorf("AddVirtualTable: Failed to write \n to virtual tablename=%v, in file=%v, err=%v", *tname, vTableFileName, err) 181 return err 182 } 183 if err = fd.Sync(); err != nil { 184 log.Errorf("AddVirtualTable: Failed to sync virtual tablename=%v, in file=%v, err=%v", *tname, vTableFileName, err) 185 return err 186 } 187 return nil 188 } 189 190 func AddVirtualTable(tname *string, orgid uint64) error { 191 192 vTableRawFileAccessLock.Lock() 193 err := addVirtualTableHelper(tname, orgid) 194 vTableRawFileAccessLock.Unlock() 195 if err != nil { 196 log.Errorf("AddVirtualTable: Error in adding virtual table to the file!. Err: %v", err) 197 return err 198 } 199 return nil 200 } 201 202 func IsVirtualTablePresent(tname *string, orgid uint64) bool { 203 vtables, err := GetVirtualTableNames(orgid) 204 if err != nil { 205 log.Errorf("Could not get virtual tables for orgid %v. Err: %v", orgid, err) 206 return false 207 } 208 for vtable := range vtables { 209 if vtable == *tname { 210 return true 211 } 212 } 213 return false 214 } 215 216 func AddVirtualTableAndMapping(tname *string, mapping *string, orgid uint64) error { 217 218 //todo for dupe entries, write a goroutine that wakes up once per day (random time) and reads the 219 // central place of virtualtablenames.txt and de-dupes the table names by creating a lock 220 221 err := AddVirtualTable(tname, orgid) 222 if err != nil { 223 return err 224 } 225 226 return AddMapping(tname, mapping, orgid) 227 228 } 229 230 func AddMapping(tname *string, mapping *string, orgid uint64) error { 231 var sb1 strings.Builder 232 sb1.WriteString(VTableMappingsDir) 233 if orgid != 0 { 234 sb1.WriteString(strconv.FormatUint(orgid, 10)) 235 sb1.WriteString("/") 236 } 237 sb1.WriteString(*tname) 238 sb1.WriteString(".json") 239 240 fname := sb1.String() 241 242 fd, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0644) 243 if err != nil { 244 log.Errorf("AddMapping: Failed to open mappings file tablename=%v, in file=%v, err=%v", *tname, fname, err) 245 return err 246 } 247 if _, err := fd.WriteString(*mapping); err != nil { 248 log.Errorf("AddMapping: Failed to write mappings file tablename=%v, in file=%v, err=%v", *tname, fname, err) 249 return err 250 } 251 if err = fd.Sync(); err != nil { 252 log.Errorf("AddMapping: Failed to sync mappings file tablename=%v, in file=%v, err=%v", *tname, fname, err) 253 return err 254 } 255 fd.Close() 256 return nil 257 } 258 259 func GetVirtualTableNames(orgid uint64) (map[string]bool, error) { 260 vTableFileName := getVirtualTableFileName(orgid) 261 return getVirtualTableNamesHelper(vTableFileName) 262 } 263 264 func getVirtualTableNamesHelper(fileName string) (map[string]bool, error) { 265 var result = make(map[string]bool) 266 fd, err := os.OpenFile(fileName, os.O_RDONLY, 0644) 267 if err != nil { 268 if errors.Is(err, os.ErrNotExist) { 269 return result, nil 270 } 271 log.Errorf("GetVirtualTableNames: Failed to open file=%v, err=%v", fileName, err) 272 return nil, err 273 } 274 275 defer fd.Close() 276 277 scanner := bufio.NewScanner(fd) 278 for scanner.Scan() { 279 rawbytes := scanner.Bytes() 280 result[string(rawbytes)] = true 281 } 282 return result, nil 283 } 284 285 func AddAliases(indexName string, aliases []string, orgid uint64) error { 286 287 if indexName == "" { 288 log.Errorf("AddAliases: indexName was null") 289 return errors.New("indexName was null") 290 } 291 292 alLen := len(aliases) 293 if alLen == 0 { 294 log.Errorf("AddAliases: len of aliases was 0") 295 return errors.New("len of aliases was 0") 296 } 297 298 currentAliases, err := GetAliases(indexName, orgid) 299 if err != nil { 300 log.Errorf("AddAliases: GetAliases returned err=%v", err) 301 return err 302 } 303 304 log.Infof("AddAliases: idxname=%v, existing aliases=[%v], newaliases=[%v]", indexName, currentAliases, aliases) 305 306 for i := 0; i < alLen; i++ { 307 currentAliases[aliases[i]] = true 308 } 309 310 err = writeAliasFile(&indexName, currentAliases, orgid) 311 if err != nil { 312 return err 313 } 314 315 for key := range currentAliases { 316 putAliasToIndexInMem(key, indexName, orgid) 317 } 318 319 return nil 320 } 321 322 func GetAllAliasesAsMapArray(orgid uint64) (map[string][]string, error) { 323 retVal := make(map[string][]string) 324 325 if _, ok := aliasToIndexNames[orgid]; ok { 326 for alias, indexNames := range aliasToIndexNames[orgid] { 327 allIdxNames := []string{} 328 for idxName := range indexNames { 329 allIdxNames = append(allIdxNames, idxName) 330 } 331 retVal[alias] = allIdxNames 332 } 333 } 334 335 return retVal, nil 336 } 337 338 func GetAliasesAsArray(indexName string, orgid uint64) ([]string, error) { 339 340 retVal := []string{} 341 342 aliasNames, err := GetAliases(indexName, orgid) 343 if err != nil { 344 return retVal, err 345 } 346 347 for aliasName := range aliasNames { 348 retVal = append(retVal, aliasName) 349 } 350 return retVal, nil 351 } 352 353 func GetAliases(indexName string, orgid uint64) (map[string]bool, error) { 354 355 var sb1 strings.Builder 356 sb1.WriteString(VTableAliasesDir) 357 if orgid != 0 { 358 sb1.WriteString(strconv.FormatUint(orgid, 10)) 359 sb1.WriteString("/") 360 } 361 sb1.WriteString(indexName) 362 sb1.WriteString(".json") 363 364 fullname := sb1.String() 365 366 retval := map[string]bool{} 367 rdata, err := os.ReadFile(fullname) 368 if err != nil { 369 if errors.Is(err, os.ErrNotExist) { 370 return retval, nil 371 } 372 log.Errorf("GetAliases: Failed to readfile fullname=%v, err=%v", fullname, err) 373 return retval, err 374 } 375 376 err = json.Unmarshal(rdata, &retval) 377 if err != nil { 378 log.Errorf("GetAliases: Failed to unmarshall fullname=%v, err=%v", fullname, err) 379 return retval, err 380 } 381 382 return retval, nil 383 384 } 385 386 func writeAliasFile(indexName *string, allnames map[string]bool, orgid uint64) error { 387 388 var sb1 strings.Builder 389 sb1.WriteString(VTableAliasesDir) 390 if orgid != 0 { 391 sb1.WriteString(strconv.FormatUint(orgid, 10)) 392 sb1.WriteString("/") 393 } 394 sb1.WriteString(*indexName) 395 sb1.WriteString(".json") 396 397 fullname := sb1.String() 398 399 jdata, err := json.Marshal(&allnames) 400 if err != nil { 401 log.Errorf("writeAliasFile: Failed to marshall fullname=%v, err=%v", fullname, err) 402 return err 403 } 404 405 err = os.WriteFile(fullname, jdata, 0644) 406 if err != nil { 407 log.Errorf("writeAliasFile: Failed to writefile fullname=%v, err=%v", fullname, err) 408 return err 409 } 410 411 return nil 412 } 413 414 func initializeAliasToIndexMap() error { 415 dirs, err := os.ReadDir(VTableAliasesDir) 416 if err != nil { 417 log.Errorf("initializeAliasToIndexMap: Failed to readdir vTableAliasesDir=%v, err=%v", VTableAliasesDir, err) 418 return err 419 } 420 421 for _, dir := range dirs { 422 if dir.IsDir() { 423 orgid := dir.Name() 424 orgIdNumber, _ := strconv.ParseUint(orgid, 10, 64) 425 files, err := os.ReadDir(VTableAliasesDir + dir.Name()) 426 if err != nil { 427 log.Errorf("initializeAliasToIndexMap: Failed to readdir for org =%v, err=%v", orgid, err) 428 return err 429 } 430 for _, f := range files { 431 var sb strings.Builder 432 sb.WriteString(VTableAliasesDir) 433 fname := f.Name() 434 435 if strings.HasSuffix(fname, ".json") { 436 indexName := strings.TrimSuffix(fname, ".json") 437 aliasNames, err := GetAliases(indexName, orgIdNumber) 438 if err != nil { 439 log.Errorf("initializeAliasToIndexMap: Failed to getAllAliasInIndexFile fname=%v, err=%v", fname, err) 440 return err 441 } 442 443 for aliasName := range aliasNames { 444 putAliasToIndexInMem(aliasName, indexName, orgIdNumber) 445 } 446 } 447 } 448 } 449 } 450 return nil 451 } 452 453 func putAliasToIndexInMem(aliasName string, indexName string, orgid uint64) { 454 455 if aliasName == "" { 456 log.Errorf("putAliasToIndexInMem: aliasName was empty") 457 return 458 } 459 460 if indexName == "" { 461 log.Errorf("putAliasToIndexInMem: indexName was empty") 462 return 463 } 464 465 if _, ok := aliasToIndexNames[orgid]; !ok { 466 aliasToIndexNames[orgid] = make(map[string]map[string]bool) 467 } 468 if _, pres := aliasToIndexNames[orgid][aliasName]; !pres { 469 aliasToIndexNames[orgid][aliasName] = make(map[string]bool) 470 } 471 472 aliasToIndexNames[orgid][aliasName][indexName] = true 473 } 474 475 func FlushAliasMapToFile() error { 476 log.Warnf("Flushing alias map to file on exit") 477 for orgid := range aliasToIndexNames { 478 for alias, indexNames := range aliasToIndexNames[orgid] { 479 err := writeAliasFile(&alias, indexNames, orgid) 480 if err != nil { 481 log.Errorf("Failed to save alias map! %+v", err) 482 } 483 } 484 } 485 return nil 486 } 487 488 func GetIndexNameFromAlias(aliasName string, orgid uint64) (string, error) { 489 if aliasName == "" { 490 log.Errorf("getIndexNameFromAlias: aliasName was empty") 491 return "", errors.New("getIndexNameFromAlias: aliasName was empty") 492 } 493 494 // log.Infof("GetIndexNameFromAlias: aliasName=%v, aliasToIndexNames=%v", aliasName, aliasToIndexNames) 495 496 if _, pres := aliasToIndexNames[orgid][aliasName]; pres { 497 for key := range aliasToIndexNames[orgid][aliasName] { 498 return key, nil 499 } 500 } 501 502 return "", errors.New("not found") 503 } 504 505 func IsAlias(nameToCheck string, orgid uint64) (bool, string) { 506 507 if valMap, ok := aliasToIndexNames[orgid][nameToCheck]; ok { 508 for indexName := range valMap { 509 return true, indexName 510 } 511 } 512 513 return false, "" 514 } 515 516 func RemoveAliases(indexName string, aliases []string, orgid uint64) error { 517 518 if indexName == "" { 519 log.Errorf("RemoveAliases: indexName was null") 520 return errors.New("indexName was null") 521 } 522 523 alLen := len(aliases) 524 if alLen == 0 { 525 log.Errorf("RemoveAliases: len of aliases was 0") 526 return errors.New("len of aliases was 0") 527 } 528 529 currentAliases, err := GetAliases(indexName, orgid) 530 if err != nil { 531 log.Errorf("RemoveAliases: GetAliases returned err=%v", err) 532 return err 533 } 534 535 log.Infof("RemoveAliases: idxname=%v, existing aliases=[%v], aliasesToRemove=[%v]", indexName, currentAliases, aliases) 536 537 for i := 0; i < alLen; i++ { 538 delete(currentAliases, aliases[i]) 539 delete(aliasToIndexNames[orgid][aliases[i]], indexName) 540 } 541 542 if len(currentAliases) == 0 { 543 return removeAliasFile(&indexName, orgid) 544 } 545 546 err = writeAliasFile(&indexName, currentAliases, orgid) 547 if err != nil { 548 return err 549 } 550 return nil 551 } 552 553 func removeAliasFile(indexName *string, orgid uint64) error { 554 var sb1 strings.Builder 555 sb1.WriteString(VTableAliasesDir) 556 if orgid != 0 { 557 sb1.WriteString(strconv.FormatUint(orgid, 10)) 558 sb1.WriteString("/") 559 } 560 sb1.WriteString(*indexName) 561 sb1.WriteString(".json") 562 563 fullname := sb1.String() 564 565 err := os.Remove(fullname) 566 if err != nil { 567 log.Errorf("removeAliasFile: Failed to remove fullname=%v, err=%v", fullname, err) 568 return err 569 } 570 571 return nil 572 573 } 574 575 // returns all indexNames that the input corresponding to after expanding "*" && aliases 576 // if isElastic is false, indices containing .kibana will not be matched 577 func ExpandAndReturnIndexNames(indexNameIn string, orgid uint64, isElastic bool) []string { 578 579 finalResultsMap := make(map[string]bool) 580 581 // we don't support <remoteCluster:indexPattern>, so we will just convert this to indexPattern 582 if idx := strings.Index(indexNameIn, ":"); idx != -1 { 583 log.Infof("ExpandAndReturnIndexNames: converting *:<> to *") 584 indexNameIn = indexNameIn[idx+1:] 585 } 586 587 if indexNameIn == "*" { 588 indexNames, err := GetVirtualTableNames(orgid) 589 if err != nil { 590 log.Infof("ExpandAndReturnIndexNames: Error reading virtual table names for orgid %v: %v", orgid, err) 591 return []string{} 592 } 593 for indexName := range indexNames { 594 if isIndexExcluded(indexName) { 595 continue 596 } 597 if !isElastic && strings.Contains(indexName, ".kibana") { 598 continue 599 } 600 601 finalResultsMap[indexName] = true 602 } 603 } else { 604 indexNames := strings.Split(indexNameIn, ",") 605 for _, indexName := range indexNames { 606 if strings.Contains(indexName, "*") { 607 if isIndexExcluded(indexName) { 608 continue 609 } 610 regexStr := "^" + strings.ReplaceAll(indexName, "*", `.*`) + "$" 611 indexRegExp, err := regexp.Compile(regexStr) 612 if err != nil { 613 log.Infof("ExpandAndReturnIndexNames: Error compiling match: %v", err) 614 return []string{} 615 } 616 // check all aliases for matches 617 // TODO: what to do for alias when orgid != 0 618 for alias, indexMap := range aliasToIndexNames[orgid] { 619 if indexRegExp.Match([]byte(alias)) { 620 for index := range indexMap { 621 finalResultsMap[index] = true 622 } 623 } 624 } 625 // check all indexName matches 626 indexNamesFromFile, _ := GetVirtualTableNames(orgid) 627 for indexNameFromFile := range indexNamesFromFile { 628 if indexRegExp.Match([]byte(indexNameFromFile)) { 629 finalResultsMap[indexNameFromFile] = true 630 } 631 } 632 } else { 633 // check if the indexnameIn is an alias if no wildcard 634 // TODO: what to do for alias when orgid != 0 635 if indexMap, pres := aliasToIndexNames[orgid][indexName]; pres { 636 for index := range indexMap { 637 finalResultsMap[index] = true 638 } 639 } else { 640 finalResultsMap[indexName] = true 641 } 642 } 643 } 644 } 645 646 // check if indices found 647 indexCount := len(finalResultsMap) 648 649 // if there are no entries in the results map, return the index as is 650 if indexCount == 0 { 651 if isIndexExcluded(indexNameIn) { 652 return []string{} 653 } 654 finalResults := []string{indexNameIn} 655 return finalResults 656 } else { 657 finalResults := make([]string, indexCount) 658 i := 0 659 for indexName := range finalResultsMap { 660 finalResults[i] = indexName 661 i++ 662 } 663 finalResults = finalResults[:i] 664 sort.Strings(finalResults) 665 return finalResults 666 } 667 668 } 669 670 func isIndexExcluded(indexName string) bool { 671 for _, value := range excludedInternalIndices { 672 if strings.ReplaceAll(indexName, "*", "") == value { 673 return true 674 } 675 } 676 return false 677 } 678 679 func DeleteVirtualTable(tname *string, orgid uint64) error { 680 681 vTableFileName := getVirtualTableFileName(orgid) 682 vtableFd, err := os.OpenFile(vTableFileName, os.O_APPEND|os.O_RDONLY, 0644) 683 if err != nil { 684 log.Errorf("DeleteVirtualTable : Error opening file: %v, err: %v", vTableFileName, err) 685 return err 686 } 687 defer func() { 688 err = vtableFd.Close() 689 if err != nil { 690 log.Errorf("DeleteVirtualTable: Failed to close file name=%v, err:%v", vTableFileName, err) 691 } 692 }() 693 scanner := bufio.NewScanner(vtableFd) 694 var store string 695 for scanner.Scan() { 696 rawbytes := scanner.Bytes() 697 val := string(rawbytes) 698 if val == *tname { 699 continue 700 } 701 store += val 702 store += "\n" 703 } 704 errW := os.WriteFile(vTableFileName, []byte(store), 0644) 705 if errW != nil { 706 log.Errorf("DeleteVirtualTable : Error writing to vtableFd: %v", errW) 707 return errW 708 } 709 return nil 710 }