github.com/minio/madmin-go/v3@v3.0.51/service-commands.go (about) 1 // 2 // Copyright (c) 2015-2024 MinIO, Inc. 3 // 4 // This file is part of MinIO Object Storage stack 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU Affero General Public License as 8 // published by the Free Software Foundation, either version 3 of the 9 // License, or (at your option) any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU Affero General Public License for more details. 15 // 16 // You should have received a copy of the GNU Affero General Public License 17 // along with this program. If not, see <http://www.gnu.org/licenses/>. 18 // 19 20 package madmin 21 22 import ( 23 "context" 24 "encoding/json" 25 "net/http" 26 "net/url" 27 "strconv" 28 "strings" 29 "time" 30 ) 31 32 // ServiceRestartV2 - restarts the MinIO cluster 33 func (adm *AdminClient) ServiceRestartV2(ctx context.Context) error { 34 _, err := adm.serviceCallActionV2(ctx, ServiceActionOpts{Action: ServiceActionRestart}) 35 return err 36 } 37 38 // ServiceStopV2 - stops the MinIO cluster 39 func (adm *AdminClient) ServiceStopV2(ctx context.Context) error { 40 _, err := adm.serviceCallActionV2(ctx, ServiceActionOpts{Action: ServiceActionStop}) 41 return err 42 } 43 44 // ServiceFreezeV2 - freezes all incoming S3 API calls on MinIO cluster 45 func (adm *AdminClient) ServiceFreezeV2(ctx context.Context) error { 46 _, err := adm.serviceCallActionV2(ctx, ServiceActionOpts{Action: ServiceActionFreeze}) 47 return err 48 } 49 50 // ServiceUnfreezeV2 - un-freezes all incoming S3 API calls on MinIO cluster 51 func (adm *AdminClient) ServiceUnfreezeV2(ctx context.Context) error { 52 _, err := adm.serviceCallActionV2(ctx, ServiceActionOpts{Action: ServiceActionUnfreeze}) 53 return err 54 } 55 56 // ServiceAction - type to restrict service-action values 57 type ServiceAction string 58 59 const ( 60 // ServiceActionRestart represents restart action 61 ServiceActionRestart ServiceAction = "restart" 62 // ServiceActionStop represents stop action 63 ServiceActionStop = "stop" 64 // ServiceActionFreeze represents freeze action 65 ServiceActionFreeze = "freeze" 66 // ServiceActionUnfreeze represents unfreeze a previous freeze action 67 ServiceActionUnfreeze = "unfreeze" 68 ) 69 70 // ServiceActionOpts specifies the action that the service is requested 71 // to take, dryRun indicates if the action is a no-op, force indicates 72 // that server must make best effort to restart the process. 73 type ServiceActionOpts struct { 74 Action ServiceAction 75 DryRun bool 76 } 77 78 // ServiceActionPeerResult service peer result 79 type ServiceActionPeerResult struct { 80 Host string `json:"host"` 81 Err string `json:"err,omitempty"` 82 WaitingDrives map[string]DiskMetrics `json:"waitingDrives,omitempty"` 83 } 84 85 // ServiceActionResult service action result 86 type ServiceActionResult struct { 87 Action ServiceAction `json:"action"` 88 DryRun bool `json:"dryRun"` 89 Results []ServiceActionPeerResult `json:"results,omitempty"` 90 } 91 92 // ServiceAction - specify the type of service action that we are requesting the server to perform 93 func (adm *AdminClient) ServiceAction(ctx context.Context, opts ServiceActionOpts) (ServiceActionResult, error) { 94 return adm.serviceCallActionV2(ctx, opts) 95 } 96 97 // serviceCallActionV2 - call service restart/stop/freeze/unfreeze 98 func (adm *AdminClient) serviceCallActionV2(ctx context.Context, opts ServiceActionOpts) (ServiceActionResult, error) { 99 queryValues := url.Values{} 100 queryValues.Set("action", string(opts.Action)) 101 queryValues.Set("dry-run", strconv.FormatBool(opts.DryRun)) 102 queryValues.Set("type", "2") 103 104 // Request API to Restart server 105 resp, err := adm.executeMethod(ctx, 106 http.MethodPost, requestData{ 107 relPath: adminAPIPrefix + "/service", 108 queryValues: queryValues, 109 }, 110 ) 111 defer closeResponse(resp) 112 if err != nil { 113 return ServiceActionResult{}, err 114 } 115 116 if resp.StatusCode != http.StatusOK { 117 return ServiceActionResult{}, httpRespToErrorResponse(resp) 118 } 119 120 srvRes := ServiceActionResult{} 121 dec := json.NewDecoder(resp.Body) 122 if err = dec.Decode(&srvRes); err != nil { 123 return ServiceActionResult{}, err 124 } 125 126 return srvRes, nil 127 } 128 129 // ServiceTraceInfo holds http trace 130 type ServiceTraceInfo struct { 131 Trace TraceInfo 132 Err error `json:"-"` 133 } 134 135 // ServiceTraceOpts holds tracing options 136 type ServiceTraceOpts struct { 137 // Trace types: 138 S3 bool 139 Internal bool 140 Storage bool 141 OS bool 142 Scanner bool 143 Decommission bool 144 Healing bool 145 BatchReplication bool 146 BatchKeyRotation bool 147 BatchExpire bool 148 BatchAll bool 149 Rebalance bool 150 ReplicationResync bool 151 Bootstrap bool 152 FTP bool 153 ILM bool 154 OnlyErrors bool 155 Threshold time.Duration 156 } 157 158 // TraceTypes returns the enabled traces as a bitfield value. 159 func (t ServiceTraceOpts) TraceTypes() TraceType { 160 var tt TraceType 161 tt.SetIf(t.S3, TraceS3) 162 tt.SetIf(t.Internal, TraceInternal) 163 tt.SetIf(t.Storage, TraceStorage) 164 tt.SetIf(t.OS, TraceOS) 165 tt.SetIf(t.Scanner, TraceScanner) 166 tt.SetIf(t.Decommission, TraceDecommission) 167 tt.SetIf(t.Healing, TraceHealing) 168 if t.BatchAll { 169 tt.SetIf(true, TraceBatchReplication) 170 tt.SetIf(true, TraceBatchKeyRotation) 171 tt.SetIf(true, TraceBatchExpire) 172 } else { 173 tt.SetIf(t.BatchReplication, TraceBatchReplication) 174 tt.SetIf(t.BatchKeyRotation, TraceBatchKeyRotation) 175 tt.SetIf(t.BatchExpire, TraceBatchExpire) 176 } 177 178 tt.SetIf(t.Rebalance, TraceRebalance) 179 tt.SetIf(t.ReplicationResync, TraceReplicationResync) 180 tt.SetIf(t.Bootstrap, TraceBootstrap) 181 tt.SetIf(t.FTP, TraceFTP) 182 tt.SetIf(t.ILM, TraceILM) 183 184 return tt 185 } 186 187 // AddParams will add parameter to url values. 188 func (t ServiceTraceOpts) AddParams(u url.Values) { 189 u.Set("err", strconv.FormatBool(t.OnlyErrors)) 190 u.Set("threshold", t.Threshold.String()) 191 192 u.Set("s3", strconv.FormatBool(t.S3)) 193 u.Set("internal", strconv.FormatBool(t.Internal)) 194 u.Set("storage", strconv.FormatBool(t.Storage)) 195 u.Set("os", strconv.FormatBool(t.OS)) 196 u.Set("scanner", strconv.FormatBool(t.Scanner)) 197 u.Set("decommission", strconv.FormatBool(t.Decommission)) 198 u.Set("healing", strconv.FormatBool(t.Healing)) 199 u.Set("batch-replication", strconv.FormatBool(t.BatchReplication)) 200 u.Set("batch-keyrotation", strconv.FormatBool(t.BatchKeyRotation)) 201 u.Set("batch-expire", strconv.FormatBool(t.BatchExpire)) 202 if t.BatchAll { 203 u.Set("batch-replication", "true") 204 u.Set("batch-keyrotation", "true") 205 u.Set("batch-expire", "true") 206 } 207 u.Set("rebalance", strconv.FormatBool(t.Rebalance)) 208 u.Set("replication-resync", strconv.FormatBool(t.ReplicationResync)) 209 u.Set("bootstrap", strconv.FormatBool(t.Bootstrap)) 210 u.Set("ftp", strconv.FormatBool(t.FTP)) 211 u.Set("ilm", strconv.FormatBool(t.ILM)) 212 } 213 214 // ParseParams will parse parameters and set them to t. 215 func (t *ServiceTraceOpts) ParseParams(r *http.Request) (err error) { 216 t.S3 = r.Form.Get("s3") == "true" 217 t.OS = r.Form.Get("os") == "true" 218 t.Scanner = r.Form.Get("scanner") == "true" 219 t.Decommission = r.Form.Get("decommission") == "true" 220 t.Healing = r.Form.Get("healing") == "true" 221 t.BatchReplication = r.Form.Get("batch-replication") == "true" 222 t.BatchKeyRotation = r.Form.Get("batch-keyrotation") == "true" 223 t.BatchExpire = r.Form.Get("batch-expire") == "true" 224 t.Rebalance = r.Form.Get("rebalance") == "true" 225 t.Storage = r.Form.Get("storage") == "true" 226 t.Internal = r.Form.Get("internal") == "true" 227 t.OnlyErrors = r.Form.Get("err") == "true" 228 t.ReplicationResync = r.Form.Get("replication-resync") == "true" 229 t.Bootstrap = r.Form.Get("bootstrap") == "true" 230 t.FTP = r.Form.Get("ftp") == "true" 231 t.ILM = r.Form.Get("ilm") == "true" 232 233 if th := r.Form.Get("threshold"); th != "" { 234 d, err := time.ParseDuration(th) 235 if err != nil { 236 return err 237 } 238 t.Threshold = d 239 } 240 return nil 241 } 242 243 // ServiceTrace - listen on http trace notifications. 244 func (adm AdminClient) ServiceTrace(ctx context.Context, opts ServiceTraceOpts) <-chan ServiceTraceInfo { 245 traceInfoCh := make(chan ServiceTraceInfo) 246 // Only success, start a routine to start reading line by line. 247 go func(traceInfoCh chan<- ServiceTraceInfo) { 248 defer close(traceInfoCh) 249 for { 250 urlValues := make(url.Values) 251 opts.AddParams(urlValues) 252 253 reqData := requestData{ 254 relPath: adminAPIPrefix + "/trace", 255 queryValues: urlValues, 256 } 257 // Execute GET to call trace handler 258 resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) 259 if err != nil { 260 traceInfoCh <- ServiceTraceInfo{Err: err} 261 return 262 } 263 264 if resp.StatusCode != http.StatusOK { 265 closeResponse(resp) 266 traceInfoCh <- ServiceTraceInfo{Err: httpRespToErrorResponse(resp)} 267 return 268 } 269 270 dec := json.NewDecoder(resp.Body) 271 for { 272 var info traceInfoLegacy 273 if err = dec.Decode(&info); err != nil { 274 closeResponse(resp) 275 traceInfoCh <- ServiceTraceInfo{Err: err} 276 break 277 } 278 // Convert if legacy... 279 if info.TraceType == TraceType(0) { 280 if strings.HasPrefix(info.FuncName, "s3.") { 281 info.TraceType = TraceS3 282 } else { 283 info.TraceType = TraceInternal 284 } 285 info.HTTP = &TraceHTTPStats{} 286 if info.ReqInfo != nil { 287 info.Path = info.ReqInfo.Path 288 info.HTTP.ReqInfo = *info.ReqInfo 289 } 290 if info.RespInfo != nil { 291 info.HTTP.RespInfo = *info.RespInfo 292 } 293 if info.CallStats != nil { 294 info.Duration = info.CallStats.Latency 295 info.HTTP.CallStats = *info.CallStats 296 } 297 } 298 if info.TraceType == TraceOS && info.OSStats != nil { 299 info.Path = info.OSStats.Path 300 info.Duration = info.OSStats.Duration 301 } 302 if info.TraceType == TraceStorage && info.StorageStats != nil { 303 info.Path = info.StorageStats.Path 304 info.Duration = info.StorageStats.Duration 305 } 306 select { 307 case <-ctx.Done(): 308 closeResponse(resp) 309 return 310 case traceInfoCh <- ServiceTraceInfo{Trace: info.TraceInfo}: 311 } 312 } 313 } 314 }(traceInfoCh) 315 316 // Returns the trace info channel, for caller to start reading from. 317 return traceInfoCh 318 }