File: controller.go

package info (click to toggle)
golang-github-linbit-golinstor 0.57.0-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental, forky, sid
  • size: 472 kB
  • sloc: makefile: 11
file content (340 lines) | stat: -rw-r--r-- 13,874 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
// Copyright (C) LINBIT HA-Solutions GmbH
// All Rights Reserved.
// Author: Roland Kammerer <roland.kammerer@linbit.com>
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package client

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/url"
	"strconv"
	"time"
)

// copy & paste from generated code

// ControllerVersion represents version information of the LINSTOR controller
type ControllerVersion struct {
	Version        string `json:"version,omitempty"`
	GitHash        string `json:"git_hash,omitempty"`
	BuildTime      string `json:"build_time,omitempty"`
	RestApiVersion string `json:"rest_api_version,omitempty"`
}

// ErrorReport struct for ErrorReport
type ErrorReport struct {
	NodeName  string      `json:"node_name,omitempty"`
	ErrorTime TimeStampMs `json:"error_time"`
	// Filename of the error report on the server.  Format is: ```ErrorReport-{instanceid}-{nodeid}-{sequencenumber}.log```
	Filename string `json:"filename,omitempty"`
	// Contains the full text of the error report file.
	Text string `json:"text,omitempty"`
	// Which module this error occurred.
	Module string `json:"module,omitempty"`
	// Linstor version this error report was created.
	Version string `json:"version,omitempty"`
	// Peer client that was involved.
	Peer string `json:"peer,omitempty"`
	// Exception that occurred
	Exception string `json:"exception,omitempty"`
	// Exception message
	ExceptionMessage string `json:"exception_message,omitempty"`
	// Origin file of the exception
	OriginFile string `json:"origin_file,omitempty"`
	// Origin method where the exception occurred
	OriginMethod string `json:"origin_method,omitempty"`
	// Origin line number
	OriginLine int32 `json:"origin_line,omitempty"`
}

type ErrorReportDelete struct {
	Since *TimeStampMs `json:"since,omitempty"`
	To    *TimeStampMs `json:"to,omitempty"`
	// on which nodes to delete error-reports, if empty/null all nodes
	Nodes []string `json:"nodes,omitempty"`
	// delete all error reports with the given exception
	Exception string `json:"exception,omitempty"`
	// delete all error reports from the given version
	Version string `json:"version,omitempty"`
	// error report ids to delete
	Ids []string `json:"ids,omitempty"`
}

type PropsInfo struct {
	Info     string `json:"info,omitempty"`
	PropType string `json:"prop_type,omitempty"`
	Value    string `json:"value,omitempty"`
	Dflt     string `json:"dflt,omitempty"`
	Unit     string `json:"unit,omitempty"`
}

// ExternalFile is an external file which can be configured to be deployed by Linstor
type ExternalFile struct {
	Path    string
	Content []byte
}

// externalFileBase64 is a golinstor-internal type which represents an external
// file as it is handled by the LINSTOR API. The API expects files to come in
// base64 encoding, and also returns files in base64 encoding. To make golinstor
// easier to use, we only present the ExternalFile type to our users an
// transparently handle the base64 encoding/decoding.
type externalFileBase64 struct {
	Path          string `json:"path,omitempty"`
	ContentBase64 string `json:"content,omitempty"`
}

func (e *ExternalFile) UnmarshalJSON(text []byte) error {
	v := externalFileBase64{}
	err := json.Unmarshal(text, &v)
	if err != nil {
		return err
	}
	e.Path = v.Path
	e.Content, err = base64.StdEncoding.DecodeString(v.ContentBase64)
	if err != nil {
		return err
	}
	return nil
}

func (e *ExternalFile) MarshalJSON() ([]byte, error) {
	v := externalFileBase64{
		Path:          e.Path,
		ContentBase64: base64.StdEncoding.EncodeToString(e.Content),
	}
	return json.Marshal(v)
}

// custom code

