github.com/netdata/go.d.plugin@v0.58.1/agent/discovery/sd/pipeline/pipeline_test.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package pipeline
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/netdata/go.d.plugin/agent/confgroup"
    13  	"github.com/netdata/go.d.plugin/agent/discovery/sd/model"
    14  
    15  	"github.com/ilyam8/hashstructure"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  	"gopkg.in/yaml.v2"
    19  )
    20  
    21  func TestNew(t *testing.T) {
    22  	tests := map[string]struct {
    23  		config  string
    24  		wantErr bool
    25  	}{
    26  		"fails when config unset": {
    27  			wantErr: true,
    28  			config:  "",
    29  		},
    30  	}
    31  
    32  	for name, test := range tests {
    33  		t.Run(name, func(t *testing.T) {
    34  
    35  			var cfg Config
    36  			err := yaml.Unmarshal([]byte(test.config), &cfg)
    37  			require.Nilf(t, err, "cfg unmarshal")
    38  
    39  			_, err = New(cfg)
    40  
    41  			if test.wantErr {
    42  				assert.Error(t, err)
    43  			} else {
    44  				assert.NoError(t, err)
    45  			}
    46  		})
    47  	}
    48  }
    49  
    50  func TestPipeline_Run(t *testing.T) {
    51  	const config = `
    52  classify:
    53    - selector: "rule1"
    54      tags: "foo1"
    55      match:
    56        - tags: "bar1"
    57          expr: '{{ glob .Name "mock*1*" }}'
    58        - tags: "bar2"
    59          expr: '{{ glob .Name "mock*2*" }}'
    60  compose:
    61    - selector: "foo1"
    62      config:
    63        - selector: "bar1"
    64          template: |
    65            name: {{ .Name }}-foobar1
    66        - selector: "bar2"
    67          template: |
    68            name: {{ .Name }}-foobar2
    69  `
    70  	tests := map[string]discoverySim{
    71  		"new group with no targets": {
    72  			config: config,
    73  			discoverers: []model.Discoverer{
    74  				newMockDiscoverer("",
    75  					newMockTargetGroup("test"),
    76  				),
    77  			},
    78  			wantClassifyCalls: 0,
    79  			wantComposeCalls:  0,
    80  			wantConfGroups:    nil,
    81  		},
    82  		"new group with targets": {
    83  			config: config,
    84  			discoverers: []model.Discoverer{
    85  				newMockDiscoverer("rule1",
    86  					newMockTargetGroup("test", "mock1", "mock2"),
    87  				),
    88  			},
    89  			wantClassifyCalls: 2,
    90  			wantComposeCalls:  2,
    91  			wantConfGroups: []*confgroup.Group{
    92  				{Source: "test", Configs: []confgroup.Config{
    93  					{
    94  						"__provider__": "mock",
    95  						"__source__":   "test",
    96  						"name":         "mock1-foobar1",
    97  					},
    98  					{
    99  						"__provider__": "mock",
   100  						"__source__":   "test",
   101  						"name":         "mock2-foobar2",
   102  					},
   103  				}},
   104  			},
   105  		},
   106  		"existing group with same targets": {
   107  			config: config,
   108  			discoverers: []model.Discoverer{
   109  				newMockDiscoverer("rule1",
   110  					newMockTargetGroup("test", "mock1", "mock2"),
   111  				),
   112  				newDelayedMockDiscoverer("rule1", 5,
   113  					newMockTargetGroup("test", "mock1", "mock2"),
   114  				),
   115  			},
   116  			wantClassifyCalls: 2,
   117  			wantComposeCalls:  2,
   118  			wantConfGroups: []*confgroup.Group{
   119  				{Source: "test", Configs: []confgroup.Config{
   120  					{
   121  						"__provider__": "mock",
   122  						"__source__":   "test",
   123  						"name":         "mock1-foobar1",
   124  					},
   125  					{
   126  						"__provider__": "mock",
   127  						"__source__":   "test",
   128  						"name":         "mock2-foobar2",
   129  					},
   130  				}},
   131  			},
   132  		},
   133  		"existing empty group that previously had targets": {
   134  			config: config,
   135  			discoverers: []model.Discoverer{
   136  				newMockDiscoverer("rule1",
   137  					newMockTargetGroup("test", "mock1", "mock2"),
   138  				),
   139  				newDelayedMockDiscoverer("rule1", 5,
   140  					newMockTargetGroup("test"),
   141  				),
   142  			},
   143  			wantClassifyCalls: 2,
   144  			wantComposeCalls:  2,
   145  			wantConfGroups: []*confgroup.Group{
   146  				{Source: "test", Configs: []confgroup.Config{
   147  					{
   148  						"__provider__": "mock",
   149  						"__source__":   "test",
   150  						"name":         "mock1-foobar1",
   151  					},
   152  					{
   153  						"__provider__": "mock",
   154  						"__source__":   "test",
   155  						"name":         "mock2-foobar2",
   156  					},
   157  				}},
   158  				{Source: "test", Configs: nil},
   159  			},
   160  		},
   161  		"existing group with old and new targets": {
   162  			config: config,
   163  			discoverers: []model.Discoverer{
   164  				newMockDiscoverer("rule1",
   165  					newMockTargetGroup("test", "mock1", "mock2"),
   166  				),
   167  				newDelayedMockDiscoverer("rule1", 5,
   168  					newMockTargetGroup("test", "mock1", "mock2", "mock11", "mock22"),
   169  				),
   170  			},
   171  			wantClassifyCalls: 4,
   172  			wantComposeCalls:  4,
   173  			wantConfGroups: []*confgroup.Group{
   174  				{Source: "test", Configs: []confgroup.Config{
   175  					{
   176  						"__provider__": "mock",
   177  						"__source__":   "test",
   178  						"name":         "mock1-foobar1",
   179  					},
   180  					{
   181  						"__provider__": "mock",
   182  						"__source__":   "test",
   183  						"name":         "mock2-foobar2",
   184  					},
   185  				}},
   186  				{Source: "test", Configs: []confgroup.Config{
   187  					{
   188  						"__provider__": "mock",
   189  						"__source__":   "test",
   190  						"name":         "mock1-foobar1",
   191  					},
   192  					{
   193  						"__provider__": "mock",
   194  						"__source__":   "test",
   195  						"name":         "mock2-foobar2",
   196  					},
   197  					{
   198  						"__provider__": "mock",
   199  						"__source__":   "test",
   200  						"name":         "mock11-foobar1",
   201  					},
   202  					{
   203  						"__provider__": "mock",
   204  						"__source__":   "test",
   205  						"name":         "mock22-foobar2",
   206  					},
   207  				}},
   208  			},
   209  		},
   210  		"existing group with new targets only": {
   211  			config: config,
   212  			discoverers: []model.Discoverer{
   213  				newMockDiscoverer("rule1",
   214  					newMockTargetGroup("test", "mock1", "mock2"),
   215  				),
   216  				newDelayedMockDiscoverer("rule1", 5,
   217  					newMockTargetGroup("test", "mock11", "mock22"),
   218  				),
   219  			},
   220  			wantClassifyCalls: 4,
   221  			wantComposeCalls:  4,
   222  			wantConfGroups: []*confgroup.Group{
   223  				{Source: "test", Configs: []confgroup.Config{
   224  					{
   225  						"__provider__": "mock",
   226  						"__source__":   "test",
   227  						"name":         "mock1-foobar1",
   228  					},
   229  					{
   230  						"__provider__": "mock",
   231  						"__source__":   "test",
   232  						"name":         "mock2-foobar2",
   233  					},
   234  				}},
   235  				{Source: "test", Configs: []confgroup.Config{
   236  					{
   237  						"__provider__": "mock",
   238  						"__source__":   "test",
   239  						"name":         "mock11-foobar1",
   240  					},
   241  					{
   242  						"__provider__": "mock",
   243  						"__source__":   "test",
   244  						"name":         "mock22-foobar2",
   245  					},
   246  				}},
   247  			},
   248  		},
   249  	}
   250  
   251  	for name, sim := range tests {
   252  		t.Run(name, func(t *testing.T) {
   253  			sim.run(t)
   254  		})
   255  	}
   256  }
   257  
   258  func newMockDiscoverer(tags string, tggs ...model.TargetGroup) *mockDiscoverer {
   259  	return &mockDiscoverer{
   260  		tags: mustParseTags(tags),
   261  		tggs: tggs,
   262  	}
   263  }
   264  
   265  func newDelayedMockDiscoverer(tags string, delay int, tggs ...model.TargetGroup) *mockDiscoverer {
   266  	return &mockDiscoverer{
   267  		tags:  mustParseTags(tags),
   268  		tggs:  tggs,
   269  		delay: time.Duration(delay) * time.Second,
   270  	}
   271  }
   272  
   273  type mockDiscoverer struct {
   274  	tggs  []model.TargetGroup
   275  	tags  model.Tags
   276  	delay time.Duration
   277  }
   278  
   279  func (md mockDiscoverer) Discover(ctx context.Context, out chan<- []model.TargetGroup) {
   280  	for _, tgg := range md.tggs {
   281  		for _, tgt := range tgg.Targets() {
   282  			tgt.Tags().Merge(md.tags)
   283  		}
   284  	}
   285  
   286  	select {
   287  	case <-ctx.Done():
   288  	case <-time.After(md.delay):
   289  		select {
   290  		case <-ctx.Done():
   291  		case out <- md.tggs:
   292  		}
   293  	}
   294  }
   295  
   296  func newMockTargetGroup(source string, targets ...string) *mockTargetGroup {
   297  	m := &mockTargetGroup{source: source}
   298  	for _, name := range targets {
   299  		m.targets = append(m.targets, &mockTarget{Name: name})
   300  	}
   301  	return m
   302  }
   303  
   304  type mockTargetGroup struct {
   305  	targets []model.Target
   306  	source  string
   307  }
   308  
   309  func (mg mockTargetGroup) Targets() []model.Target { return mg.targets }
   310  func (mg mockTargetGroup) Source() string          { return mg.source }
   311  func (mg mockTargetGroup) Provider() string        { return "mock" }
   312  
   313  func newMockTarget(name string, tags ...string) *mockTarget {
   314  	m := &mockTarget{Name: name}
   315  	v, _ := model.ParseTags(strings.Join(tags, " "))
   316  	m.Tags().Merge(v)
   317  	return m
   318  }
   319  
   320  type mockTarget struct {
   321  	model.Base
   322  	Name string
   323  }
   324  
   325  func (mt mockTarget) TUID() string { return mt.Name }
   326  func (mt mockTarget) Hash() uint64 { return mustCalcHash(mt.Name) }
   327  
   328  func mustParseTags(line string) model.Tags {
   329  	v, err := model.ParseTags(line)
   330  	if err != nil {
   331  		panic(fmt.Sprintf("mustParseTags: %v", err))
   332  	}
   333  	return v
   334  }
   335  
   336  func mustCalcHash(obj any) uint64 {
   337  	hash, err := hashstructure.Hash(obj, nil)
   338  	if err != nil {
   339  		panic(fmt.Sprintf("hash calculation: %v", err))
   340  	}
   341  	return hash
   342  }