github.com/LINBIT/golinstor@v0.52.0/client/controller.go (about) 1 // Copyright (C) LINBIT HA-Solutions GmbH 2 // All Rights Reserved. 3 // Author: Roland Kammerer <roland.kammerer@linbit.com> 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. You may obtain 7 // a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 // License for the specific language governing permissions and limitations 15 // under the License. 16 17 package client 18 19 import ( 20 "context" 21 "encoding/base64" 22 "encoding/json" 23 "fmt" 24 "net/url" 25 "strconv" 26 "time" 27 ) 28 29 // copy & paste from generated code 30 31 // ControllerVersion represents version information of the LINSTOR controller 32 type ControllerVersion struct { 33 Version string `json:"version,omitempty"` 34 GitHash string `json:"git_hash,omitempty"` 35 BuildTime string `json:"build_time,omitempty"` 36 RestApiVersion string `json:"rest_api_version,omitempty"` 37 } 38 39 // ErrorReport struct for ErrorReport 40 type ErrorReport struct { 41 NodeName string `json:"node_name,omitempty"` 42 ErrorTime TimeStampMs `json:"error_time"` 43 // Filename of the error report on the server. Format is: ```ErrorReport-{instanceid}-{nodeid}-{sequencenumber}.log``` 44 Filename string `json:"filename,omitempty"` 45 // Contains the full text of the error report file. 46 Text string `json:"text,omitempty"` 47 // Which module this error occurred. 48 Module string `json:"module,omitempty"` 49 // Linstor version this error report was created. 50 Version string `json:"version,omitempty"` 51 // Peer client that was involved. 52 Peer string `json:"peer,omitempty"` 53 // Exception that occurred 54 Exception string `json:"exception,omitempty"` 55 // Exception message 56 ExceptionMessage string `json:"exception_message,omitempty"` 57 // Origin file of the exception 58 OriginFile string `json:"origin_file,omitempty"` 59 // Origin method where the exception occurred 60 OriginMethod string `json:"origin_method,omitempty"` 61 // Origin line number 62 OriginLine int32 `json:"origin_line,omitempty"` 63 } 64 65 type ErrorReportDelete struct { 66 Since *TimeStampMs `json:"since,omitempty"` 67 To *TimeStampMs `json:"to,omitempty"` 68 // on which nodes to delete error-reports, if empty/null all nodes 69 Nodes []string `json:"nodes,omitempty"` 70 // delete all error reports with the given exception 71 Exception string `json:"exception,omitempty"` 72 // delete all error reports from the given version 73 Version string `json:"version,omitempty"` 74 // error report ids to delete 75 Ids []string `json:"ids,omitempty"` 76 } 77 78 type PropsInfo struct { 79 Info string `json:"info,omitempty"` 80 PropType string `json:"prop_type,omitempty"` 81 Value string `json:"value,omitempty"` 82 Dflt string `json:"dflt,omitempty"` 83 Unit string `json:"unit,omitempty"` 84 } 85 86 // ExternalFile is an external file which can be configured to be deployed by Linstor 87 type ExternalFile struct { 88 Path string 89 Content []byte 90 } 91 92 // externalFileBase64 is a golinstor-internal type which represents an external 93 // file as it is handled by the LINSTOR API. The API expects files to come in 94 // base64 encoding, and also returns files in base64 encoding. To make golinstor 95 // easier to use, we only present the ExternalFile type to our users an 96 // transparently handle the base64 encoding/decoding. 97 type externalFileBase64 struct { 98 Path string `json:"path,omitempty"` 99 ContentBase64 string `json:"content,omitempty"` 100 } 101 102 func (e *ExternalFile) UnmarshalJSON(text []byte) error { 103 v := externalFileBase64{} 104 err := json.Unmarshal(text, &v) 105 if err != nil { 106 return err 107 } 108 e.Path = v.Path 109 e.Content, err = base64.StdEncoding.DecodeString(v.ContentBase64) 110 if err != nil { 111 return err 112 } 113 return nil 114 } 115 116 func (e *ExternalFile) MarshalJSON() ([]byte, error) { 117 v := externalFileBase64{ 118 Path: e.Path, 119 ContentBase64: base64.StdEncoding.EncodeToString(e.Content), 120 } 121 return json.Marshal(v) 122 } 123 124 // custom code 125 126 // ControllerProvider acts as an abstraction for a ControllerService. It can be 127 // swapped out for another ControllerService implementation, for example for 128 // testing. 129 type ControllerProvider interface { 130 // GetVersion queries version information for the controller. 131 GetVersion(ctx context.Context, opts ...*ListOpts) (ControllerVersion, error) 132 // GetConfig queries the configuration of a controller 133 GetConfig(ctx context.Context, opts ...*ListOpts) (ControllerConfig, error) 134 // Modify modifies the controller node and sets/deletes the given properties. 135 Modify(ctx context.Context, props GenericPropsModify) error 136 // GetProps gets all properties of a controller 137 GetProps(ctx context.Context, opts ...*ListOpts) (ControllerProps, error) 138 // DeleteProp deletes the given property/key from the controller object. 139 DeleteProp(ctx context.Context, prop string) error 140 // GetErrorReports returns all error reports. The Text field is not populated, 141 // use GetErrorReport to get the text of an error report. 142 GetErrorReports(ctx context.Context, opts ...*ListOpts) ([]ErrorReport, error) 143 // DeleteErrorReports deletes error reports as specified by the ErrorReportDelete struct. 144 DeleteErrorReports(ctx context.Context, del ErrorReportDelete) error 145 // GetErrorReportsSince returns all error reports created after a certain point in time. 146 GetErrorReportsSince(ctx context.Context, since time.Time, opts ...*ListOpts) ([]ErrorReport, error) 147 // GetErrorReport returns a specific error report, including its text. 148 GetErrorReport(ctx context.Context, id string, opts ...*ListOpts) (ErrorReport, error) 149 // CreateSOSReport creates an SOS report in the log directory of the controller 150 CreateSOSReport(ctx context.Context, opts ...*ListOpts) error 151 // DownloadSOSReport request sos report to download 152 DownloadSOSReport(ctx context.Context, opts ...*ListOpts) error 153 GetSatelliteConfig(ctx context.Context, node string) (SatelliteConfig, error) 154 ModifySatelliteConfig(ctx context.Context, node string, cfg SatelliteConfig) error 155 // GetPropsInfos gets meta information about the properties that can be 156 // set on a controller. 157 GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) 158 // GetPropsInfosAll gets meta information about all properties that can 159 // be set on a controller and all entities it contains (nodes, resource 160 // definitions, ...). 161 GetPropsInfosAll(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) 162 // GetExternalFiles gets a list of previously registered external files. 163 GetExternalFiles(ctx context.Context, opts ...*ListOpts) ([]ExternalFile, error) 164 // GetExternalFile gets the requested external file including its content 165 GetExternalFile(ctx context.Context, name string) (ExternalFile, error) 166 // ModifyExternalFile registers or modifies a previously registered 167 // external file 168 ModifyExternalFile(ctx context.Context, name string, file ExternalFile) error 169 // DeleteExternalFile deletes the given external file. This effectively 170 // also deletes the file on all satellites 171 DeleteExternalFile(ctx context.Context, name string) error 172 } 173 174 // ControllerService is the service that deals with the LINSTOR controller. 175 type ControllerService struct { 176 client *Client 177 } 178 179 // GetVersion queries version information for the controller. 180 func (s *ControllerService) GetVersion(ctx context.Context, opts ...*ListOpts) (ControllerVersion, error) { 181 var vers ControllerVersion 182 _, err := s.client.doGET(ctx, "/v1/controller/version", &vers, opts...) 183 return vers, err 184 } 185 186 // GetConfig queries the configuration of a controller 187 func (s *ControllerService) GetConfig(ctx context.Context, opts ...*ListOpts) (ControllerConfig, error) { 188 var cfg ControllerConfig 189 _, err := s.client.doGET(ctx, "/v1/controller/config", &cfg, opts...) 190 return cfg, err 191 } 192 193 // Modify modifies the controller node and sets/deletes the given properties. 194 func (s *ControllerService) Modify(ctx context.Context, props GenericPropsModify) error { 195 _, err := s.client.doPOST(ctx, "/v1/controller/properties", props) 196 return err 197 } 198 199 type ControllerProps map[string]string 200 201 // GetProps gets all properties of a controller 202 func (s *ControllerService) GetProps(ctx context.Context, opts ...*ListOpts) (ControllerProps, error) { 203 var props ControllerProps 204 _, err := s.client.doGET(ctx, "/v1/controller/properties", &props, opts...) 205 return props, err 206 } 207 208 // DeleteProp deletes the given property/key from the controller object. 209 func (s *ControllerService) DeleteProp(ctx context.Context, prop string) error { 210 _, err := s.client.doDELETE(ctx, "/v1/controller/properties/"+prop, nil) 211 return err 212 } 213 214 // GetPropsInfos gets meta information about the properties that can be set on 215 // a controller. 216 func (s *ControllerService) GetPropsInfos(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) { 217 var infos []PropsInfo 218 _, err := s.client.doGET(ctx, "/v1/controller/properties/info", &infos, opts...) 219 return infos, err 220 } 221 222 // GetPropsInfosAll gets meta information about all properties that can be set 223 // on a controller and all entities it contains (nodes, resource definitions, ...). 224 func (s *ControllerService) GetPropsInfosAll(ctx context.Context, opts ...*ListOpts) ([]PropsInfo, error) { 225 var infos []PropsInfo 226 _, err := s.client.doGET(ctx, "/v1/controller/properties/info/all", &infos, opts...) 227 return infos, err 228 } 229 230 // GetErrorReports returns all error reports. The Text field is not populated, 231 // use GetErrorReport to get the text of an error report. 232 func (s *ControllerService) GetErrorReports(ctx context.Context, opts ...*ListOpts) ([]ErrorReport, error) { 233 var reports []ErrorReport 234 _, err := s.client.doGET(ctx, "/v1/error-reports", &reports, opts...) 235 return reports, err 236 } 237 238 // DeleteErrorReports deletes error reports as specified by the ErrorReportDelete struct. 239 func (s *ControllerService) DeleteErrorReports(ctx context.Context, del ErrorReportDelete) error { 240 // Yes, this is using PATCH. don't ask me why, its just implemented that way... 241 _, err := s.client.doPATCH(ctx, "/v1/error-reports", del) 242 return err 243 } 244 245 // unixMilli returns t formatted as milliseconds since Unix epoch 246 func unixMilli(t time.Time) int64 { 247 return t.UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)) 248 } 249 250 // GetErrorReportsSince returns all error reports created after a certain point in time. 251 func (s *ControllerService) GetErrorReportsSince(ctx context.Context, since time.Time, opts ...*ListOpts) ([]ErrorReport, error) { 252 var reports []ErrorReport 253 v := url.Values{} 254 v.Set("since", strconv.FormatInt(unixMilli(since), 10)) 255 _, err := s.client.doGET(ctx, "/v1/error-reports/?"+v.Encode(), &reports, opts...) 256 return reports, err 257 } 258 259 // GetErrorReport returns a specific error report, including its text. 260 func (s *ControllerService) GetErrorReport(ctx context.Context, id string, opts ...*ListOpts) (ErrorReport, error) { 261 var report []ErrorReport 262 _, err := s.client.doGET(ctx, "/v1/error-reports/"+id, &report, opts...) 263 return report[0], err 264 } 265 266 // CreateSOSReport creates an SOS report in the log directory of the controller 267 func (s *ControllerService) CreateSOSReport(ctx context.Context, opts ...*ListOpts) error { 268 _, err := s.client.doGET(ctx, "/v1/sos-report", nil, opts...) 269 return err 270 } 271 272 // DownloadSOSReport request sos report to download 273 func (s *ControllerService) DownloadSOSReport(ctx context.Context, opts ...*ListOpts) error { 274 _, err := s.client.doGET(ctx, "/v1/sos-report/download", nil, opts...) 275 return err 276 } 277 278 func (s *ControllerService) GetSatelliteConfig(ctx context.Context, node string) (SatelliteConfig, error) { 279 var cfg SatelliteConfig 280 _, err := s.client.doGET(ctx, "/v1/nodes/"+node+"/config", &cfg) 281 return cfg, err 282 } 283 284 func (s *ControllerService) ModifySatelliteConfig(ctx context.Context, node string, cfg SatelliteConfig) error { 285 _, err := s.client.doPUT(ctx, "/v1/nodes/"+node+"/config", &cfg) 286 return err 287 } 288 289 // GetExternalFiles get a list of previously registered external files. 290 // File contents are not included, unless ListOpts.Content is true. 291 func (s *ControllerService) GetExternalFiles(ctx context.Context, opts ...*ListOpts) ([]ExternalFile, error) { 292 var files []ExternalFile 293 _, err := s.client.doGET(ctx, "/v1/files", &files, opts...) 294 return files, err 295 } 296 297 // GetExternalFile gets the requested external file including its content 298 func (s *ControllerService) GetExternalFile(ctx context.Context, name string) (ExternalFile, error) { 299 file := ExternalFile{} 300 _, err := s.client.doGET(ctx, "/v1/files/"+url.QueryEscape(name), &file) 301 if err != nil { 302 return ExternalFile{}, fmt.Errorf("request failed: %w", err) 303 } 304 return file, nil 305 } 306 307 // ModifyExternalFile registers or modifies a previously registered external 308 // file 309 func (s *ControllerService) ModifyExternalFile(ctx context.Context, name string, file ExternalFile) error { 310 b64file := externalFileBase64{ 311 Path: file.Path, 312 ContentBase64: base64.StdEncoding.EncodeToString(file.Content), 313 } 314 _, err := s.client.doPUT(ctx, "/v1/files/"+url.QueryEscape(name), b64file) 315 return err 316 } 317 318 // DeleteExternalFile deletes the given external file. This effectively also 319 // deletes the file on all satellites 320 func (s *ControllerService) DeleteExternalFile(ctx context.Context, name string) error { 321 _, err := s.client.doDELETE(ctx, "/v1/files/"+url.QueryEscape(name), nil) 322 return err 323 }