github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/admin-service-restart.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "fmt" 23 "strings" 24 "time" 25 26 "github.com/fatih/color" 27 "github.com/jedib0t/go-pretty/v6/table" 28 "github.com/jedib0t/go-pretty/v6/text" 29 "github.com/minio/cli" 30 json "github.com/minio/colorjson" 31 "github.com/minio/madmin-go/v3" 32 "github.com/minio/mc/pkg/probe" 33 "github.com/minio/pkg/v2/console" 34 ) 35 36 var serviceRestartFlag = []cli.Flag{ 37 cli.BoolFlag{ 38 Name: "dry-run", 39 Usage: "do not attempt a restart, however verify the peer status", 40 }, 41 } 42 43 var adminServiceRestartCmd = cli.Command{ 44 Name: "restart", 45 Usage: "restart a MinIO cluster", 46 Action: mainAdminServiceRestart, 47 OnUsageError: onUsageError, 48 Before: setGlobalsFromContext, 49 Flags: append(serviceRestartFlag, globalFlags...), 50 CustomHelpTemplate: `NAME: 51 {{.HelpName}} - {{.Usage}} 52 53 USAGE: 54 {{.HelpName}} TARGET 55 56 FLAGS: 57 {{range .VisibleFlags}}{{.}} 58 {{end}} 59 EXAMPLES: 60 1. Restart MinIO server represented by its alias 'play'. 61 {{.Prompt}} {{.HelpName}} play/ 62 `, 63 } 64 65 // serviceRestartCommand is container for service restart command success and failure messages. 66 type serviceRestartCommand struct { 67 Status string `json:"status"` 68 ServerURL string `json:"serverURL"` 69 Result madmin.ServiceActionResult `json:"result"` 70 } 71 72 // String colorized service restart command message. 73 func (s serviceRestartCommand) String() string { 74 var s1 strings.Builder 75 s1.WriteString("Restart command successfully sent to `" + s.ServerURL + "`. Type Ctrl-C to quit or wait to follow the status of the restart process.") 76 77 if len(s.Result.Results) > 0 { 78 s1.WriteString("\n") 79 var rows []table.Row 80 for _, peerRes := range s.Result.Results { 81 errStr := tickCell 82 if peerRes.Err != "" { 83 errStr = peerRes.Err 84 } else if len(peerRes.WaitingDrives) > 0 { 85 errStr = fmt.Sprintf("%d drives are waiting for I/O and are offline, manual restart of OS is recommended", len(peerRes.WaitingDrives)) 86 } 87 rows = append(rows, table.Row{peerRes.Host, errStr}) 88 } 89 90 t := table.NewWriter() 91 t.SetOutputMirror(&s1) 92 t.SetColumnConfigs([]table.ColumnConfig{{Align: text.AlignCenter}}) 93 94 t.AppendHeader(table.Row{"Host", "Status"}) 95 t.AppendRows(rows) 96 t.SetStyle(table.StyleLight) 97 t.Render() 98 } 99 100 return console.Colorize("ServiceRestart", s1.String()) 101 } 102 103 // JSON jsonified service restart command message. 104 func (s serviceRestartCommand) JSON() string { 105 serviceRestartJSONBytes, e := json.MarshalIndent(s, "", " ") 106 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 107 108 return string(serviceRestartJSONBytes) 109 } 110 111 // serviceRestartMessage is container for service restart success and failure messages. 112 type serviceRestartMessage struct { 113 Status string `json:"status"` 114 ServerURL string `json:"serverURL"` 115 TimeTaken time.Duration `json:"timeTaken"` 116 Err error `json:"error,omitempty"` 117 } 118 119 // String colorized service restart message. 120 func (s serviceRestartMessage) String() string { 121 if s.Err == nil { 122 return console.Colorize("ServiceRestart", fmt.Sprintf("\nRestarted `%s` successfully in %s", s.ServerURL, timeDurationToHumanizedDuration(s.TimeTaken).StringShort())) 123 } 124 return console.Colorize("FailedServiceRestart", "Failed to restart `"+s.ServerURL+"`. error: "+s.Err.Error()) 125 } 126 127 // JSON jsonified service restart message. 128 func (s serviceRestartMessage) JSON() string { 129 serviceRestartJSONBytes, e := json.MarshalIndent(s, "", " ") 130 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 131 132 return string(serviceRestartJSONBytes) 133 } 134 135 // checkAdminServiceRestartSyntax - validate all the passed arguments 136 func checkAdminServiceRestartSyntax(ctx *cli.Context) { 137 if len(ctx.Args()) != 1 { 138 showCommandHelpAndExit(ctx, 1) // last argument is exit code 139 } 140 } 141 142 func mainAdminServiceRestart(ctx *cli.Context) error { 143 // Validate serivce restart syntax. 144 checkAdminServiceRestartSyntax(ctx) 145 146 ctxt, cancel := context.WithCancel(globalContext) 147 defer cancel() 148 149 // Set color. 150 console.SetColor("ServiceOffline", color.New(color.FgRed, color.Bold)) 151 console.SetColor("ServiceInitializing", color.New(color.FgYellow, color.Bold)) 152 console.SetColor("ServiceRestart", color.New(color.FgGreen, color.Bold)) 153 console.SetColor("FailedServiceRestart", color.New(color.FgRed, color.Bold)) 154 155 // Get the alias parameter from cli 156 args := ctx.Args() 157 aliasedURL := args.Get(0) 158 159 client, err := newAdminClient(aliasedURL) 160 fatalIf(err, "Unable to initialize admin connection.") 161 162 // Restart the specified MinIO server 163 result, e := client.ServiceAction(ctxt, madmin.ServiceActionOpts{ 164 Action: madmin.ServiceActionRestart, 165 DryRun: ctx.Bool("dry-run"), 166 }) 167 if e != nil { 168 // Attempt an older API server might be old 169 e = client.ServiceRestart(ctxt) 170 } 171 fatalIf(probe.NewError(e), "Unable to restart the server.") 172 173 // Success.. 174 printMsg(serviceRestartCommand{Status: "success", ServerURL: aliasedURL, Result: result}) 175 176 // Start pinging the service until it is ready 177 178 anonClient, err := newAnonymousClient(aliasedURL) 179 fatalIf(err.Trace(aliasedURL), "Could not ping `"+aliasedURL+"`.") 180 181 coloring := color.New(color.FgRed) 182 mark := "..." 183 184 // Print restart progress 185 printProgress := func() { 186 if !globalQuiet && !globalJSON { 187 coloring.Printf(mark) 188 } 189 } 190 191 printProgress() 192 mark = "." 193 194 t := time.Now() 195 for { 196 healthCtx, healthCancel := context.WithTimeout(ctxt, 2*time.Second) 197 198 // Fetch the health status of the specified MinIO server 199 healthResult, healthErr := anonClient.Healthy(healthCtx, madmin.HealthOpts{}) 200 healthCancel() 201 202 switch { 203 case healthErr == nil && healthResult.Healthy: 204 printMsg(serviceRestartMessage{ 205 Status: "success", 206 ServerURL: aliasedURL, 207 TimeTaken: time.Since(t), 208 }) 209 return nil 210 case healthErr == nil && !healthResult.Healthy: 211 coloring = color.New(color.FgYellow) 212 mark = "!" 213 fallthrough 214 default: 215 printProgress() 216 } 217 218 select { 219 case <-ctxt.Done(): 220 return ctxt.Err() 221 default: 222 time.Sleep(500 * time.Millisecond) 223 } 224 } 225 }