github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmd/xmeta/xmeta.go (about) 1 // Package xmeta provides low-level tools to format or extract 2 // into plain text some of the AIS control structures. 3 /* 4 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 5 */ 6 package main 7 8 import ( 9 "errors" 10 "flag" 11 "fmt" 12 "os" 13 "strings" 14 15 "github.com/NVIDIA/aistore/cmn" 16 "github.com/NVIDIA/aistore/cmn/cos" 17 "github.com/NVIDIA/aistore/cmn/jsp" 18 "github.com/NVIDIA/aistore/core" 19 "github.com/NVIDIA/aistore/core/meta" 20 "github.com/NVIDIA/aistore/core/mock" 21 "github.com/NVIDIA/aistore/ec" 22 "github.com/NVIDIA/aistore/fs" 23 "github.com/NVIDIA/aistore/volume" 24 jsoniter "github.com/json-iterator/go" 25 ) 26 27 // TODO: can LOM be used? LOM.Copies outside of a target has a lot of empty fields. 28 type lomInfo struct { 29 Attrs *cmn.ObjAttrs `json:"attrs"` 30 Copies []string `json:"copies,omitempty"` 31 } 32 33 var flags struct { 34 in, out string 35 format string 36 extract bool 37 help bool 38 } 39 40 const ( 41 helpMsg = `Build: 42 go install xmeta.go 43 44 Examples: 45 xmeta -h - show usage 46 # Smap: 47 xmeta -x -in=~/.ais0/.ais.smap - extract Smap to STDOUT 48 xmeta -x -in=~/.ais0/.ais.smap -out=/tmp/smap.txt - extract Smap to /tmp/smap.txt 49 xmeta -x -in=./.ais.smap -f smap - extract Smap to STDOUT with explicit source format 50 xmeta -in=/tmp/smap.txt -out=/tmp/.ais.smap - format plain-text /tmp/smap.txt 51 # BMD: 52 xmeta -x -in=~/.ais0/.ais.bmd - extract BMD to STDOUT 53 xmeta -x -in=~/.ais0/.ais.bmd -out=/tmp/bmd.txt - extract BMD to /tmp/bmd.txt 54 xmeta -x -in=./.ais.bmd -f bmd - extract BMD to STDOUT with explicit source format 55 xmeta -in=/tmp/bmd.txt -out=/tmp/.ais.bmd - format plain-text /tmp/bmd.txt 56 # RMD: 57 xmeta -x -in=~/.ais0/.ais.rmd - extract RMD to STDOUT 58 xmeta -x -in=~/.ais0/.ais.rmd -out=/tmp/rmd.txt - extract RMD to /tmp/rmd.txt 59 xmeta -x -in=./.ais.rmd -f rmd - extract RMD to STDOUT with explicit source format 60 xmeta -in=/tmp/rmd.txt -out=/tmp/.ais.rmd - format plain-text /tmp/rmd.txt 61 # Config: 62 xmeta -x -in=~/.ais0/.ais.conf - extract Config to STDOUT 63 xmeta -x -in=~/.ais0/.ais.conf -out=/tmp/conf.txt - extract Config to /tmp/config.txt 64 xmeta -x -in=./.ais.conf -f conf - extract Config to STDOUT with explicit source format 65 xmeta -in=/tmp/conf.txt -out=/tmp/.ais.conf - format plain-text /tmp/config.txt 66 # VMD: 67 xmeta -x -in=~/.ais0/.ais.vmd - extract VMD to STDOUT 68 xmeta -x -in=~/.ais0/.ais.vmd -out=/tmp/vmd.txt - extract VMD to /tmp/vmd.txt 69 xmeta -x -in=./.ais.vmd -f conf - extract VMD to STDOUT with explicit source format 70 xmeta -in=/tmp/vmd.txt -out=/tmp/.ais.vmd - format plain-text /tmp/vmd.txt 71 # EC Metadata: 72 xmeta -x -in=/data/@ais/abc/%mt/readme - extract Metadata to STDOUT with auto-detection (by directory name) 73 xmeta -x -in=./readme -f mt - extract Metadata to STDOUT with explicit source format 74 # LOM (readonly, no format auto-detection): 75 xmeta -x -in=/data/@ais/abc/%ob/img001.tar -f lom - extract LOM to STDOUT 76 xmeta -x -in=/data/@ais/abc/%ob/img001.tar -out=/tmp/lom.txt -f lom - extract LOM to /tmp/lom.txt 77 ` 78 ) 79 80 var m = map[string]struct { 81 extract func() error 82 format func() error 83 what string 84 }{ 85 "smap": {extractSmap, formatSmap, "Smap"}, 86 "bmd": {extractBMD, formatBMD, "BMD"}, 87 "rmd": {extractRMD, formatRMD, "RMD"}, 88 "conf": {extractConfig, formatConfig, "Config"}, 89 "vmd": {extractVMD, formatVMD, "VMD"}, 90 "mt": {extractECMeta, formatECMeta, "EC Metadata"}, 91 "lom": {extractLOM, formatLOM, "LOM"}, 92 } 93 94 // "extract*" routines expect AIS-formatted (smap, bmd, rmd, etc.) 95 96 func extractSmap() error { return extractMeta(&meta.Smap{}) } 97 func extractBMD() error { return extractMeta(&meta.BMD{}) } 98 func extractRMD() error { return extractMeta(&meta.RMD{}) } 99 func extractConfig() error { return extractMeta(&cmn.ClusterConfig{}) } 100 func extractVMD() error { return extractMeta(&volume.VMD{}) } 101 102 // "format*" routines require output filename 103 104 func formatSmap() error { return formatMeta(&meta.Smap{}) } 105 func formatBMD() error { return formatMeta(&meta.BMD{}) } 106 func formatRMD() error { return formatMeta(&meta.RMD{}) } 107 func formatConfig() error { return formatMeta(&cmn.ClusterConfig{}) } 108 func formatVMD() error { return formatMeta(&volume.VMD{}) } 109 func formatLOM() error { return errors.New("saving LOM is unsupported") } 110 111 func main() { 112 newFlag := flag.NewFlagSet(os.Args[0], flag.ExitOnError) // discard flags of imported packages 113 newFlag.BoolVar(&flags.extract, "x", false, 114 "true: extract AIS-formatted metadata type, false: pack and AIS-format plain-text metadata") 115 newFlag.StringVar(&flags.in, "in", "", "fully-qualified input filename") 116 newFlag.StringVar(&flags.out, "out", "", "output filename (optional when extracting)") 117 newFlag.BoolVar(&flags.help, "h", false, "print usage and exit") 118 newFlag.StringVar(&flags.format, "f", "", "override automatic format detection (one of smap, bmd, rmd, conf, vmd, mt, lom)") 119 newFlag.Parse(os.Args[1:]) 120 if flags.help || len(os.Args[1:]) == 0 { 121 newFlag.Usage() 122 hmsg := helpMsg 123 fmt.Print(hmsg) 124 os.Exit(0) 125 } 126 127 os.Args = []string{os.Args[0]} 128 flag.Parse() // don't complain 129 130 flags.in = cos.ExpandPath(flags.in) 131 if flags.out != "" { 132 flags.out = cos.ExpandPath(flags.out) 133 } 134 in := strings.ToLower(flags.in) 135 f, what := detectFormat(in) 136 if err := f(); err != nil { 137 if flags.extract { 138 fmt.Printf("Failed to extract %s from %s: %v\n", what, in, err) 139 } else { 140 fmt.Printf("Cannot format %s: plain-text input %s, error=\"%v\"\n", what, in, err) 141 } 142 } 143 } 144 145 func detectFormat(in string) (f func() error, what string) { 146 if flags.format == "" { 147 return parseDetect(in, flags.extract) 148 } 149 e, ok := m[flags.format] 150 if !ok { 151 fmt.Printf("Invalid file format %q. Supported formats are (", flags.format) 152 for k := range m { 153 fmt.Printf("%s, ", k) 154 } 155 fmt.Printf(")\n") 156 os.Exit(1) 157 } 158 f, what = e.format, e.what 159 if flags.extract { 160 f = e.extract 161 } 162 return 163 } 164 165 func parseDetect(in string, extract bool) (f func() error, what string) { 166 var all []string 167 for k, e := range m { 168 if !strings.Contains(in, k) { 169 all = append(all, e.what) 170 continue 171 } 172 if extract { 173 f, what = e.extract, e.what 174 } else { 175 f, what = e.format, e.what 176 } 177 break 178 } 179 if what == "" { 180 fmt.Printf("Failed to auto-detect %q for AIS metadata type - one of %q\n(use '-f' option to specify)\n", in, all) 181 os.Exit(1) 182 } 183 return 184 } 185 186 func extractMeta(v jsp.Opts) (err error) { 187 f := os.Stdout 188 if flags.out != "" { 189 f, err = cos.CreateFile(flags.out) 190 if err != nil { 191 return 192 } 193 } 194 _, err = jsp.LoadMeta(flags.in, v) 195 if err != nil { 196 return 197 } 198 s, _ := jsoniter.MarshalIndent(v, "", " ") 199 _, err = fmt.Fprintln(f, string(s)) 200 return err 201 } 202 203 func extractECMeta() (err error) { 204 f := os.Stdout 205 if flags.out != "" { 206 f, err = cos.CreateFile(flags.out) 207 if err != nil { 208 return 209 } 210 } 211 var v *ec.Metadata 212 v, err = ec.LoadMetadata(flags.in) 213 if err != nil { 214 return 215 } 216 s, _ := jsoniter.MarshalIndent(v, "", " ") 217 _, err = fmt.Fprintln(f, string(s)) 218 return err 219 } 220 221 func formatMeta(v jsp.Opts) error { 222 if flags.out == "" { 223 return errors.New("output filename (the -out option) must be defined") 224 } 225 if _, err := jsp.Load(flags.in, v, jsp.Plain()); err != nil { 226 return err 227 } 228 return jsp.SaveMeta(flags.out, v, nil) 229 } 230 231 func formatECMeta() error { 232 if flags.out == "" { 233 return errors.New("output filename (the -out option) must be defined") 234 } 235 v := &ec.Metadata{} 236 if _, err := jsp.Load(flags.in, v, jsp.Plain()); err != nil { 237 return err 238 } 239 file, err := cos.CreateFile(flags.out) 240 if err != nil { 241 return err 242 } 243 defer cos.Close(file) 244 buf := v.NewPack() 245 _, err = file.Write(buf) 246 if err != nil { 247 cos.RemoveFile(flags.out) 248 } 249 return err 250 } 251 252 func extractLOM() (err error) { 253 f := os.Stdout 254 if flags.out != "" { 255 f, err = cos.CreateFile(flags.out) 256 if err != nil { 257 return 258 } 259 } 260 if flags.in == "" || flags.in == "." { 261 return errors.New("make sure to specify '-in=<fully qualified source filename>', run 'xmeta' for help and examples") 262 } 263 os.Setenv(core.DumpLomEnvVar, "1") 264 fs.TestNew(nil) 265 266 _ = mock.NewTarget(mock.NewBaseBownerMock()) // => cluster.Tinit 267 268 lom := &core.LOM{FQN: flags.in} 269 err = lom.LoadMetaFromFS() 270 if err != nil { 271 return 272 } 273 lmi := lomInfo{Attrs: lom.ObjAttrs()} 274 if lom.HasCopies() { 275 lmi.Copies = make([]string, 0, lom.NumCopies()) 276 for mp := range lom.GetCopies() { 277 lmi.Copies = append(lmi.Copies, mp) 278 } 279 } 280 s, _ := jsoniter.MarshalIndent(lmi, "", " ") 281 _, err = fmt.Fprintln(f, string(s)) 282 return err 283 }