// ControllerProvider acts as an abstraction for a ControllerService. It can be
// swapped out for another ControllerService implementation, for example for
// testing.
type ControllerProvider interface {
	// GetVersion queries version information for the controller.
	GetVersion(ctx context.Context, opts ...*ListOpts) (ControllerVersion, error)
	// GetConfig queries the configuration of a controller
	GetConfig(ctx context.Context, opts ...*ListOpts) (ControllerConfig, error)
	// Modify modifies the controller node and sets/deletes the given properties.
	Modify(ctx context.Context, props GenericPropsModify) error
	// GetProps gets all properties of a controller
	GetProps(ctx context.Context, opts ...*ListOpts) (ControllerProps, error)
	// DeleteProp deletes the given property/key from the controller object.
	DeleteProp(ctx context.Context, prop string) error
	// GetErrorReports returns all error reports. The Text field is not populated,
	// use GetErrorReport to get the text of an error report.
	GetErrorReports(ctx context.Context, opts ...*ListOpts) ([]ErrorReport, error)
	// DeleteErrorReports deletes error reports as specified by the ErrorReportDelete struct.
	DeleteErrorReports(ctx context.Context, del ErrorReportDelete) error
	// GetErrorReportsSince returns all error reports created after a certain point in time.
	GetErrorReportsSince(ctx context.Context, since time.Time, opts ...*ListOpts) ([]ErrorReport, error)
	// GetErrorReport returns a specific error report, including its text.
	GetErrorReport(ctx context.Context, id string, opts ...*ListOpts) (ErrorReport, error)
	// CreateSOSReport creates an SOS report in the log directory of the controller
	CreateSOSReport(ctx context.Context, opts ...*ListOpts) error
	// DownloadSOSReport creates and downloads an SOS report. The report is written to w as a tar.gz file.
	DownloadSOSReport(ctx context.Context, w io.WriteCloser, opts ...*ListOpts) error
	GetSatelliteConfig(ctx context.Context, node string) (SatelliteConfig, error)
	ModifySatelliteConfig(ctx context.Context, node string, cfg SatelliteConfig) error
	// GetPropsInfos gets meta information about the properties that can be
	// set on a controller.
	GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error)
	// GetPropsInfosAll gets meta information about all properties that can
	// be set on a controller and all entities it contains (nodes, resource
	// definitions, ...).
	GetPropsInfosAll(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error)
	// GetExternalFiles gets a list of previously registered external files.
	GetExternalFiles(ctx context.Context, opts ...*ListOpts) ([]ExternalFile, error)
	// GetExternalFile gets the requested external file including its content
	GetExternalFile(ctx context.Context, name string) (ExternalFile, error)
	// ModifyExternalFile registers or modifies a previously registered
	// external file
	ModifyExternalFile(ctx context.Context, name string, file ExternalFile) error
	// DeleteExternalFile deletes the given external file. This effectively
	// also deletes the file on all satellites
	DeleteExternalFile(ctx context.Context, name string) error
}

// ControllerService is the service that deals with the LINSTOR controller.
type ControllerService struct {
	client *Client
}

// GetVersion queries version information for the controller.
func (s *ControllerService) GetVersion(ctx context.Context, opts ...*ListOpts) (ControllerVersion, error) {
	var vers ControllerVersion
	_, err := s.client.doGET(ctx, "/v1/controller/version", &vers, opts...)
	return vers, err
}

// GetConfig queries the configuration of a controller
func (s *ControllerService) GetConfig(ctx context.Context, opts ...*ListOpts) (ControllerConfig, error) {
	var cfg ControllerConfig
	_, err := s.client.doGET(ctx, "/v1/controller/config", &cfg, opts...)
	return cfg, err
}

// Modify modifies the controller node and sets/deletes the given properties.
func (s *ControllerService) Modify(ctx context.Context, props GenericPropsModify) error {
	_, err := s.client.doPOST(ctx, "/v1/controller/properties", props)
	return err
}

type ControllerProps map[string]string

// GetProps gets all properties of a controller
func (s *ControllerService) GetProps(ctx context.Context, opts ...*ListOpts) (ControllerProps, error) {
	var props ControllerProps
	_, err := s.client.doGET(ctx, "/v1/controller/properties", &props, opts...)
	return props, err
}

// DeleteProp deletes the given property/key from the controller object.
func (s *ControllerService) DeleteProp(ctx context.Context, prop string) error {
	_, err := s.client.doDELETE(ctx, "/v1/controller/properties/"+prop, nil)
	return err
}

// GetPropsInfos gets meta information about the properties that can be set on
// a controller.
func (s *ControllerService) GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) {
	var infos []PropsInfo
	_, err := s.client.doGET(ctx, "/v1/controller/properties/info", &infos, opts...)
	return infos, err
}

// GetPropsInfosAll gets meta information about all properties that can be set
// on a controller and all entities it contains (nodes, resource definitions, ...).
func (s *ControllerService) GetPropsInfosAll(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) {
	var infos []PropsInfo
	_, err := s.client.doGET(ctx, "/v1/controller/properties/info/all", &infos, opts...)
	return infos, err
}

