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  }