@cryptotaxi247 / netdata-1 / commits / e68ab50e8

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