// GetErrorReports returns all error reports. The Text field is not populated,
// use GetErrorReport to get the text of an error report.
func (s *ControllerService) GetErrorReports(ctx context.Context, opts ...*ListOpts) ([]ErrorReport, error) {
	var reports []ErrorReport
	_, err := s.client.doGET(ctx, "/v1/error-reports", &reports, opts...)
	return reports, err
}

// DeleteErrorReports deletes error reports as specified by the ErrorReportDelete struct.
func (s *ControllerService) DeleteErrorReports(ctx context.Context, del ErrorReportDelete) error {
	// Yes, this is using PATCH. don't ask me why, its just implemented that way...
	_, err := s.client.doPATCH(ctx, "/v1/error-reports", del)
	return err
}

// unixMilli returns t formatted as milliseconds since Unix epoch
func unixMilli(t time.Time) int64 {
	return t.UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
}

// GetErrorReportsSince returns all error reports created after a certain point in time.
func (s *ControllerService) GetErrorReportsSince(ctx context.Context, since time.Time, opts ...*ListOpts) ([]ErrorReport, error) {
	var reports []ErrorReport
	v := url.Values{}
	v.Set("since", strconv.FormatInt(unixMilli(since), 10))
	_, err := s.client.doGET(ctx, "/v1/error-reports/?"+v.Encode(), &reports, opts...)
	return reports, err
}

// GetErrorReport returns a specific error report, including its text.
func (s *ControllerService) GetErrorReport(ctx context.Context, id string, opts ...*ListOpts) (ErrorReport, error) {
	var report []ErrorReport
	_, err := s.client.doGET(ctx, "/v1/error-reports/"+id, &report, opts...)
	return report[0], err
}

// CreateSOSReport creates an SOS report in the log directory of the controller
func (s *ControllerService) CreateSOSReport(ctx context.Context, opts ...*ListOpts) error {
	_, err := s.client.doGET(ctx, "/v1/sos-report", nil, opts...)
	return err
}

// DownloadSOSReport creates and downloads an SOS report. The report is written to w as a tar.gz file.
func (s *ControllerService) DownloadSOSReport(ctx context.Context, w io.WriteCloser, opts ...*ListOpts) error {
	reqUrl := "/v1/sos-report/download"
	u, err := addOptions(reqUrl, genOptions(opts...))
	if err != nil {
		return err
	}

	req, err := s.client.newRequest("GET", u, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Accept", "application/octet-stream")
	resp, err := s.client.do(ctx, req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	_, err = io.Copy(w, resp.Body)
	return err
}

func (s *ControllerService) GetSatelliteConfig(ctx context.Context, node string) (SatelliteConfig, error) {
	var cfg SatelliteConfig
	_, err := s.client.doGET(ctx, "/v1/nodes/"+node+"/config", &cfg)
	return cfg, err
}

func (s *ControllerService) ModifySatelliteConfig(ctx context.Context, node string, cfg SatelliteConfig) error {
	_, err := s.client.doPUT(ctx, "/v1/nodes/"+node+"/config", &cfg)
	return err
}

// GetExternalFiles get a list of previously registered external files.
// File contents are not included, unless ListOpts.Content is true.
func (s *ControllerService) GetExternalFiles(ctx context.Context, opts ...*ListOpts) ([]ExternalFile, error) {
	var files []ExternalFile
	_, err := s.client.doGET(ctx, "/v1/files", &files, opts...)
	return files, err
}

// GetExternalFile gets the requested external file including its content
func (s *ControllerService) GetExternalFile(ctx context.Context, name string) (ExternalFile, error) {
	file := ExternalFile{}
	_, err := s.client.doGET(ctx, "/v1/files/"+url.QueryEscape(name), &file)
	if err != nil {
		return ExternalFile{}, fmt.Errorf("request failed: %w", err)
	}
	return file, nil
}

// ModifyExternalFile registers or modifies a previously registered external
// file
func (s *ControllerService) ModifyExternalFile(ctx context.Context, name string, file ExternalFile) error {
	b64file := externalFileBase64{
		Path:          file.Path,
		ContentBase64: base64.StdEncoding.EncodeToString(file.Content),
	}
	_, err := s.client.doPUT(ctx, "/v1/files/"+url.QueryEscape(name), b64file)
	return err
}

// DeleteExternalFile deletes the given external file. This effectively also
// deletes the file on all satellites
func (s *ControllerService) DeleteExternalFile(ctx context.Context, name string) error {
	_, err := s.client.doDELETE(ctx, "/v1/files/"+url.QueryEscape(name), nil)
	return err
}