chore(go.d.plugin): simplify netdataapi pkg (#19145)
Ilya Mashchenko committed
Dec 6, 2024 at 21:32 UTC
e68ab50e899b44ae4658c8d0ac1b5d32f3fee43a
8 files changed
+464
-387
src/go/pkg/netdataapi/api.go
+96
-129
@@ -9,13 +9,11 @@ import (
9
"strconv"
10
)
11
12
-type (
13
- // API implements Netdata external plugins API.
14
- // https://learn.netdata.cloud/docs/agent/plugins.d#the-output-of-the-plugin
15
- API struct {
16
- io.Writer
17
- }
18
-)
12
+// API implements Netdata external plugins API.
13
+// See: https://learn.netdata.cloud/docs/agent/plugins.d#the-output-of-the-plugin
14
+type API struct {
15
+ io.Writer
16
+}
17
18
const quotes = "' '"
19
@@ -25,163 +23,129 @@ var (
23
newLine = []byte("\n")
24
)
25
28
-func New(w io.Writer) *API { return &API{w} }
29
-
30
-// CHART creates or update a chart.
31
-func (a *API) CHART(
32
- typeID string,
33
- ID string,
34
- name string,
35
- title string,
36
- units string,
37
- family string,
38
- context string,
39
- chartType string,
40
- priority int,
41
- updateEvery int,
42
- options string,
43
- plugin string,
44
- module string) error {
45
- _, err := a.Write([]byte("CHART " + "'" +
46
- typeID + "." + ID + quotes +
47
- name + quotes +
48
- title + quotes +
49
- units + quotes +
50
- family + quotes +
51
- context + quotes +
52
- chartType + quotes +
53
- strconv.Itoa(priority) + quotes +
54
- strconv.Itoa(updateEvery) + quotes +
55
- options + quotes +
56
- plugin + quotes +
57
- module + "'\n"))
58
- return err
59
-}
60
-
61
-// DIMENSION adds or update a dimension to the chart just created.
62
-func (a *API) DIMENSION(
63
- ID string,
64
- name string,
65
- algorithm string,
66
- multiplier int,
67
- divisor int,
68
- options string) error {
69
- _, err := a.Write([]byte("DIMENSION '" +
70
- ID + quotes +
71
- name + quotes +
72
- algorithm + quotes +
73
- strconv.Itoa(multiplier) + quotes +
74
- strconv.Itoa(divisor) + quotes +
75
- options + "'\n"))
76
- return err
77
-}
78
-
79
-// CLABEL adds or update a label to the chart.
80
-func (a *API) CLABEL(key, value string, source int) error {
81
- _, err := a.Write([]byte("CLABEL '" +
26
+// New creates a new API instance for interacting with Netdata.
27
+// Panics if the provided writer is nil.
28
+func New(w io.Writer) *API {
29
+ if w == nil {
30
+ panic("writer cannot be nil")
31
+ }
32
+ return &API{w}
33
+}
34
+
35
+// CHART creates or updates a chart.
36
+func (a *API) CHART(opts ChartOpts) {
37
+ _, _ = a.Write([]byte("CHART " + "'" +
38
+ opts.TypeID + "." + opts.ID + quotes +
39
+ opts.Name + quotes +
40
+ opts.Title + quotes +
41
+ opts.Units + quotes +
42
+ opts.Family + quotes +
43
+ opts.Context + quotes +
44
+ opts.ChartType + quotes +
45
+ strconv.Itoa(opts.Priority) + quotes +
46
+ strconv.Itoa(opts.UpdateEvery) + quotes +
47
+ opts.Options + quotes +
48
+ opts.Plugin + quotes +
49
+ opts.Module + "'\n"))
50
+}
51
+
52
+// DIMENSION adds or updates a dimension to the most recently created chart.
53
+func (a *API) DIMENSION(opts DimensionOpts) {
54
+ _, _ = a.Write([]byte("DIMENSION '" +
55
+ opts.ID + quotes +
56
+ opts.Name + quotes +
57
+ opts.Algorithm + quotes +
58
+ strconv.Itoa(opts.Multiplier) + quotes +
59
+ strconv.Itoa(opts.Divisor) + quotes +
60
+ opts.Options + "'\n"))
61
+}
62
+
63
+// CLABEL adds or updates a label to the most recently created chart.
64
+func (a *API) CLABEL(key, value string, source int) {
65
+ _, _ = a.Write([]byte("CLABEL '" +
66
key + quotes +
67
value + quotes +
68
strconv.Itoa(source) + "'\n"))
85
- return err
69
}
70
71
// CLABELCOMMIT adds labels to the chart. Should be called after one or more CLABEL.
89
-func (a *API) CLABELCOMMIT() error {
90
- _, err := a.Write(clabelCommit)
91
- return err
72
+func (a *API) CLABELCOMMIT() {
73
+ _, _ = a.Write(clabelCommit)
74
}
75
76
// BEGIN initializes data collection for a chart.
95
-func (a *API) BEGIN(typeID string, ID string, msSince int) (err error) {
77
+func (a *API) BEGIN(typeID string, id string, msSince int) {
78
if msSince > 0 {
97
- _, err = a.Write([]byte("BEGIN " + "'" + typeID + "." + ID + "' " + strconv.Itoa(msSince) + "\n"))
79
+ _, _ = a.Write([]byte("BEGIN " + "'" + typeID + "." + id + "' " + strconv.Itoa(msSince) + "\n"))
80
} else {
99
- _, err = a.Write([]byte("BEGIN " + "'" + typeID + "." + ID + "'\n"))
81
+ _, _ = a.Write([]byte("BEGIN " + "'" + typeID + "." + id + "'\n"))
82
}
101
- return err
83
}
84
85
// SET sets the value of a dimension for the initialized chart.
105
-func (a *API) SET(ID string, value int64) error {
106
- _, err := a.Write([]byte("SET '" + ID + "' = " + strconv.FormatInt(value, 10) + "\n"))
107
- return err
86
+func (a *API) SET(id string, value int64) {
87
+ _, _ = a.Write([]byte("SET '" + id + "' = " + strconv.FormatInt(value, 10) + "\n"))
88
}
89
110
-// SETEMPTY sets the empty value of a dimension for the initialized chart.
111
-func (a *API) SETEMPTY(ID string) error {
112
- _, err := a.Write([]byte("SET '" + ID + "' = \n"))
113
- return err
90
+// SETEMPTY sets an empty value for a dimension in the initialized chart.
91
+func (a *API) SETEMPTY(id string) {
92
+ _, _ = a.Write([]byte("SET '" + id + "' = \n"))
93
}
94
95
// VARIABLE sets the value of a CHART scope variable for the initialized chart.
117
-func (a *API) VARIABLE(ID string, value int64) error {
118
- _, err := a.Write([]byte("VARIABLE CHART '" + ID + "' = " + strconv.FormatInt(value, 10) + "\n"))
119
- return err
96
+func (a *API) VARIABLE(ID string, value int64) {
97
+ _, _ = a.Write([]byte("VARIABLE CHART '" + ID + "' = " + strconv.FormatInt(value, 10) + "\n"))
98
}
99
100
// END completes data collection for the initialized chart.
123
-func (a *API) END() error {
124
- _, err := a.Write(end)
125
- return err
101
+// Should be called after all SET operations are complete.
102
+func (a *API) END() {
103
+ _, _ = a.Write(end)
104
}
105
128
-// DISABLE disables this plugin. This will prevent Netdata from restarting the plugin.
129
-func (a *API) DISABLE() error {
130
- _, err := a.Write([]byte("DISABLE\n"))
131
- return err
106
+// DISABLE disables this plugin.
107
+// This will prevent Netdata from restarting the plugin.
108
+func (a *API) DISABLE() {
109
+ _, _ = a.Write([]byte("DISABLE\n"))
110
}
111
134
-// EMPTYLINE writes an empty line.
112
+// EMPTYLINE writes an empty line to the output.
113
func (a *API) EMPTYLINE() error {
114
_, err := a.Write(newLine)
115
return err
116
}
117
140
-func (a *API) HOSTINFO(guid, hostname string, labels map[string]string) error {
141
- if err := a.HOSTDEFINE(guid, hostname); err != nil {
142
- return err
143
- }
144
- for k, v := range labels {
145
- if err := a.HOSTLABEL(k, v); err != nil {
146
- return err
147
- }
148
- }
149
- return a.HOSTDEFINEEND()
150
-}
151
-
152
-func (a *API) HOSTDEFINE(guid, hostname string) error {
153
- _, err := fmt.Fprintf(a, "HOST_DEFINE '%s' '%s'\n", guid, hostname)
154
- return err
155
-}
118
+// HOSTINFO defines a host with its labels.
119
+func (a *API) HOSTINFO(info HostInfo) {
120
+ var buf bytes.Buffer
121
157
-func (a *API) HOSTLABEL(name, value string) error {
158
- _, err := fmt.Fprintf(a, "HOST_LABEL '%s' '%s'\n", name, value)
159
- return err
160
-}
122
+ buf.WriteString(fmt.Sprintf("HOST_DEFINE '%s' '%s'\n", info.GUID, info.Hostname))
123
+ for k, v := range info.Labels {
124
+ buf.WriteString(fmt.Sprintf("HOST_LABEL '%s' '%s'\n", k, v))
125
+ }
126
+ buf.WriteString("HOST_DEFINE_END\n\n")
127
162
-func (a *API) HOSTDEFINEEND() error {
163
- _, err := fmt.Fprintf(a, "HOST_DEFINE_END\n\n")
164
- return err
128
+ _, _ = buf.WriteTo(a)
129
}
130
167
-func (a *API) HOST(guid string) error {
168
- _, err := a.Write([]byte("HOST " + "'" +
169
- guid + "'\n\n"))
170
- return err
131
+// HOST switches the current context to a specific host.
132
+func (a *API) HOST(guid string) {
133
+ _, _ = a.Write([]byte("HOST " + "'" + guid + "'\n\n"))
134
}
135
173
-func (a *API) FUNCRESULT(uid, contentType, payload, code, expireTimestamp string) {
136
+// FUNCRESULT writes a function result to Netdata.
137
+func (a *API) FUNCRESULT(result FunctionResult) {
138
var buf bytes.Buffer
139
140
buf.WriteString("FUNCTION_RESULT_BEGIN " +
177
- uid + " " +
178
- code + " " +
179
- contentType + " " +
180
- expireTimestamp + "\n",
141
+ result.UID + " " +
142
+ result.Code + " " +
143
+ result.ContentType + " " +
144
+ result.ExpireTimestamp + "\n",
145
)
146
183
- if payload != "" {
184
- buf.WriteString(payload + "\n")
147
+ if result.Payload != "" {
148
+ buf.WriteString(result.Payload + "\n")
149
}
150
151
buf.WriteString("FUNCTION_RESULT_END\n\n")
@@ -189,25 +153,28 @@ func (a *API) FUNCRESULT(uid, contentType, payload, code, expireTimestamp string
153
_, _ = buf.WriteTo(a)
154
}
155
192
-func (a *API) CONFIGCREATE(id, status, configType, path, sourceType, source, supportedCommands string) {
156
+// CONFIGCREATE creates a new configuration
157
+func (a *API) CONFIGCREATE(opts ConfigOpts) {
158
// https://learn.netdata.cloud/docs/contributing/external-plugins/#config
159
160
_, _ = a.Write([]byte("CONFIG " +
196
- id + " " +
161
+ opts.ID + " " +
162
"create" + " " +
198
- status + " " +
199
- configType + " " +
200
- path + " " +
201
- sourceType + " '" +
202
- source + "' '" +
203
- supportedCommands + "' 0x0000 0x0000\n\n",
163
+ opts.Status + " " +
164
+ opts.ConfigType + " " +
165
+ opts.Path + " " +
166
+ opts.SourceType + " '" +
167
+ opts.Source + "' '" +
168
+ opts.SupportedCommands + "' 0x0000 0x0000\n\n",
169
))
170
}
171
172
+// CONFIGDELETE deletes a configuration
173
func (a *API) CONFIGDELETE(id string) {
174
_, _ = a.Write([]byte("CONFIG " + id + " delete\n\n"))
175
}
176
177
+// CONFIGSTATUS updates a configuration status
178
func (a *API) CONFIGSTATUS(id, status string) {
179
_, _ = a.Write([]byte("CONFIG " + id + " status " + status + "\n\n"))
180
}
src/go/pkg/netdataapi/api_test.go
+217
-194
@@ -6,260 +6,283 @@ import (
6
"bytes"
7
"testing"
8
9
- "github.com/stretchr/testify/assert"
9
+ "github.com/stretchr/testify/require"
10
)
11
12
-func TestAPI_CHART(t *testing.T) {
13
- buf := &bytes.Buffer{}
14
- a := API{Writer: buf}
15
-
16
- _ = a.CHART(
17
- "",
18
- "id",
19
- "name",
20
- "title",
21
- "units",
22
- "family",
23
- "context",
24
- "line",
25
- 1,
26
- 1,
27
- "",
28
- "plugin",
29
- "module",
30
- )
31
-
32
- assert.Equal(
33
- t,
34
- "CHART '.id' 'name' 'title' 'units' 'family' 'context' 'line' '1' '1' '' 'plugin' 'module'\n",
35
- buf.String(),
36
- )
12
+func TestNew(t *testing.T) {
13
+ t.Run("valid writer", func(t *testing.T) {
14
+ require.NotNil(t, New(&bytes.Buffer{}))
15
+ })
16
+
17
+ t.Run("nil writer", func(t *testing.T) {
18
+ require.Panics(t, func() { New(nil) })
19
+ })
20
}
21
39
-func TestAPI_DIMENSION(t *testing.T) {
40
- buf := &bytes.Buffer{}
41
- a := API{Writer: buf}
42
-
43
- _ = a.DIMENSION(
44
- "id",
45
- "name",
46
- "absolute",
47
- 1,
48
- 1,
49
- "",
50
- )
51
-
52
- assert.Equal(
53
- t,
54
- "DIMENSION 'id' 'name' 'absolute' '1' '1' ''\n",
55
- buf.String(),
56
- )
22
+func TestChart(t *testing.T) {
23
+ w := &bytes.Buffer{}
24
+ api := New(w)
25
+
26
+ opts := ChartOpts{
27
+ TypeID: "system",
28
+ ID: "cpu",
29
+ Name: "cpu_system",
30
+ Title: "CPU Usage",
31
+ Units: "percentage",
32
+ Family: "cpu",
33
+ Context: "system.cpu",
34
+ ChartType: "line",
35
+ Priority: 1000,
36
+ UpdateEvery: 1,
37
+ Options: "",
38
+ Plugin: "system",
39
+ Module: "cpu",
40
+ }
41
+
42
+ api.CHART(opts)
43
+
44
+ expected := "CHART 'system.cpu' 'cpu_system' 'CPU Usage' 'percentage' 'cpu' 'system.cpu' " +
45
+ "'line' '1000' '1' '' 'system' 'cpu'\n"
46
+
47
+ require.Equal(t, expected, w.String())
48
}
49
59
-func TestAPI_BEGIN(t *testing.T) {
60
- buf := &bytes.Buffer{}
61
- a := API{Writer: buf}
62
-
63
- _ = a.BEGIN(
64
- "typeID",
65
- "id",
66
- 0,
67
- )
68
-
69
- assert.Equal(
70
- t,
71
- "BEGIN 'typeID.id'\n",
72
- buf.String(),
73
- )
74
-
75
- buf.Reset()
76
-
77
- _ = a.BEGIN(
78
- "typeID",
79
- "id",
80
- 1,
81
- )
82
-
83
- assert.Equal(
84
- t,
85
- "BEGIN 'typeID.id' 1\n",
86
- buf.String(),
87
- )
50
+func TestDimension(t *testing.T) {
51
+ w := &bytes.Buffer{}
52
+ api := New(w)
53
+
54
+ opts := DimensionOpts{
55
+ ID: "user",
56
+ Name: "user",
57
+ Algorithm: "absolute",
58
+ Multiplier: 1,
59
+ Divisor: 1,
60
+ Options: "",
61
+ }
62
+
63
+ api.DIMENSION(opts)
64
+
65
+ expected := "DIMENSION 'user' 'user' 'absolute' '1' '1' ''\n"
66
+
67
+ require.Equal(t, expected, w.String())
68
}
69
90
-func TestAPI_SET(t *testing.T) {
91
- buf := &bytes.Buffer{}
92
- a := API{Writer: buf}
70
+func TestCLABEL(t *testing.T) {
71
+ w := &bytes.Buffer{}
72
+ api := New(w)
73
+
74
+ api.CLABEL("key1", "value1", 1)
75
94
- _ = a.SET("id", 100)
76
+ expected := "CLABEL 'key1' 'value1' '1'\n"
77
96
- assert.Equal(
97
- t,
98
- "SET 'id' = 100\n",
99
- buf.String(),
100
- )
78
+ require.Equal(t, expected, w.String())
79
}
80
103
-func TestAPI_SETEMPTY(t *testing.T) {
104
- buf := &bytes.Buffer{}
105
- a := API{Writer: buf}
81
+func TestCLABELCOMMIT(t *testing.T) {
82
+ w := &bytes.Buffer{}
83
+ api := New(w)
84
107
- _ = a.SETEMPTY("id")
85
+ api.CLABELCOMMIT()
86
109
- assert.Equal(
110
- t,
111
- "SET 'id' = \n",
112
- buf.String(),
113
- )
87
+ expected := "CLABEL_COMMIT\n"
88
+
89
+ require.Equal(t, expected, w.String())
90
}
91
116
-func TestAPI_VARIABLE(t *testing.T) {
117
- buf := &bytes.Buffer{}
118
- a := API{Writer: buf}
92
+func TestBEGIN(t *testing.T) {
93
+
94
+ tests := map[string]struct {
95
+ name string
96
+ typeID string
97
+ ID string
98
+ msSince int
99
+ expected string
100
+ }{
101
+ "without msSince": {
102
+ typeID: "system",
103
+ ID: "cpu",
104
+ msSince: 0,
105
+ expected: "BEGIN 'system.cpu'\n",
106
+ },
107
+ "with msSince": {
108
+ typeID: "system",
109
+ ID: "cpu",
110
+ msSince: 1000,
111
+ expected: "BEGIN 'system.cpu' 1000\n",
112
+ },
113
+ }
114
+
115
+ for name, test := range tests {
116
+ t.Run(name, func(t *testing.T) {
117
+ w := &bytes.Buffer{}
118
+ api := New(w)
119
+
120
+ api.BEGIN(test.typeID, test.ID, test.msSince)
121
+
122
+ require.Equal(t, test.expected, w.String())
123
+ })
124
+ }
125
+}
126
120
- _ = a.VARIABLE("id", 100)
127
+func TestSET(t *testing.T) {
128
+ w := &bytes.Buffer{}
129
+ api := New(w)
130
122
- assert.Equal(
123
- t,
124
- "VARIABLE CHART 'id' = 100\n",
125
- buf.String(),
126
- )
131
+ api.SET("cpu_user", 42)
132
+
133
+ expected := "SET 'cpu_user' = 42\n"
134
+
135
+ require.Equal(t, expected, w.String())
136
}
137
129
-func TestAPI_END(t *testing.T) {
130
- buf := &bytes.Buffer{}
131
- a := API{Writer: buf}
138
+func TestSETEMPTY(t *testing.T) {
139
+ w := &bytes.Buffer{}
140
+ api := New(w)
141
133
- _ = a.END()
142
+ api.SETEMPTY("cpu_user")
143
135
- assert.Equal(
136
- t,
137
- "END\n\n",
138
- buf.String(),
139
- )
144
+ expected := "SET 'cpu_user' = \n"
145
+
146
+ require.Equal(t, expected, w.String())
147
}
148
142
-func TestAPI_CLABEL(t *testing.T) {
143
- buf := &bytes.Buffer{}
144
- a := API{Writer: buf}
149
+func TestVARIABLE(t *testing.T) {
150
+ w := &bytes.Buffer{}
151
+ api := New(w)
152
+
153
+ api.VARIABLE("var1", 100)
154
146
- _ = a.CLABEL("key", "value", 1)
155
+ expected := "VARIABLE CHART 'var1' = 100\n"
156
148
- assert.Equal(
149
- t,
150
- "CLABEL 'key' 'value' '1'\n",
151
- buf.String(),
152
- )
157
+ require.Equal(t, expected, w.String())
158
}
159
155
-func TestAPI_CLABELCOMMIT(t *testing.T) {
156
- buf := &bytes.Buffer{}
157
- a := API{Writer: buf}
160
+func TestEND(t *testing.T) {
161
+ w := &bytes.Buffer{}
162
+ api := New(w)
163
159
- _ = a.CLABELCOMMIT()
164
+ api.END()
165
161
- assert.Equal(
162
- t,
163
- "CLABEL_COMMIT\n",
164
- buf.String(),
165
- )
166
+ expected := "END\n\n"
167
+
168
+ require.Equal(t, expected, w.String())
169
}
170
168
-func TestAPI_DISABLE(t *testing.T) {
169
- buf := &bytes.Buffer{}
170
- a := API{Writer: buf}
171
+func TestDISABLE(t *testing.T) {
172
+ w := &bytes.Buffer{}
173
+ api := New(w)
174
+
175
+ api.DISABLE()
176
172
- _ = a.DISABLE()
177
+ expected := "DISABLE\n"
178
174
- assert.Equal(
175
- t,
176
- "DISABLE\n",
177
- buf.String(),
178
- )
179
+ require.Equal(t, expected, w.String())
180
}
181
181
-func TestAPI_EMPTYLINE(t *testing.T) {
182
- buf := &bytes.Buffer{}
183
- a := API{Writer: buf}
182
+func TestEMPTYLINE(t *testing.T) {
183
+ w := &bytes.Buffer{}
184
+ api := New(w)
185
+
186
+ require.NoError(t, api.EMPTYLINE())
187
185
- _ = a.EMPTYLINE()
188
+ expected := "\n"
189
187
- assert.Equal(
188
- t,
189
- "\n",
190
- buf.String(),
191
- )
190
+ require.Equal(t, expected, w.String())
191
}
192
194
-func TestAPI_HOST(t *testing.T) {
195
- buf := &bytes.Buffer{}
196
- a := API{Writer: buf}
193
+func TestHOSTINFO(t *testing.T) {
194
+ w := &bytes.Buffer{}
195
+ api := New(w)
196
198
- _ = a.HOST("guid")
197
+ info := HostInfo{
198
+ GUID: "test-guid",
199
+ Hostname: "test-host",
200
+ Labels: map[string]string{
201
+ "label1": "value1",
202
+ },
203
+ }
204
200
- assert.Equal(
201
- t,
202
- "HOST 'guid'\n\n",
203
- buf.String(),
204
- )
205
+ api.HOSTINFO(info)
206
+
207
+ expected := `
208
+HOST_DEFINE 'test-guid' 'test-host'
209
+HOST_LABEL 'label1' 'value1'
210
+HOST_DEFINE_END
211
+
212
+`[1:]
213
+
214
+ require.Equal(t, expected, w.String())
215
}
216
207
-func TestAPI_HOSTDEFINE(t *testing.T) {
208
- buf := &bytes.Buffer{}
209
- a := API{Writer: buf}
217
+func TestHOST(t *testing.T) {
218
+ w := &bytes.Buffer{}
219
+ api := New(w)
220
+
221
+ api.HOST("test-guid")
222
211
- _ = a.HOSTDEFINE("guid", "hostname")
223
+ expected := "HOST 'test-guid'\n\n"
224
213
- assert.Equal(
214
- t,
215
- "HOST_DEFINE 'guid' 'hostname'\n",
216
- buf.String(),
217
- )
225
+ require.Equal(t, expected, w.String())
226
}
227
220
-func TestAPI_HOSTLABEL(t *testing.T) {
221
- buf := &bytes.Buffer{}
222
- a := API{Writer: buf}
228
+func TestFUNCRESULT(t *testing.T) {
229
+ w := &bytes.Buffer{}
230
+ api := New(w)
231
+
232
+ result := FunctionResult{
233
+ UID: "test-uid",
234
+ ContentType: "text/plain",
235
+ Payload: "test payload",
236
+ Code: "200",
237
+ ExpireTimestamp: "1234567890",
238
+ }
239
224
- _ = a.HOSTLABEL("name", "value")
240
+ api.FUNCRESULT(result)
241
226
- assert.Equal(
227
- t,
228
- "HOST_LABEL 'name' 'value'\n",
229
- buf.String(),
230
- )
242
+ expected := "FUNCTION_RESULT_BEGIN test-uid 200 text/plain 1234567890\ntest payload\nFUNCTION_RESULT_END\n\n"
243
+
244
+ require.Equal(t, expected, w.String())
245
}
246
233
-func TestAPI_HOSTDEFINEEND(t *testing.T) {
234
- buf := &bytes.Buffer{}
235
- a := API{Writer: buf}
247
+func TestCONFIGCREATE(t *testing.T) {
248
+ w := &bytes.Buffer{}
249
+ api := New(w)
250
+
251
+ opts := ConfigOpts{
252
+ ID: "test-config",
253
+ Status: "active",
254
+ ConfigType: "test",
255
+ Path: "/test/path",
256
+ SourceType: "file",
257
+ Source: "test.conf",
258
+ SupportedCommands: "read,write",
259
+ }
260
237
- _ = a.HOSTDEFINEEND()
261
+ api.CONFIGCREATE(opts)
262
239
- assert.Equal(
240
- t,
241
- "HOST_DEFINE_END\n\n",
242
- buf.String(),
243
- )
263
+ expected := "CONFIG test-config create active test /test/path file 'test.conf' 'read,write' 0x0000 0x0000\n\n"
264
+
265
+ require.Equal(t, expected, w.String())
266
}
267
246
-func TestAPI_HOSTINFO(t *testing.T) {
247
- buf := &bytes.Buffer{}
248
- a := API{Writer: buf}
268
+func TestCONFIGDELETE(t *testing.T) {
269
+ w := &bytes.Buffer{}
270
+ api := New(w)
271
250
- _ = a.HOSTINFO("guid", "hostname", map[string]string{"label1": "value1"})
272
+ api.CONFIGDELETE("test-config")
273
252
- assert.Equal(
253
- t,
254
- `HOST_DEFINE 'guid' 'hostname'
255
-HOST_LABEL 'label1' 'value1'
256
-HOST_DEFINE_END
274
+ expected := "CONFIG test-config delete\n\n"
275
258
-`,
259
- buf.String(),
260
- )
276
+ require.Equal(t, expected, w.String())
277
}
278
263
-func TestAPI_FUNCRESULT(t *testing.T) {
279
+func TestCONFIGSTATUS(t *testing.T) {
280
+ w := &bytes.Buffer{}
281
+ api := New(w)
282
+
283
+ api.CONFIGSTATUS("test-config", "inactive")
284
+
285
+ expected := "CONFIG test-config status inactive\n\n"
286
287
+ require.Equal(t, expected, w.String())
288
}
src/go/pkg/netdataapi/opts.go
new
+57
@@ -0,0 +1,57 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+package netdataapi
4
+
5
+// ChartOpts contains all options needed to create a chart
6
+type ChartOpts struct {
7
+ TypeID string
8
+ ID string
9
+ Name string
10
+ Title string
11
+ Units string
12
+ Family string
13
+ Context string
14
+ ChartType string
15
+ Priority int
16
+ UpdateEvery int
17
+ Options string
18
+ Plugin string
19
+ Module string
20
+}
21
+
22
+// DimensionOpts contains all options needed to create a dimension
23
+type DimensionOpts struct {
24
+ ID string
25
+ Name string
26
+ Algorithm string
27
+ Multiplier int
28
+ Divisor int
29
+ Options string
30
+}
31
+
32
+// HostInfo contains the information needed for host definition
33
+type HostInfo struct {
34
+ GUID string
35
+ Hostname string
36
+ Labels map[string]string
37
+}
38
+
39
+// FunctionResult contains all parameters for a function result
40
+type FunctionResult struct {
41
+ UID string
42
+ ContentType string
43
+ Payload string
44
+ Code string
45
+ ExpireTimestamp string
46
+}
47
+
48
+// ConfigOpts contains options needed for config operations
49
+type ConfigOpts struct {
50
+ ID string
51
+ Status string
52
+ ConfigType string
53
+ Path string
54
+ SourceType string
55
+ Source string
56
+ SupportedCommands string
57
+}
src/go/plugin/go.d/agent/agent.go
+2
-2
@@ -163,7 +163,7 @@ func (a *Agent) run(ctx context.Context) {
163
if isTerminal {
164
os.Exit(0)
165
}
166
- _ = a.api.DISABLE()
166
+ a.api.DISABLE()
167
return
168
}
169
@@ -173,7 +173,7 @@ func (a *Agent) run(ctx context.Context) {
173
if isTerminal {
174
os.Exit(0)
175
}
176
- _ = a.api.DISABLE()
176
+ a.api.DISABLE()
177
return
178
}
179
src/go/plugin/go.d/agent/functions/manager.go
+8
-2
@@ -114,6 +114,12 @@ func (m *Manager) respf(fn *Function, code int, msgf string, a ...any) {
114
Status: code,
115
Message: fmt.Sprintf(msgf, a...),
116
})
117
- ts := strconv.FormatInt(time.Now().Unix(), 10)
118
- m.api.FUNCRESULT(fn.UID, "application/json", string(bs), strconv.Itoa(code), ts)
117
+
118
+ m.api.FUNCRESULT(netdataapi.FunctionResult{
119
+ UID: fn.UID,
120
+ ContentType: "application/json",
121
+ Payload: string(bs),
122
+ Code: strconv.Itoa(code),
123
+ ExpireTimestamp: strconv.FormatInt(time.Now().Unix(), 10),
124
+ })
125
}
src/go/plugin/go.d/agent/jobmgr/di.go
+3
-2
@@ -3,6 +3,7 @@
3
package jobmgr
4
5
import (
6
+ "github.com/netdata/netdata/go/plugins/pkg/netdataapi"
7
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/confgroup"
8
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/functions"
9
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/vnodes"
@@ -32,8 +33,8 @@ type FunctionRegistry interface {
33
}
34
35
type dyncfgAPI interface {
35
- CONFIGCREATE(id, status, configType, path, sourceType, source, supportedCommands string)
36
+ CONFIGCREATE(opts netdataapi.ConfigOpts)
37
CONFIGDELETE(id string)
38
CONFIGSTATUS(id, status string)
38
- FUNCRESULT(uid, contentType, payload, code, expireTimestamp string)
39
+ FUNCRESULT(result netdataapi.FunctionResult)
40
}
src/go/plugin/go.d/agent/jobmgr/dyncfg.go
+33
-15
@@ -15,6 +15,7 @@ import (
15
"unicode"
16
17
"github.com/netdata/netdata/go/plugins/logger"
18
+ "github.com/netdata/netdata/go/plugins/pkg/netdataapi"
19
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/confgroup"
20
"github.com/netdata/netdata/go/plugins/plugin/go.d/agent/functions"
21
@@ -73,20 +74,27 @@ func dyncfgJobCmds(cfg confgroup.Config) string {
74
}
75
76
func (m *Manager) dyncfgModuleCreate(name string) {
76
- id := dyncfgModID(name)
77
- path := dyncfgPath
78
- cmds := dyncfgModCmds()
79
- typ := "template"
80
- src := "internal"
81
- m.api.CONFIGCREATE(id, dyncfgAccepted.String(), typ, path, src, src, cmds)
77
+ m.api.CONFIGCREATE(netdataapi.ConfigOpts{
78
+ ID: dyncfgModID(name),
79
+ Status: dyncfgAccepted.String(),
80
+ ConfigType: "template",
81
+ Path: dyncfgPath,
82
+ SourceType: "internal",
83
+ Source: "internal",
84
+ SupportedCommands: dyncfgModCmds(),
85
+ })
86
}
87
88
func (m *Manager) dyncfgJobCreate(cfg confgroup.Config, status dyncfgStatus) {
85
- id := dyncfgJobID(cfg)
86
- path := dyncfgPath
87
- cmds := dyncfgJobCmds(cfg)
88
- typ := "job"
89
- m.api.CONFIGCREATE(id, status.String(), typ, path, cfg.SourceType(), cfg.Source(), cmds)
89
+ m.api.CONFIGCREATE(netdataapi.ConfigOpts{
90
+ ID: dyncfgJobID(cfg),
91
+ Status: status.String(),
92
+ ConfigType: "job",
93
+ Path: dyncfgPath,
94
+ SourceType: cfg.SourceType(),
95
+ Source: cfg.Source(),
96
+ SupportedCommands: dyncfgJobCmds(cfg),
97
+ })
98
}
99
100
func (m *Manager) dyncfgJobRemove(cfg confgroup.Config) {
@@ -735,8 +743,13 @@ func (m *Manager) dyncfgRespPayloadYAML(fn functions.Function, payload string) {
743
}
744
745
func (m *Manager) dyncfgRespPayload(fn functions.Function, payload string, contentType string) {
738
- ts := strconv.FormatInt(time.Now().Unix(), 10)
739
- m.api.FUNCRESULT(fn.UID, contentType, payload, "200", ts)
746
+ m.api.FUNCRESULT(netdataapi.FunctionResult{
747
+ UID: fn.UID,
748
+ ContentType: contentType,
749
+ Payload: payload,
750
+ Code: "200",
751
+ ExpireTimestamp: strconv.FormatInt(time.Now().Unix(), 10),
752
+ })
753
}
754
755
func (m *Manager) dyncfgRespf(fn functions.Function, code int, msgf string, a ...any) {
@@ -750,8 +763,13 @@ func (m *Manager) dyncfgRespf(fn functions.Function, code int, msgf string, a ..
763
Status: code,
764
Message: fmt.Sprintf(msgf, a...),
765
})
753
- ts := strconv.FormatInt(time.Now().Unix(), 10)
754
- m.api.FUNCRESULT(fn.UID, "application/json", string(bs), strconv.Itoa(code), ts)
766
+ m.api.FUNCRESULT(netdataapi.FunctionResult{
767
+ UID: fn.UID,
768
+ ContentType: "application/json",
769
+ Payload: string(bs),
770
+ Code: strconv.Itoa(code),
771
+ ExpireTimestamp: strconv.FormatInt(time.Now().Unix(), 10),
772
+ })
773
}
774
775
func userConfigFromPayload(cfg any, jobName string, fn functions.Function) ([]byte, error) {
src/go/plugin/go.d/agent/module/job.go
+48
-43
@@ -305,10 +305,14 @@ func (j *Job) Cleanup() {
305
}
306
307
if !j.vnodeCreated && j.vnodeGUID != "" {
308
- _ = j.api.HOSTINFO(j.vnodeGUID, j.vnodeHostname, j.vnodeLabels)
308
+ j.api.HOSTINFO(netdataapi.HostInfo{
309
+ GUID: j.vnodeGUID,
310
+ Hostname: j.vnodeHostname,
311
+ Labels: j.vnodeLabels,
312
+ })
313
j.vnodeCreated = true
314
}
311
- _ = j.api.HOST(j.vnodeGUID)
315
+ j.api.HOST(j.vnodeGUID)
316
317
if j.collectStatusChart.created {
318
j.collectStatusChart.MarkRemove()
@@ -414,12 +418,16 @@ func (j *Job) processMetrics(metrics map[string]int64, startTime time.Time, sinc
418
}
419
}
420
if j.vnodeGUID != "" {
417
- _ = j.api.HOSTINFO(j.vnodeGUID, j.vnodeHostname, j.vnodeLabels)
421
+ j.api.HOSTINFO(netdataapi.HostInfo{
422
+ GUID: j.vnodeGUID,
423
+ Hostname: j.vnodeHostname,
424
+ Labels: j.vnodeLabels,
425
+ })
426
j.vnodeCreated = true
427
}
428
}
429
422
- _ = j.api.HOST(j.vnodeGUID)
430
+ j.api.HOST(j.vnodeGUID)
431
432
if !j.collectStatusChart.created {
433
j.collectStatusChart.ID = fmt.Sprintf("%s_%s_data_collection_status", cleanPluginName(j.pluginName), j.FullName())
@@ -483,21 +491,21 @@ func (j *Job) createChart(chart *Chart) {
491
chart.Priority = j.priority
492
j.priority++
493
}
486
- _ = j.api.CHART(
487
- getChartType(chart, j),
488
- getChartID(chart),
489
- chart.OverID,
490
- chart.Title,
491
- chart.Units,
492
- chart.Fam,
493
- chart.Ctx,
494
- chart.Type.String(),
495
- chart.Priority,
496
- j.updateEvery,
497
- chart.Opts.String(),
498
- j.pluginName,
499
- j.moduleName,
500
- )
494
+ j.api.CHART(netdataapi.ChartOpts{
495
+ TypeID: getChartType(chart, j),
496
+ ID: getChartID(chart),
497
+ Name: chart.OverID,
498
+ Title: chart.Title,
499
+ Units: chart.Units,
500
+ Family: chart.Fam,
501
+ Context: chart.Ctx,
502
+ ChartType: chart.Type.String(),
503
+ Priority: chart.Priority,
504
+ UpdateEvery: j.updateEvery,
505
+ Options: chart.Opts.String(),
506
+ Plugin: j.pluginName,
507
+ Module: j.moduleName,
508
+ })
509
510
if chart.Obsolete {
511
_ = j.api.EMPTYLINE()
@@ -514,32 +522,32 @@ func (j *Job) createChart(chart *Chart) {
522
if ls == 0 {
523
ls = LabelSourceAuto
524
}
517
- _ = j.api.CLABEL(l.Key, lblReplacer.Replace(l.Value), ls)
525
+ j.api.CLABEL(l.Key, lblReplacer.Replace(l.Value), ls)
526
}
527
}
528
for k, v := range j.labels {
529
if !seen[k] {
522
- _ = j.api.CLABEL(k, lblReplacer.Replace(v), LabelSourceConf)
530
+ j.api.CLABEL(k, lblReplacer.Replace(v), LabelSourceConf)
531
}
532
}
525
- _ = j.api.CLABEL("_collect_job", lblReplacer.Replace(j.Name()), LabelSourceAuto)
526
- _ = j.api.CLABELCOMMIT()
533
+ j.api.CLABEL("_collect_job", lblReplacer.Replace(j.Name()), LabelSourceAuto)
534
+ j.api.CLABELCOMMIT()
535
536
for _, dim := range chart.Dims {
529
- _ = j.api.DIMENSION(
530
- firstNotEmpty(dim.Name, dim.ID),
531
- dim.Name,
532
- dim.Algo.String(),
533
- handleZero(dim.Mul),
534
- handleZero(dim.Div),
535
- dim.DimOpts.String(),
536
- )
537
+ j.api.DIMENSION(netdataapi.DimensionOpts{
538
+ ID: firstNotEmpty(dim.Name, dim.ID),
539
+ Name: dim.Name,
540
+ Algorithm: dim.Algo.String(),
541
+ Multiplier: handleZero(dim.Mul),
542
+ Divisor: handleZero(dim.Div),
543
+ Options: dim.DimOpts.String(),
544
+ })
545
}
546
for _, v := range chart.Vars {
547
if v.Name != "" {
540
- _ = j.api.VARIABLE(v.Name, v.Value)
548
+ j.api.VARIABLE(v.Name, v.Value)
549
} else {
542
- _ = j.api.VARIABLE(v.ID, v.Value)
550
+ j.api.VARIABLE(v.ID, v.Value)
551
}
552
}
553
_ = j.api.EMPTYLINE()
@@ -561,11 +569,8 @@ func (j *Job) updateChart(chart *Chart, collected map[string]int64, sinceLastRun
569
sinceLastRun = 0
570
}
571
564
- _ = j.api.BEGIN(
565
- getChartType(chart, j),
566
- getChartID(chart),
567
- sinceLastRun,
568
- )
572
+ j.api.BEGIN(getChartType(chart, j), getChartID(chart), sinceLastRun)
573
+
574
var i, updated int
575
for _, dim := range chart.Dims {
576
if dim.remove {
@@ -574,9 +579,9 @@ func (j *Job) updateChart(chart *Chart, collected map[string]int64, sinceLastRun
579
chart.Dims[i] = dim
580
i++
581
if v, ok := collected[dim.ID]; !ok {
577
- _ = j.api.SETEMPTY(firstNotEmpty(dim.Name, dim.ID))
582
+ j.api.SETEMPTY(firstNotEmpty(dim.Name, dim.ID))
583
} else {
579
- _ = j.api.SET(firstNotEmpty(dim.Name, dim.ID), v)
584
+ j.api.SET(firstNotEmpty(dim.Name, dim.ID), v)
585
updated++
586
}
587
}
@@ -585,14 +590,14 @@ func (j *Job) updateChart(chart *Chart, collected map[string]int64, sinceLastRun
590
for _, vr := range chart.Vars {
591
if v, ok := collected[vr.ID]; ok {
592
if vr.Name != "" {
588
- _ = j.api.VARIABLE(vr.Name, v)
593
+ j.api.VARIABLE(vr.Name, v)
594
} else {
590
- _ = j.api.VARIABLE(vr.ID, v)
595
+ j.api.VARIABLE(vr.ID, v)
596
}
597
}
598
599
}
595
- _ = j.api.END()
600
+ j.api.END()
601
602
if chart.updated = updated > 0; chart.updated {
603
chart.Retries = 0