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  }