| // Licensed to Apache Software Foundation (ASF) under one or more contributor |
| // license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright |
| // ownership. Apache Software Foundation (ASF) licenses this file to you under |
| // the Apache License, Version 2.0 (the "License"); you may |
| // not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| package validate |
| |
| import ( |
| "testing" |
| |
| "github.com/stretchr/testify/assert" |
| |
| commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1" |
| databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1" |
| ) |
| |
| func TestMeasureShardingKeyNil(t *testing.T) { |
| measure := &databasev1.Measure{ |
| Metadata: &commonv1.Metadata{ |
| Name: "test_measure", |
| Group: "test_group", |
| }, |
| Entity: &databasev1.Entity{ |
| TagNames: []string{"service_id"}, |
| }, |
| TagFamilies: []*databasev1.TagFamilySpec{ |
| { |
| Name: "default", |
| Tags: []*databasev1.TagSpec{ |
| {Name: "service_id", Type: databasev1.TagType_TAG_TYPE_STRING}, |
| }, |
| }, |
| }, |
| } |
| err := Measure(measure) |
| assert.NoError(t, err) |
| } |
| |
| func TestMeasurePassesWithNonPrefixShardingKey(t *testing.T) { |
| measure := &databasev1.Measure{ |
| Metadata: &commonv1.Metadata{Name: "endpoint_cpm", Group: "sw_metric"}, |
| Entity: &databasev1.Entity{TagNames: []string{"entity_id"}}, |
| TagFamilies: []*databasev1.TagFamilySpec{ |
| { |
| Name: "default", |
| Tags: []*databasev1.TagSpec{ |
| {Name: "entity_id", Type: databasev1.TagType_TAG_TYPE_STRING}, |
| {Name: "service_id", Type: databasev1.TagType_TAG_TYPE_STRING}, |
| }, |
| }, |
| }, |
| ShardingKey: &databasev1.ShardingKey{TagNames: []string{"service_id"}}, |
| } |
| err := Measure(measure) |
| assert.NoError(t, err, "Measure() must not reject a non-prefix sharding key") |
| } |
| |
| func TestValidateTracePipelineConfig_StagePluginsRejected(t *testing.T) { |
| // A config with sampler plugins declared only under stages[] must be rejected |
| // with a clear error — not silently accepted (which would load zero samplers |
| // and retain all traces). |
| stagePlugin := &commonv1.Plugin{ |
| Name: "lss", |
| Kind: &commonv1.Plugin_Sampler{ |
| Sampler: &commonv1.SamplerPlugin{ |
| Path: "sampler.so", |
| Symbol: "NewSampler", |
| AbiVersion: 1, |
| }, |
| }, |
| } |
| cfg := &commonv1.TracePipelineConfig{ |
| Enabled: true, |
| Stages: []*commonv1.StageRule{ |
| {Stage: "warm", Plugins: []*commonv1.Plugin{stagePlugin}}, |
| }, |
| } |
| err := validateTracePipelineConfig(cfg) |
| assert.Error(t, err, "stages[].plugins with a sampler must be rejected") |
| assert.Contains(t, err.Error(), "stages[].plugins are not supported in v1") |
| } |
| |
| func TestCheckShardingKeySubset(t *testing.T) { |
| tests := []struct { |
| name string |
| errContains string |
| entity []string |
| shardingKey []string |
| wantErr bool |
| }{ |
| { |
| name: "single entity tag — composite-id pattern, always skip", |
| entity: []string{"entity_id"}, |
| shardingKey: []string{"service_id"}, |
| wantErr: false, |
| }, |
| { |
| name: "valid subset, same order", |
| entity: []string{"service_id", "instance_id", "endpoint_id"}, |
| shardingKey: []string{"service_id", "endpoint_id"}, |
| wantErr: false, |
| }, |
| { |
| name: "valid subset, identical to entity", |
| entity: []string{"service_id", "instance_id"}, |
| shardingKey: []string{"service_id", "instance_id"}, |
| wantErr: false, |
| }, |
| { |
| name: "valid subset, single sharding key tag in multi-entity", |
| entity: []string{"service_id", "instance_id"}, |
| shardingKey: []string{"instance_id"}, |
| wantErr: false, |
| }, |
| { |
| name: "invalid — sharding key tag not in entity", |
| entity: []string{"service_id", "instance_id"}, |
| shardingKey: []string{"endpoint_id"}, |
| wantErr: true, |
| errContains: "is not present in Entity tags", |
| }, |
| { |
| name: "invalid — superset of entity", |
| entity: []string{"service_id"}, |
| shardingKey: []string{"service_id", "instance_id"}, |
| wantErr: false, // single entity tag — skip |
| }, |
| { |
| name: "invalid — superset of multi-entity", |
| entity: []string{"service_id", "instance_id"}, |
| shardingKey: []string{"service_id", "instance_id", "endpoint_id"}, |
| wantErr: true, |
| errContains: "is not present in Entity tags", |
| }, |
| { |
| name: "invalid — wrong relative order", |
| entity: []string{"service_id", "instance_id"}, |
| shardingKey: []string{"instance_id", "service_id"}, |
| wantErr: true, |
| errContains: "is not in the same relative order", |
| }, |
| { |
| name: "nil sharding key", |
| entity: []string{"service_id"}, |
| shardingKey: nil, |
| wantErr: false, |
| }, |
| { |
| name: "empty sharding key", |
| entity: []string{"service_id"}, |
| shardingKey: []string{}, |
| wantErr: false, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| measure := &databasev1.Measure{ |
| Metadata: &commonv1.Metadata{Name: "test", Group: "group"}, |
| Entity: &databasev1.Entity{TagNames: tt.entity}, |
| } |
| if tt.shardingKey != nil { |
| measure.ShardingKey = &databasev1.ShardingKey{TagNames: tt.shardingKey} |
| } |
| checkErr := CheckShardingKeySubset(measure) |
| if tt.wantErr { |
| assert.Error(t, checkErr) |
| if tt.errContains != "" { |
| assert.Contains(t, checkErr.Error(), tt.errContains) |
| } |
| } else { |
| assert.NoError(t, checkErr) |
| } |
| }) |
| } |
| } |