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
}
|