github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/support-top-locks.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 "time" 22 23 humanize "github.com/dustin/go-humanize" 24 "github.com/fatih/color" 25 "github.com/minio/cli" 26 json "github.com/minio/colorjson" 27 "github.com/minio/madmin-go/v3" 28 "github.com/minio/mc/pkg/probe" 29 "github.com/minio/pkg/v2/console" 30 ) 31 32 var supportTopLocksFlag = []cli.Flag{ 33 cli.BoolFlag{ 34 Name: "stale", 35 Usage: "list all stale locks", 36 Hidden: true, 37 }, 38 cli.IntFlag{ 39 Name: "count", 40 Usage: "list N number of locks", 41 Hidden: true, 42 Value: 10, 43 }, 44 } 45 46 var supportTopLocksCmd = cli.Command{ 47 Name: "locks", 48 Usage: "list all active locks on a MinIO cluster", 49 Before: setGlobalsFromContext, 50 Action: mainSupportTopLocks, 51 OnUsageError: onUsageError, 52 Flags: append(supportTopLocksFlag, supportGlobalFlags...), 53 CustomHelpTemplate: `NAME: 54 {{.HelpName}} - {{.Usage}} 55 56 USAGE: 57 {{.HelpName}} TARGET 58 59 FLAGS: 60 {{range .VisibleFlags}}{{.}} 61 {{end}} 62 EXAMPLES: 63 1. List oldest locks on a MinIO cluster. 64 {{.Prompt}} {{.HelpName}} myminio/ 65 `, 66 } 67 68 // lockMessage struct to list lock information. 69 type lockMessage struct { 70 Status string `json:"status"` 71 Lock madmin.LockEntry `json:"locks"` 72 } 73 74 // String colorized oldest locks message. 75 func (u lockMessage) String() string { 76 elapsed := u.Lock.Elapsed 77 // elapsed can be zero with older MinIO versions, 78 // so this code is deprecated and can be removed later. 79 if elapsed == 0 { 80 elapsed = time.Now().UTC().Sub(u.Lock.Timestamp) 81 } 82 83 stale := u.Lock.Quorum > len(u.Lock.ServerList) 84 lockState := "Lock" 85 if stale { 86 lockState = "StaleLock" 87 } 88 89 return console.Colorize(lockState, newPrettyTable(" ", 90 Field{"Since", timeFieldMaxLen}, 91 Field{"Type", typeFieldMaxLen}, 92 Field{"Owner", timeFieldMaxLen}, 93 Field{"Resource", resourceFieldMaxLen}, 94 ).buildRow(humanize.Time(time.Now().UTC().Add(-elapsed)), u.Lock.Type, u.Lock.Owner, u.Lock.Resource)) 95 } 96 97 // JSON jsonified top oldest locks message. 98 func (u lockMessage) JSON() string { 99 type lockEntry struct { 100 Timestamp time.Time `json:"time"` // When the lock was first granted 101 Elapsed string `json:"elapsed"` // Humanized duration for which lock has been held 102 Resource string `json:"resource"` // Resource contains info like bucket+object 103 Type string `json:"type"` // Type indicates if 'Write' or 'Read' lock 104 Source string `json:"source"` // Source at which lock was granted 105 ServerList []string `json:"serverlist"` // List of servers participating in the lock. 106 Owner string `json:"owner"` // Owner UUID indicates server owns the lock. 107 ID string `json:"id"` // UID to uniquely identify request of client. 108 // Represents quorum number of servers required to hold this lock, used to look for stale locks. 109 Quorum int `json:"quorum"` 110 Stale bool // Represents if the lock is stale. 111 } 112 113 le := lockEntry{ 114 Timestamp: u.Lock.Timestamp, 115 Elapsed: u.Lock.Elapsed.Round(time.Second).String(), 116 Resource: u.Lock.Resource, 117 Type: u.Lock.Type, 118 Source: u.Lock.Source, 119 ServerList: u.Lock.ServerList, 120 Owner: u.Lock.Owner, 121 ID: u.Lock.ID, 122 Quorum: u.Lock.Quorum, 123 Stale: u.Lock.Quorum > len(u.Lock.ServerList), 124 } 125 statusJSONBytes, e := json.MarshalIndent(le, "", " ") 126 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 127 return string(statusJSONBytes) 128 } 129 130 // checkAdminTopLocksSyntax - validate all the passed arguments 131 func checkSupportTopLocksSyntax(ctx *cli.Context) { 132 if len(ctx.Args()) == 0 || len(ctx.Args()) > 1 { 133 showCommandHelpAndExit(ctx, 1) // last argument is exit code 134 } 135 } 136 137 func mainSupportTopLocks(ctx *cli.Context) error { 138 checkSupportTopLocksSyntax(ctx) 139 // Get the alias parameter from cli 140 args := ctx.Args() 141 aliasedURL := args.Get(0) 142 alias, _ := url2Alias(aliasedURL) 143 validateClusterRegistered(alias, false) 144 145 console.SetColor("StaleLock", color.New(color.FgRed, color.Bold)) 146 console.SetColor("Lock", color.New(color.FgBlue, color.Bold)) 147 console.SetColor("Headers", color.New(color.FgGreen, color.Bold)) 148 149 // Create a new MinIO Admin Client 150 client, err := newAdminClient(aliasedURL) 151 fatalIf(err, "Unable to initialize admin connection.") 152 153 // Call top locks API 154 entries, e := client.TopLocksWithOpts(globalContext, madmin.TopLockOpts{ 155 Count: ctx.Int("count"), 156 Stale: ctx.Bool("stale"), 157 }) 158 fatalIf(probe.NewError(e), "Unable to get server locks list.") 159 160 // Print 161 printLocks(entries) 162 return nil 163 } 164 165 const ( 166 timeFieldMaxLen = 20 167 typeFieldMaxLen = 6 168 resourceFieldMaxLen = 150 169 ) 170 171 func printHeaders() { 172 console.Println(console.Colorize("Headers", newPrettyTable(" ", 173 Field{"Since", timeFieldMaxLen}, 174 Field{"Type", typeFieldMaxLen}, 175 Field{"Owner", timeFieldMaxLen}, 176 Field{"Resource", resourceFieldMaxLen}, 177 ).buildRow("Since", "Type", "Owner", "Resource"))) 178 } 179 180 // Prints oldest locks. 181 func printLocks(locks madmin.LockEntries) { 182 if !globalJSON { 183 printHeaders() 184 } 185 for _, entry := range locks { 186 printMsg(lockMessage{Lock: entry}) 187 } 188 }