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 }