1// Copyright (c) 2015-2021 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 18package cmd 19 20import ( 21 "errors" 22 "strconv" 23 24 "github.com/dustin/go-humanize" 25 "github.com/fatih/color" 26 "github.com/minio/cli" 27 json "github.com/minio/colorjson" 28 "github.com/minio/madmin-go" 29 "github.com/minio/mc/pkg/probe" 30 "github.com/minio/pkg/console" 31) 32 33var adminTierInfoCmd = cli.Command{ 34 Name: "info", 35 Usage: "Displays per-tier statistics of all tier targets", 36 Action: mainAdminTierInfo, 37 OnUsageError: onUsageError, 38 Before: setGlobalsFromContext, 39 Flags: globalFlags, 40 CustomHelpTemplate: `NAME: 41 {{.HelpName}} - {{.Usage}} 42 43USAGE: 44 {{.HelpName}} TARGET 45 46FLAGS: 47 {{range .VisibleFlags}}{{.}} 48 {{end}} 49 50EXAMPLES: 51 1. Prints per-tier statistics of all remote tier targets configured in myminio 52 {{.Prompt}} {{.HelpName}} myminio 53`, 54} 55 56// checkAdminTierInfoSyntax - validate all the passed arguments 57func checkAdminTierInfoSyntax(ctx *cli.Context) { 58 argsNr := len(ctx.Args()) 59 if argsNr < 1 { 60 cli.ShowCommandHelpAndExit(ctx, ctx.Command.Name, 1) // last argument is exit code 61 } 62 if argsNr > 1 { 63 fatalIf(errInvalidArgument().Trace(ctx.Args().Tail()...), 64 "Incorrect number of arguments for tier-info subcommand.") 65 } 66} 67 68type tierInfoRowHdr int 69 70const ( 71 tierInfoNameHdr tierInfoRowHdr = iota 72 tierInfoAPIHdr 73 tierInfoTypeHdr 74 tierInfoUsageHdr 75 tierInfoObjectsHdr 76 tierInfoVersionsHdr 77) 78 79var tierInfoRowNames = []string{ 80 "Tier Name", 81 "API", 82 "Type", 83 "Usage", 84 "Objects", 85 "Versions", 86} 87 88var tierInfoColorScheme = []*color.Color{ 89 color.New(color.FgYellow), 90 color.New(color.FgCyan), 91 color.New(color.FgCyan), 92 color.New(color.FgHiWhite), 93 color.New(color.FgHiWhite), 94 color.New(color.FgHiWhite), 95} 96 97type tierInfos []madmin.TierInfo 98 99func (t tierInfos) NumRows() int { 100 return len([]madmin.TierInfo(t)) 101} 102 103func (t tierInfos) NumCols() int { 104 return len(tierInfoRowNames) 105} 106func (t tierInfos) EmptyMessage() string { 107 return "No remote tiers configured." 108} 109 110func (t tierInfos) MarshalJSON() ([]byte, error) { 111 type tierInfo struct { 112 Name string 113 API string 114 Type string 115 Stats madmin.TierStats 116 } 117 ts := make([]tierInfo, 0, len(t)) 118 for _, tInfo := range t { 119 ts = append(ts, tierInfo{ 120 Name: tInfo.Name, 121 API: tierInfoAPI(tInfo.Type), 122 Type: tierInfoType(tInfo.Type), 123 Stats: tInfo.Stats, 124 }) 125 } 126 return json.Marshal(ts) 127} 128 129func tierInfoAPI(tierType string) string { 130 switch tierType { 131 case madmin.S3.String(), madmin.GCS.String(): 132 return tierType 133 case madmin.Azure.String(): 134 return "blob" 135 case "internal": 136 return madmin.S3.String() 137 default: 138 return "unknown" 139 } 140} 141 142func tierInfoType(tierType string) string { 143 if tierType == "internal" { 144 return "hot" 145 } 146 return "warm" 147} 148 149func (t tierInfos) ToRow(i int, ls []int) []string { 150 row := make([]string, len(tierInfoRowNames)) 151 if i == -1 { 152 copy(row, tierInfoRowNames) 153 } else { 154 tierInfo := t[i] 155 row[tierInfoNameHdr] = tierInfo.Name 156 row[tierInfoAPIHdr] = tierInfoAPI(tierInfo.Type) 157 row[tierInfoTypeHdr] = tierInfoType(tierInfo.Type) 158 row[tierInfoUsageHdr] = humanize.IBytes(tierInfo.Stats.TotalSize) 159 row[tierInfoObjectsHdr] = strconv.Itoa(tierInfo.Stats.NumObjects) 160 row[tierInfoVersionsHdr] = strconv.Itoa(tierInfo.Stats.NumVersions) 161 } 162 163 // update ls to accommodate this row's values 164 for i := range tierInfoRowNames { 165 if ls[i] < len(row[i]) { 166 ls[i] = len(row[i]) 167 } 168 } 169 return row 170} 171 172func mainAdminTierInfo(ctx *cli.Context) error { 173 checkAdminTierInfoSyntax(ctx) 174 175 for i, color := range tierInfoColorScheme { 176 console.SetColor(tierInfoRowNames[i], color) 177 } 178 179 args := ctx.Args() 180 aliasedURL := args.Get(0) 181 182 // Create a new MinIO Admin Client 183 client, cerr := newAdminClient(aliasedURL) 184 fatalIf(cerr, "Unable to initialize admin connection.") 185 186 var msg tierInfoMessage 187 tInfos, err := client.TierStats(globalContext) 188 if err != nil { 189 msg = tierInfoMessage{ 190 Status: "error", 191 Context: ctx, 192 Error: err.Error(), 193 } 194 } else { 195 msg = tierInfoMessage{ 196 Status: "success", 197 Context: ctx, 198 TierInfos: tierInfos(tInfos), 199 } 200 } 201 printMsg(&msg) 202 return nil 203} 204 205type tierInfoMessage struct { 206 Status string `json:"status"` 207 Context *cli.Context `json:"-"` 208 TierInfos tierInfos `json:"tiers,omitempty"` 209 Error string `json:"error,omitempty"` 210} 211 212// String method returns a tabular listing of remote tier configurations. 213func (msg *tierInfoMessage) String() string { 214 if msg.Status == "error" { 215 fatal(probe.NewError(errors.New(msg.Error)), "Unable to get tier statistics") 216 } 217 return toTable(tierInfos(msg.TierInfos)) 218} 219 220// JSON method returns JSON encoding of msg. 221func (msg *tierInfoMessage) JSON() string { 222 b, _ := json.Marshal(msg) 223 return string(b) 224} 225