diff --git a/apier/v1/filter_indexes_health_it_test.go b/apier/v1/filter_indexes_health_it_test.go index c73371be0..ec6c4979f 100644 --- a/apier/v1/filter_indexes_health_it_test.go +++ b/apier/v1/filter_indexes_health_it_test.go @@ -53,8 +53,7 @@ var ( testV1FIdxGetThresholdsIndexesHealth, testV1FIdxGetResourcesIndexesHealth, testV1FIdxGetStatsIndexesHealth, - testV1FIdxGetRoutesIndexesHealth, - + testV1FIdxGetSupplierIndexesHealth, testV1FIdxGetChargersIndexesHealth, testV1FIdxGetAttributesIndexesHealth, testV1FIdxCacheClear, @@ -485,7 +484,7 @@ func testV1FIdxGetStatsIndexesHealth(t *testing.T) { } } -func testV1FIdxGetRoutesIndexesHealth(t *testing.T) { +func testV1FIdxGetSupplierIndexesHealth(t *testing.T) { // set another routes profile different than the one from tariffplan rPrf := &SupplierWithCache{ SupplierProfile: &engine.SupplierProfile{ diff --git a/engine/z_libindex_health_test.go b/engine/z_libindex_health_test.go index b32ea94bd..1056a8110 100644 --- a/engine/z_libindex_health_test.go +++ b/engine/z_libindex_health_test.go @@ -19,12 +19,13 @@ along with this program. If not, see package engine import ( - "reflect" - "testing" - "github.com/cgrates/cgrates/config" "github.com/cgrates/cgrates/utils" "github.com/cgrates/ltcache" + "reflect" + "sort" + "testing" + "time" ) func TestHealthAccountAction(t *testing.T) { @@ -355,3 +356,995 @@ func TestHealthFilterAttributes(t *testing.T) { t.Errorf("Expecting: %+v, received: %+v", utils.ToJSON(exp), utils.ToJSON(rply)) } } + +func TestHealthIndexThreshold(t *testing.T) { + Cache.Clear(nil) + cfg, err := config.NewDefaultCGRConfig() + if err != nil { + t.Error(err) + } + db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := NewDataManager(db, cfg.CacheCfg(), nil) + + // we will set this threshold but without indexing + thPrf := &ThresholdProfile{ + Tenant: "cgrates.org", + ID: "TestHealthIndexThreshold", + FilterIDs: []string{"*string:~*opts.*eventType:AccountUpdate", + "*string:~*asm.ID:1002", + "*prefix:StaticValue:AlwaysTrue", + }, + MaxHits: 1, + } + + if err := dm.SetThresholdProfile(thPrf, false); err != nil { + t.Error(err) + } + + args := &IndexHealthArgsWith3Ch{} + exp := &FilterIHReply{ + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*opts.*eventType:AccountUpdate": {"TestHealthIndexThreshold"}, + "cgrates.org:*string:~*asm.ID:1002": {"TestHealthIndexThreshold"}, + "cgrates.org:*prefix:StaticValue:AlwaysTrue": {"TestHealthIndexThreshold"}, + }, + BrokenIndexes: map[string][]string{}, + MissingFilters: map[string][]string{}, + MissingObjects: []string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheThresholdFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + indexes := map[string]utils.StringMap{ + "*prefix:req.InvalidIdx:10": { // obj exist but the index don't + "TestHealthIndexThreshold": true, + }, + "*string:*req.Destination:123": { // index is valid but the obj does not exist + "InexistingThreshold": true, + }, + } + + // we will set manually some indexes that points to an nil object or index is valid but the obj is missing + if err := dm.SetFilterIndexes(utils.CacheThresholdFilterIndexes, "cgrates.org", + indexes, true, utils.NonTransactional); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{"cgrates.org:InexistingThreshold"}, + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*opts.*eventType:AccountUpdate": {"TestHealthIndexThreshold"}, + "cgrates.org:*string:~*asm.ID:1002": {"TestHealthIndexThreshold"}, + "cgrates.org:*prefix:StaticValue:AlwaysTrue": {"TestHealthIndexThreshold"}, + }, + BrokenIndexes: map[string][]string{ + "cgrates.org:*prefix:req.InvalidIdx:10": {"TestHealthIndexThreshold"}, + }, + MissingFilters: map[string][]string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheThresholdFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + //we will use an inexisting Filter(not inline) for the same ThresholdProfile + thPrf = &ThresholdProfile{ + Tenant: "cgrates.org", + ID: "TestHealthIndexThreshold", + FilterIDs: []string{"*string:~*opts.*eventType:AccountUpdate", + "*string:~*asm.ID:1002", + "FLTR_1_DOES_NOT_EXIST", + }, + MaxHits: 1, + } + if err := dm.SetThresholdProfile(thPrf, false); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{"cgrates.org:InexistingThreshold"}, + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*opts.*eventType:AccountUpdate": {"TestHealthIndexThreshold"}, + "cgrates.org:*string:~*asm.ID:1002": {"TestHealthIndexThreshold"}, + }, + BrokenIndexes: map[string][]string{ + "cgrates.org:*prefix:req.InvalidIdx:10": {"TestHealthIndexThreshold"}, + }, + MissingFilters: map[string][]string{ + "FLTR_1_DOES_NOT_EXIST": {"TestHealthIndexThreshold"}, + }, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheThresholdFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } +} + +func TestHealthIndexCharger(t *testing.T) { + Cache.Clear(nil) + cfg, err := config.NewDefaultCGRConfig() + if err != nil { + t.Error(err) + } + db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := NewDataManager(db, cfg.CacheCfg(), nil) + + // we will set this charger but without indexing + chPrf := &ChargerProfile{ + Tenant: "cgrates.org", + ID: "Raw", + FilterIDs: []string{ + "*string:~*opts.*eventType:ChargerAccountUpdate", + "*string:~*req.*Account:1234", + "*string:~*asm.ID:1002", + "*suffix:BrokenIndex:Invalid"}, // suffix is not indexed + RunID: "raw", + AttributeIDs: []string{"*constant:*req.RequestType:*none"}, + Weight: 20, + } + if err := dm.SetChargerProfile(chPrf, false); err != nil { + t.Error(err) + } + + args := &IndexHealthArgsWith3Ch{} + exp := &FilterIHReply{ + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*opts.*eventType:ChargerAccountUpdate": {"Raw"}, + "cgrates.org:*string:~*req.*Account:1234": {"Raw"}, + "cgrates.org:*string:~*asm.ID:1002": {"Raw"}, + }, + BrokenIndexes: map[string][]string{}, + MissingFilters: map[string][]string{}, + MissingObjects: []string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheChargerFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + indexes := map[string]utils.StringMap{ + "*prefix:req.Destination:+10": { // obj exist but the index don't + "Raw": true, + }, + "*string:*req.Destination:123": { // index is valid but the obj does not exist + "InexistingCharger": true, + }, + } + + // we will set manually some indexes that points to an nil object or index is valid but the obj is missing + if err := dm.SetFilterIndexes(utils.CacheChargerFilterIndexes, "cgrates.org", + indexes, true, utils.NonTransactional); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{"cgrates.org:InexistingCharger"}, + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*opts.*eventType:ChargerAccountUpdate": {"Raw"}, + "cgrates.org:*string:~*req.*Account:1234": {"Raw"}, + "cgrates.org:*string:~*asm.ID:1002": {"Raw"}, + }, + BrokenIndexes: map[string][]string{ + "cgrates.org:*prefix:req.Destination:+10": {"Raw"}, + }, + MissingFilters: map[string][]string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheChargerFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + //we will use an inexisting Filter(not inline) for the same ChargerProfile + chPrf = &ChargerProfile{ + Tenant: "cgrates.org", + ID: "Raw", + FilterIDs: []string{ + "*string:~*opts.*eventType:ChargerAccountUpdate", + "*string:~*req.*Account:1234", + "*string:~*asm.ID:1002", + "*suffix:BrokenFilter:Invalid", + "FLTR_1_DOES_NOT_EXIST_CHRGR", + }, + RunID: "raw", + AttributeIDs: []string{"*constant:*req.RequestType:*none"}, + Weight: 20, + } + if err := dm.SetChargerProfile(chPrf, false); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{"cgrates.org:InexistingCharger"}, + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*opts.*eventType:ChargerAccountUpdate": {"Raw"}, + "cgrates.org:*string:~*req.*Account:1234": {"Raw"}, + "cgrates.org:*string:~*asm.ID:1002": {"Raw"}, + }, + BrokenIndexes: map[string][]string{ + "cgrates.org:*prefix:req.Destination:+10": {"Raw"}, + }, + MissingFilters: map[string][]string{ + "FLTR_1_DOES_NOT_EXIST_CHRGR": {"Raw"}, + }, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheChargerFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } +} + +func TestHealthIndexResources(t *testing.T) { + Cache.Clear(nil) + cfg, err := config.NewDefaultCGRConfig() + if err != nil { + t.Error(err) + } + db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := NewDataManager(db, cfg.CacheCfg(), nil) + + // we will set this resource but without indexing + rsPrf := &ResourceProfile{ + Tenant: "tenant.custom", + ID: "RES_GRP1", + FilterIDs: []string{ + "*string:~*opts.*eventType:ResourceAccountUpdate", + "*string:~*req.RequestType:*rated", + "*prefix:~*accounts.RES_GRP1.Available:10", + "*suffix:BrokenFilter:Invalid", + }, + UsageTTL: 10 * time.Microsecond, + Limit: 10, + AllocationMessage: "MessageAllocation", + Blocker: true, + Stored: true, + Weight: 20, + } + if err := dm.SetResourceProfile(rsPrf, false); err != nil { + t.Error(err) + } + + args := &IndexHealthArgsWith3Ch{} + exp := &FilterIHReply{ + MissingIndexes: map[string][]string{ + "tenant.custom:*string:~*opts.*eventType:ResourceAccountUpdate": {"RES_GRP1"}, + "tenant.custom:*string:~*req.RequestType:*rated": {"RES_GRP1"}, + "tenant.custom:*prefix:~*accounts.RES_GRP1.Available:10": {"RES_GRP1"}, + }, + BrokenIndexes: map[string][]string{}, + MissingFilters: map[string][]string{}, + MissingObjects: []string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheResourceFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + indexes := map[string]utils.StringMap{ + "*suffix:*req.Destination:+10": { // obj exist but the index don't + "RES_GRP1": true, + }, + "*string:*req.CGRID:not_an_id": { // index is valid but the obj does not exist + "InexistingResource": true, + }, + } + + // we will set manually some indexes that points to an nil object or index is valid but the obj is missing + if err := dm.SetFilterIndexes(utils.CacheResourceFilterIndexes, "tenant.custom", + indexes, true, utils.NonTransactional); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{"tenant.custom:InexistingResource"}, + MissingIndexes: map[string][]string{ + "tenant.custom:*string:~*opts.*eventType:ResourceAccountUpdate": {"RES_GRP1"}, + "tenant.custom:*string:~*req.RequestType:*rated": {"RES_GRP1"}, + "tenant.custom:*prefix:~*accounts.RES_GRP1.Available:10": {"RES_GRP1"}, + }, + BrokenIndexes: map[string][]string{ + "tenant.custom:*suffix:*req.Destination:+10": {"RES_GRP1"}, + }, + MissingFilters: map[string][]string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheResourceFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + //we will use an inexisting Filter(not inline) for the same ResourceProfile + rsPrf = &ResourceProfile{ + Tenant: "tenant.custom", + ID: "RES_GRP1", + FilterIDs: []string{ + "*string:~*opts.*eventType:ResourceAccountUpdate", + "*string:~*req.RequestType:*rated", + "*prefix:~*accounts.RES_GRP1.Available:10", + "*suffix:BrokenFilter:Invalid", // suffix will not be indexed + "FLTR_1_NOT_EXIST", + }, + UsageTTL: 10 * time.Microsecond, + Limit: 10, + AllocationMessage: "MessageAllocation", + Blocker: true, + Stored: true, + Weight: 20, + } + if err := dm.SetResourceProfile(rsPrf, false); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{"tenant.custom:InexistingResource"}, + MissingIndexes: map[string][]string{ + "tenant.custom:*string:~*opts.*eventType:ResourceAccountUpdate": {"RES_GRP1"}, + "tenant.custom:*string:~*req.RequestType:*rated": {"RES_GRP1"}, + "tenant.custom:*prefix:~*accounts.RES_GRP1.Available:10": {"RES_GRP1"}, + }, + BrokenIndexes: map[string][]string{ + "tenant.custom:*suffix:*req.Destination:+10": {"RES_GRP1"}, + }, + MissingFilters: map[string][]string{ + "FLTR_1_NOT_EXIST": {"RES_GRP1"}, + }, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheResourceFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } +} + +func TestHealthIndexStats(t *testing.T) { + Cache.Clear(nil) + cfg, err := config.NewDefaultCGRConfig() + if err != nil { + t.Error(err) + } + db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := NewDataManager(db, cfg.CacheCfg(), nil) + + // we will set this statQueue but without indexing + sqPrf := &StatQueueProfile{ + Tenant: "cgrates.org", + ID: "Stat_1", + FilterIDs: []string{ + "*string:~*opts.*apikey:sts1234", + "*string:~*req.RequestType:*postpaid", + "*prefix:~*resources.RES_GRP1.Available:10", + "*suffix:BrokenFilter:Invalid", // suffix will not be indexed + }, + Weight: 30, + QueueLength: 100, + TTL: 10 * time.Second, + MinItems: 0, + Metrics: []*MetricWithFilters{ + { + MetricID: "*tcd", + }, + { + MetricID: "*asr", + }, + { + MetricID: "*acd", + }, + }, + Blocker: true, + ThresholdIDs: []string{utils.META_NONE}, + } + if err := dm.SetStatQueueProfile(sqPrf, false); err != nil { + t.Error(err) + } + + args := &IndexHealthArgsWith3Ch{} + exp := &FilterIHReply{ + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*opts.*apikey:sts1234": {"Stat_1"}, + "cgrates.org:*string:~*req.RequestType:*postpaid": {"Stat_1"}, + "cgrates.org:*prefix:~*resources.RES_GRP1.Available:10": {"Stat_1"}, + }, + BrokenIndexes: map[string][]string{}, + MissingFilters: map[string][]string{}, + MissingObjects: []string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheStatFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + indexes := map[string]utils.StringMap{ + "*suffix:*req.Destination:+60": { // obj exist but the index don't + "Stat_1": true, + }, + "*string:*req.ExtraField:Usage": { // index is valid but the obj does not exist + "InexistingStats": true, + }, + } + + // we will set manually some indexes that points to an nil object or index is valid but the obj is missing + if err := dm.SetFilterIndexes(utils.CacheStatFilterIndexes, "cgrates.org", + indexes, true, utils.NonTransactional); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{"cgrates.org:InexistingStats"}, + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*opts.*apikey:sts1234": {"Stat_1"}, + "cgrates.org:*string:~*req.RequestType:*postpaid": {"Stat_1"}, + "cgrates.org:*prefix:~*resources.RES_GRP1.Available:10": {"Stat_1"}, + }, + BrokenIndexes: map[string][]string{ + "cgrates.org:*suffix:*req.Destination:+60": {"Stat_1"}, + }, + MissingFilters: map[string][]string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheStatFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + //we will use an inexisting Filter(not inline) for the same StatQueueProfile + sqPrf = &StatQueueProfile{ + Tenant: "cgrates.org", + ID: "Stat_1", + FilterIDs: []string{ + "*string:~*opts.*apikey:sts1234", + "*string:~*req.RequestType:*postpaid", + "*prefix:~*resources.RES_GRP1.Available:10", // *resources will not be indexing + "*suffix:BrokenFilter:Invalid", + "FLTR_1_NOT_EXIST", + }, + Weight: 30, + QueueLength: 100, + TTL: 10 * time.Second, + MinItems: 0, + Metrics: []*MetricWithFilters{ + { + MetricID: "*tcd", + }, + { + MetricID: "*asr", + }, + { + MetricID: "*acd", + }, + }, + Blocker: true, + ThresholdIDs: []string{utils.META_NONE}, + } + if err := dm.SetStatQueueProfile(sqPrf, false); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{"cgrates.org:InexistingStats"}, + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*opts.*apikey:sts1234": {"Stat_1"}, + "cgrates.org:*string:~*req.RequestType:*postpaid": {"Stat_1"}, + "cgrates.org:*prefix:~*resources.RES_GRP1.Available:10": {"Stat_1"}, + }, + BrokenIndexes: map[string][]string{ + "cgrates.org:*suffix:*req.Destination:+60": {"Stat_1"}, + }, + MissingFilters: map[string][]string{ + "FLTR_1_NOT_EXIST": {"Stat_1"}, + }, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheStatFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } +} + +func TestHealthIndexSupplier(t *testing.T) { + Cache.Clear(nil) + cfg, err := config.NewDefaultCGRConfig() + if err != nil { + t.Error(err) + } + db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := NewDataManager(db, cfg.CacheCfg(), nil) + + // we will set this routes but without indexing + rtPrf := &SupplierProfile{ + Tenant: "routes.com", + ID: "ROUTE_ACNT_1001", + FilterIDs: []string{"*string:~*opts.*apikey:rts1234", + "*string:~*req.Usage:160s", + "*string:~*stats.STATS_VENDOR_2.*acd:1m", + "*string:~*nothing.Denied:true", + "*suffix:BrokenFilter:Invalid", // suffix will not be indexes + }, + Sorting: utils.MetaLC, + SortingParameters: []string{}, + Suppliers: []*Supplier{ + { + ID: "route1", + Weight: 10, + Blocker: false, + }, + { + ID: "route2", + RatingPlanIDs: []string{"RP_1002"}, + Weight: 20, + Blocker: false, + }, + }, + Weight: 10, + } + if err := dm.SetSupplierProfile(rtPrf, false); err != nil { + t.Error(err) + } + + args := &IndexHealthArgsWith3Ch{} + exp := &FilterIHReply{ + MissingIndexes: map[string][]string{ + "routes.com:*string:~*opts.*apikey:rts1234": {"ROUTE_ACNT_1001"}, + "routes.com:*string:~*req.Usage:160s": {"ROUTE_ACNT_1001"}, + "routes.com:*string:~*nothing.Denied:true": {"ROUTE_ACNT_1001"}, + "routes.com:*string:~*stats.STATS_VENDOR_2.*acd:1m": {"ROUTE_ACNT_1001"}, + }, + BrokenIndexes: map[string][]string{}, + MissingFilters: map[string][]string{}, + MissingObjects: []string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheSupplierFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + indexes := map[string]utils.StringMap{ + "*string:*req.RequestType:*rated": { // obj exist but the index don't + "ROUTE_ACNT_1001": true, + }, + "*suffix:*req.Destination:+222": { + "ROUTE_ACNT_1001": true, + "ROUTE_ACNT_1002": true, + }, + "*suffix:*req.Destination:+333": { + "ROUTE_ACNT_1001": true, + "ROUTE_ACNT_1002": true, + }, + "*string:*req.ExtraField:Usage": { // index is valid but the obj does not exist + "InexistingRoute": true, + }, + } + + // we will set manually some indexes that points to an nil object or index is valid but the obj is missing + if err := dm.SetFilterIndexes(utils.CacheSupplierFilterIndexes, "routes.com", + indexes, true, utils.NonTransactional); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{ + "routes.com:InexistingRoute", + "routes.com:ROUTE_ACNT_1002", + }, + MissingIndexes: map[string][]string{ + "routes.com:*string:~*opts.*apikey:rts1234": {"ROUTE_ACNT_1001"}, + "routes.com:*string:~*req.Usage:160s": {"ROUTE_ACNT_1001"}, + "routes.com:*string:~*nothing.Denied:true": {"ROUTE_ACNT_1001"}, + "routes.com:*string:~*stats.STATS_VENDOR_2.*acd:1m": {"ROUTE_ACNT_1001"}, + }, + BrokenIndexes: map[string][]string{ + "routes.com:*suffix:*req.Destination:+222": {"ROUTE_ACNT_1001"}, + "routes.com:*suffix:*req.Destination:+333": {"ROUTE_ACNT_1001"}, + "routes.com:*string:*req.RequestType:*rated": {"ROUTE_ACNT_1001"}, + }, + MissingFilters: map[string][]string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheSupplierFilterIndexes); err != nil { + t.Error(err) + } else { + sort.Strings(rply.MissingObjects) + sort.Strings(exp.MissingObjects) + if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + } + + //we will use an inexisting Filter(not inline) for the same RouteProfile + rtPrf = &SupplierProfile{ + Tenant: "routes.com", + ID: "ROUTE_ACNT_1001", + FilterIDs: []string{"*string:~*opts.*apikey:rts1234", + "*string:~*req.Usage:160s", + "*string:~*stats.STATS_VENDOR_2.*acd:1m", // *stats will not be indexing + "*string:~*nothing.Denied:true", + "*suffix:BrokenFilter:Invalid", + "FLTR_1_NOT_EXIST", + }, + Sorting: utils.MetaLC, + SortingParameters: []string{}, + Suppliers: []*Supplier{ + { + ID: "route1", + Weight: 10, + Blocker: false, + }, + { + ID: "route2", + RatingPlanIDs: []string{"RP_1002"}, + Weight: 20, + Blocker: false, + }, + }, + Weight: 10, + } + if err := dm.SetSupplierProfile(rtPrf, false); err != nil { + t.Error(err) + } + exp = &FilterIHReply{ + MissingObjects: []string{ + "routes.com:InexistingRoute", + "routes.com:ROUTE_ACNT_1002", + }, + MissingIndexes: map[string][]string{ + "routes.com:*string:~*opts.*apikey:rts1234": {"ROUTE_ACNT_1001"}, + "routes.com:*string:~*req.Usage:160s": {"ROUTE_ACNT_1001"}, + "routes.com:*string:~*nothing.Denied:true": {"ROUTE_ACNT_1001"}, + "routes.com:*string:~*stats.STATS_VENDOR_2.*acd:1m": {"ROUTE_ACNT_1001"}, + }, + BrokenIndexes: map[string][]string{ + "routes.com:*suffix:*req.Destination:+222": {"ROUTE_ACNT_1001"}, + "routes.com:*suffix:*req.Destination:+333": {"ROUTE_ACNT_1001"}, + "routes.com:*string:*req.RequestType:*rated": {"ROUTE_ACNT_1001"}, + }, + MissingFilters: map[string][]string{ + "FLTR_1_NOT_EXIST": {"ROUTE_ACNT_1001"}, + }, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheSupplierFilterIndexes); err != nil { + t.Error(err) + } else { + sort.Strings(rply.MissingObjects) + sort.Strings(exp.MissingObjects) + if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + } +} + +func TestHealthIndexDispatchers(t *testing.T) { + Cache.Clear(nil) + cfg, err := config.NewDefaultCGRConfig() + if err != nil { + t.Error(err) + } + db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := NewDataManager(db, cfg.CacheCfg(), nil) + + // we will set this dispatcherProfile but without indexing + dspPrf := &DispatcherProfile{ + Tenant: "cgrates.org", + ID: "Dsp1", + Subsystems: []string{utils.META_ANY, utils.MetaSessionS}, + FilterIDs: []string{ + "*string:~*opts.*apikey:dps1234;dsp9876", + "*string:~*req.AnswerTime:2013-11-07T08:42:26Z", + "*string:~*libphonenumber.<~*req.Destination>:+443234566", + "*suffix:BrokenFilter:Invalid", // suffix will not be indexing + }, + Strategy: utils.MetaRandom, + Weight: 20, + Hosts: DispatcherHostProfiles{ + { + ID: "ALL", + }, + }, + } + if err := dm.SetDispatcherProfile(dspPrf, false); err != nil { + t.Error(err) + } + + args := &IndexHealthArgsWith3Ch{} + exp := &FilterIHReply{ + MissingIndexes: map[string][]string{ + "cgrates.org:*any:*string:~*opts.*apikey:dps1234": {"Dsp1"}, + "cgrates.org:*any:*string:~*opts.*apikey:dsp9876": {"Dsp1"}, + "cgrates.org:*any:*string:~*req.AnswerTime:2013-11-07T08:42:26Z": {"Dsp1"}, + "cgrates.org:*any:*string:~*libphonenumber.<~*req.Destination>:+443234566": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*opts.*apikey:dps1234": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*opts.*apikey:dsp9876": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*req.AnswerTime:2013-11-07T08:42:26Z": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*libphonenumber.<~*req.Destination>:+443234566": {"Dsp1"}, + }, + BrokenIndexes: map[string][]string{}, + MissingFilters: map[string][]string{}, + MissingObjects: []string{}, + } + + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheDispatcherFilterIndexes); err != nil { + t.Error(err) + } else if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + + + // we will set manually some indexes that points to an nil object or index is valid but the obj is missing + indexes := map[string]utils.StringMap{ + "*string:*req.RequestType:*rated": { // obj exist but the index don't + "Dsp1": true, + "Dsp2": true, + }, + "*suffix:*opts.Destination:+100": { // obj exist but the index don't + "Dsp1": true, + "Dsp2": true, + }, + "*string:*req.ExtraField:Usage": { // index is valid but the obj does not exist + "InexistingDispatcher": true, + "InexistingDispatcher2": true, + }, + } + if err := dm.SetFilterIndexes(utils.CacheDispatcherFilterIndexes, "cgrates.org", + indexes, true, utils.NonTransactional); err != nil { + t.Error(err) + } + + //get the newIdxHealth for dispatchersProfile + exp = &FilterIHReply{ + MissingIndexes: map[string][]string{ + "cgrates.org:*any:*string:~*opts.*apikey:dps1234": {"Dsp1"}, + "cgrates.org:*any:*string:~*opts.*apikey:dsp9876": {"Dsp1"}, + "cgrates.org:*any:*string:~*req.AnswerTime:2013-11-07T08:42:26Z": {"Dsp1"}, + "cgrates.org:*any:*string:~*libphonenumber.<~*req.Destination>:+443234566": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*opts.*apikey:dps1234": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*opts.*apikey:dsp9876": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*req.AnswerTime:2013-11-07T08:42:26Z": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*libphonenumber.<~*req.Destination>:+443234566": {"Dsp1"}, + }, + BrokenIndexes: map[string][]string{ + "cgrates.org:*suffix:*opts.Destination:+100": {"Dsp1"}, + "cgrates.org:*string:*req.RequestType:*rated": {"Dsp1"}, + }, + MissingFilters: map[string][]string{}, + MissingObjects: []string{ + "cgrates.org:Dsp2", + "cgrates.org:InexistingDispatcher", + "cgrates.org:InexistingDispatcher2", + }, + } + + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheDispatcherFilterIndexes); err != nil { + t.Error(err) + } else { + sort.Strings(rply.MissingObjects) + sort.Strings(exp.MissingObjects) + if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + } + + //we will use an inexisting Filter(not inline) for the same DispatcherProfile + dspPrf = &DispatcherProfile{ + Tenant: "cgrates.org", + ID: "Dsp1", + Subsystems: []string{utils.META_ANY, utils.MetaSessionS}, + FilterIDs: []string{ + "*string:~*opts.*apikey:dps1234;dsp9876", + "*string:~*req.AnswerTime:2013-11-07T08:42:26Z", + "*string:~*libphonenumber.<~*req.Destination>:+443234566", + "*suffix:BrokenFilter:Invalid", // suffix will not be indexing + "FLTR_1_NOT_EXIST", + }, + Strategy: utils.MetaRandom, + Weight: 20, + Hosts: DispatcherHostProfiles{ + { + ID: "ALL", + }, + }, + } + if err := dm.SetDispatcherProfile(dspPrf, false); err != nil { + t.Error(err) + } + + //get the newIdxHealth for dispatchersProfile + exp = &FilterIHReply{ + MissingIndexes: map[string][]string{ + "cgrates.org:*any:*string:~*opts.*apikey:dps1234": {"Dsp1"}, + "cgrates.org:*any:*string:~*opts.*apikey:dsp9876": {"Dsp1"}, + "cgrates.org:*any:*string:~*req.AnswerTime:2013-11-07T08:42:26Z": {"Dsp1"}, + "cgrates.org:*any:*string:~*libphonenumber.<~*req.Destination>:+443234566": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*opts.*apikey:dps1234": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*opts.*apikey:dsp9876": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*req.AnswerTime:2013-11-07T08:42:26Z": {"Dsp1"}, + "cgrates.org:*sessions:*string:~*libphonenumber.<~*req.Destination>:+443234566": {"Dsp1"}, + }, + BrokenIndexes: map[string][]string{ + "cgrates.org:*suffix:*opts.Destination:+100": {"Dsp1"}, + "cgrates.org:*string:*req.RequestType:*rated": {"Dsp1"}, + }, + MissingFilters: map[string][]string{ + "FLTR_1_NOT_EXIST": {"Dsp1"}, + }, + MissingObjects: []string{ + "cgrates.org:Dsp2", + "cgrates.org:InexistingDispatcher", + "cgrates.org:InexistingDispatcher2", + }, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheDispatcherFilterIndexes); err != nil { + t.Error(err) + } else { + sort.Strings(rply.MissingObjects) + sort.Strings(exp.MissingObjects) + if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + } +} + +func TestIndexHealthMultipleProfiles(t *testing.T) { + Cache.Clear(nil) + cfg, err := config.NewDefaultCGRConfig() + if err != nil { + t.Error(err) + } + db := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := NewDataManager(db, cfg.CacheCfg(), nil) + + // we will set this multiple chargers but without indexing(same and different indexes) + chPrf1 := &ChargerProfile{ + Tenant: "cgrates.org", + ID: "Raw", + FilterIDs: []string{ + "*string:~*opts.*eventType:ChargerAccountUpdate", + "*string:~*req.Account:1234", + "*string:~*asm.ID:1002", + "*suffix:BrokenFilter:Invalid"}, // suffix will not be indexing + RunID: "raw", + AttributeIDs: []string{"*constant:*req.RequestType:*none"}, + Weight: 20, + } + chPrf2 := &ChargerProfile{ + Tenant: "cgrates.org", + ID: "Default", + FilterIDs: []string{ + "*string:~*opts.*eventType:ChargerAccountUpdate", + "*prefix:~*req.Destination:+2234;~*req.CGRID", + "*prefix:~*req.Usage:10", + "*string:~*req.Account:1234", + "FLTR_1_NOT_EXIST2", + }, + RunID: "*default", + Weight: 10, + } + chPrf3 := &ChargerProfile{ + Tenant: "cgrates.org", + ID: "Call_Attr1", + FilterIDs: []string{ + "*string:~*req.Account:1234", + "*string:*broken:index", + "FLTR_1_NOT_EXIST", + "FLTR_1_NOT_EXIST2", + }, + AttributeIDs: []string{"Attr1"}, + RunID: "*attribute", + Weight: 0, + } + if err := dm.SetChargerProfile(chPrf1, false); err != nil { + t.Error(err) + } + if err := dm.SetChargerProfile(chPrf2, false); err != nil { + t.Error(err) + } + if err := dm.SetChargerProfile(chPrf3, false); err != nil { + t.Error(err) + } + + // check the indexes health + args := &IndexHealthArgsWith3Ch{} + exp := &FilterIHReply{ + MissingIndexes: map[string][]string{ + "cgrates.org:*string:~*asm.ID:1002": {"Raw"}, + "cgrates.org:*string:~*opts.*eventType:ChargerAccountUpdate": {"Raw", "Default"}, + "cgrates.org:*string:~*req.Account:1234": {"Raw", "Default", "Call_Attr1"}, + "cgrates.org:*prefix:~*req.Destination:+2234": {"Default"}, + "cgrates.org:*prefix:~*req.Destination:~*req.CGRID": {"Default"}, + "cgrates.org:*prefix:~*req.Usage:10":{"Default"}, + "cgrates.org:*string:*broken:index": {"Call_Attr1"}, + }, + BrokenIndexes: map[string][]string{}, + MissingFilters: map[string][]string{ + "FLTR_1_NOT_EXIST2": {"Default", "Call_Attr1"}, + "FLTR_1_NOT_EXIST": {"Call_Attr1"}, + }, + MissingObjects: []string{}, + } + if rply, err := GetFltrIdxHealth(dm, + ltcache.NewCache(args.FilterCacheLimit, args.FilterCacheTTL, args.FilterCacheStaticTTL, nil), + ltcache.NewCache(args.IndexCacheLimit, args.IndexCacheTTL, args.IndexCacheStaticTTL, nil), + ltcache.NewCache(args.ObjectCacheLimit, args.ObjectCacheTTL, args.ObjectCacheStaticTTL, nil), + utils.CacheChargerFilterIndexes); err != nil { + t.Error(err) + } else { + sort.Strings(exp.MissingIndexes["cgrates.org:*string:*opts.*eventType:ChargerAccountUpdate"]) + sort.Strings(exp.MissingIndexes["cgrates.org:*string:*req.Account:1234"]) + sort.Strings(exp.MissingFilters["cgrates.org:FLTR_1_NOT_EXIST2"]) + sort.Strings(rply.MissingIndexes["cgrates.org:*string:*opts.*eventType:ChargerAccountUpdate"]) + sort.Strings(rply.MissingIndexes["cgrates.org:*string:*req.Account:1234"]) + sort.Strings(rply.MissingFilters["cgrates.org:FLTR_1_NOT_EXIST2"]) + if !reflect.DeepEqual(exp, rply) { + t.Errorf("Expected %+v, received %+v", utils.ToJSON(exp), utils.ToJSON(rply)) + } + } +} diff --git a/services/sessions.go b/services/sessions.go index e7461dc7a..656a91d1d 100644 --- a/services/sessions.go +++ b/services/sessions.go @@ -59,12 +59,12 @@ type SessionService struct { rpcv1 *v1.SessionSv1 connChan chan rpcclient.ClientConnector - // in order to stop the bircp server if necesary + // in order to stop the birpc server if necessary bircpEnabled bool connMgr *engine.ConnManager } -// Start should handle the sercive start +// Start should handle the service start func (smg *SessionService) Start() (err error) { if smg.IsRunning() { return utils.ErrServiceAlreadyRunning @@ -79,7 +79,7 @@ func (smg *SessionService) Start() (err error) { defer smg.Unlock() smg.sm = sessions.NewSessionS(smg.cfg, datadb, smg.connMgr) - //start sync session in a separate gorutine + // start sync session in a separate goroutine go func(sm *sessions.SessionS) { if err = sm.ListenAndServe(smg.exitChan); err != nil { utils.Logger.Err(fmt.Sprintf("<%s> error: %s!", utils.SessionS, err)) @@ -104,7 +104,7 @@ func (smg *SessionService) Start() (err error) { for method, handler := range smg.rpcv1.Handlers() { smg.server.BiRPCRegisterName(method, handler) } - // run this in it's own gorutine + // run this in it's own goroutine go func() { if err := smg.server.ServeBiJSON(smg.cfg.SessionSCfg().ListenBijson, smg.sm.OnBiJSONConnect, smg.sm.OnBiJSONDisconnect); err != nil { utils.Logger.Err(fmt.Sprintf("<%s> serve BiRPC error: %s!", utils.SessionS, err))