master
go 157 lines 3.12 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package dockersd
4
5 import (
6 "context"
7 "sort"
8 "sync"
9 "testing"
10 "time"
11
12 "github.com/netdata/netdata/go/plugins/plugin/agent/discovery/sd/model"
13
14 "github.com/docker/docker/api/types"
15 typesContainer "github.com/docker/docker/api/types/container"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18 )
19
20 type dockerCli interface {
21 addContainer(cntr types.Container)
22 removeContainer(id string)
23 }
24
25 type discoverySim struct {
26 dockerCli func(cli dockerCli, interval time.Duration)
27 wantGroups []model.TargetGroup
28 }
29
30 func (sim *discoverySim) run(t *testing.T) {
31 d, err := NewDiscoverer(Config{
32 Source: "",
33 })
34 require.NoError(t, err)
35
36 mock := newMockDockerd()
37
38 d.newDockerClient = func(addr string) (dockerClient, error) {
39 return mock, nil
40 }
41 d.listInterval = time.Millisecond * 100
42
43 seen := make(map[string]model.TargetGroup)
44 ctx, cancel := context.WithCancel(context.Background())
45 in := make(chan []model.TargetGroup)
46 var wg sync.WaitGroup
47
48 wg.Go(func() {
49 d.Discover(ctx, in)
50 })
51
52 wg.Go(func() {
53 for {
54 select {
55 case <-ctx.Done():
56 return
57 case tggs := <-in:
58 for _, tgg := range tggs {
59 seen[tgg.Source()] = tgg
60 }
61 }
62 }
63 })
64
65 done := make(chan struct{})
66 go func() {
67 defer close(done)
68 wg.Wait()
69 }()
70
71 select {
72 case <-d.started:
73 case <-time.After(time.Second * 3):
74 require.Fail(t, "discovery failed to start")
75 }
76
77 sim.dockerCli(mock, d.listInterval)
78 time.Sleep(time.Second)
79
80 cancel()
81
82 select {
83 case <-done:
84 case <-time.After(time.Second * 3):
85 require.Fail(t, "discovery hasn't finished after cancel")
86 }
87
88 var tggs []model.TargetGroup
89 for _, tgg := range seen {
90 tggs = append(tggs, tgg)
91 }
92
93 sortTargetGroups(tggs)
94 sortTargetGroups(sim.wantGroups)
95
96 wantLen, gotLen := len(sim.wantGroups), len(tggs)
97 assert.Equalf(t, wantLen, gotLen, "different len (want %d got %d)", wantLen, gotLen)
98 assert.Equal(t, sim.wantGroups, tggs)
99
100 assert.True(t, mock.negApiVerCalled, "NegotiateAPIVersion called")
101 assert.True(t, mock.closeCalled, "Close called")
102 }
103
104 func newMockDockerd() *mockDockerd {
105 return &mockDockerd{
106 containers: make(map[string]types.Container),
107 }
108 }
109
110 type mockDockerd struct {
111 negApiVerCalled bool
112 closeCalled bool
113 mux sync.Mutex
114 containers map[string]types.Container
115 }
116
117 func (m *mockDockerd) addContainer(cntr types.Container) {
118 m.mux.Lock()
119 defer m.mux.Unlock()
120
121 m.containers[cntr.ID] = cntr
122 }
123
124 func (m *mockDockerd) removeContainer(id string) {
125 m.mux.Lock()
126 defer m.mux.Unlock()
127
128 delete(m.containers, id)
129 }
130
131 func (m *mockDockerd) ContainerList(_ context.Context, _ typesContainer.ListOptions) ([]types.Container, error) {
132 m.mux.Lock()
133 defer m.mux.Unlock()
134
135 var cntrs []types.Container
136 for _, cntr := range m.containers {
137 cntrs = append(cntrs, cntr)
138 }
139
140 return cntrs, nil
141 }
142
143 func (m *mockDockerd) NegotiateAPIVersion(_ context.Context) {
144 m.negApiVerCalled = true
145 }
146
147 func (m *mockDockerd) Close() error {
148 m.closeCalled = true
149 return nil
150 }
151
152 func sortTargetGroups(tggs []model.TargetGroup) {
153 if len(tggs) == 0 {
154 return
155 }
156 sort.Slice(tggs, func(i, j int) bool { return tggs[i].Source() < tggs[j].Source() })
157 }