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  }