github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ec/metafile.go (about)

     1  // Package ec provides erasure coding (EC) based data protection for AIStore.
     2  /*
     3   * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package ec
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  
    12  	"github.com/NVIDIA/aistore/cmn/cos"
    13  	"github.com/NVIDIA/aistore/core"
    14  	"github.com/NVIDIA/aistore/core/meta"
    15  	"github.com/NVIDIA/aistore/fs"
    16  	"github.com/OneOfOne/xxhash"
    17  )
    18  
    19  const MDVersionLast = 1 // current version of metadata
    20  
    21  // Metadata - EC information stored in metafiles for every encoded object
    22  type Metadata struct {
    23  	Size        int64            `json:"obj_size"`      // obj size (after EC'ing sum size of slices differs from the original)
    24  	Generation  int64            `json:"generation"`    // Timestamp when the object was EC'ed
    25  	ObjCksum    string           `json:"obj_cksum"`     // checksum of the original object
    26  	ObjVersion  string           `json:"obj_version"`   // object version
    27  	CksumType   string           `json:"cksum_type"`    // slice checksum type
    28  	CksumValue  string           `json:"slice_cksum"`   // slice checksum of the slice if EC is used
    29  	FullReplica string           `json:"replica_node"`  // daemon ID where full(main) replica is
    30  	Daemons     cos.MapStrUint16 `json:"nodes"`         // Locations of all slices: DaemonID <-> SliceID
    31  	Data        int              `json:"data_slices"`   // the number of data slices
    32  	Parity      int              `json:"parity_slices"` // the number of parity slices
    33  	SliceID     int              `json:"slice_id"`      // 0 for full replica, 1 to N for slices
    34  	MDVersion   uint32           `json:"md_version"`    // Metadata format version
    35  	IsCopy      bool             `json:"is_copy"`       // object is replicated(true) or encoded(false)
    36  }
    37  
    38  // interface guard
    39  var (
    40  	_ cos.Unpacker = (*Metadata)(nil)
    41  	_ cos.Packer   = (*Metadata)(nil)
    42  )
    43  
    44  func NewMetadata() *Metadata {
    45  	return &Metadata{MDVersion: MDVersionLast}
    46  }
    47  
    48  // LoadMetadata loads and parses EC metadata from a file
    49  func LoadMetadata(fqn string) (*Metadata, error) {
    50  	b, err := os.ReadFile(fqn)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	md := &Metadata{}
    55  	unpacker := cos.NewUnpacker(b)
    56  	if err := unpacker.ReadAny(md); err != nil {
    57  		err := fmt.Errorf("damaged metafile %q: %v", fqn, err)
    58  		return nil, err
    59  	}
    60  
    61  	return md, nil
    62  }
    63  
    64  func MetaFromReader(reader io.Reader) (*Metadata, error) {
    65  	b, err := io.ReadAll(reader)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	md := &Metadata{}
    70  	unpacker := cos.NewUnpacker(b)
    71  	err = unpacker.ReadAny(md)
    72  	return md, err
    73  }
    74  
    75  // RemoteTargets returns list of Snodes that contain a slice or replica.
    76  // This target(`t`) is removed from the list.
    77  func (md *Metadata) RemoteTargets() []*meta.Snode {
    78  	if len(md.Daemons) == 0 {
    79  		return nil
    80  	}
    81  	nodes := make([]*meta.Snode, 0, len(md.Daemons))
    82  	smap := core.T.Sowner().Get()
    83  	for tid := range md.Daemons {
    84  		if tid == core.T.SID() {
    85  			continue
    86  		}
    87  		tsi := smap.GetTarget(tid)
    88  		if tsi != nil {
    89  			nodes = append(nodes, tsi)
    90  		}
    91  	}
    92  	return nodes
    93  }
    94  
    95  // TODO: use 'buf, slab = smm.Alloc()'
    96  func (md *Metadata) NewPack() []byte {
    97  	var (
    98  		buf []byte
    99  		l   = md.PackedSize()
   100  	)
   101  	packer := cos.NewPacker(buf, l)
   102  	packer.WriteAny(md)
   103  	return packer.Bytes()
   104  }
   105  
   106  func (md *Metadata) Clone() *Metadata {
   107  	clone := &Metadata{}
   108  	cos.CopyStruct(clone, md)
   109  	return clone
   110  }
   111  
   112  // ObjectMetadata returns metadata for an object or its slice if any exists
   113  func ObjectMetadata(bck *meta.Bck, objName string) (*Metadata, error) {
   114  	fqn, _, err := core.HrwFQN(bck.Bucket(), fs.ECMetaType, objName)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	return LoadMetadata(fqn)
   119  }
   120  
   121  func (md *Metadata) Unpack(unpacker *cos.ByteUnpack) (err error) {
   122  	var cksum uint64
   123  	if md.MDVersion, err = unpacker.ReadUint32(); err != nil {
   124  		return
   125  	}
   126  	switch md.MDVersion {
   127  	case MDVersionLast:
   128  		err = md.unpackLastVersion(unpacker)
   129  	default:
   130  		err = fmt.Errorf("unsupported metadata format version %d. Only %d supported",
   131  			md.MDVersion, MDVersionLast)
   132  	}
   133  	if err != nil {
   134  		return
   135  	}
   136  
   137  	if cksum, err = unpacker.ReadUint64(); err != nil {
   138  		return
   139  	}
   140  	b := unpacker.Bytes()
   141  	calcCksum := xxhash.Checksum64S(b[:len(b)-cos.SizeofI64], cos.MLCG32)
   142  	if cksum != calcCksum {
   143  		err = cos.NewErrMetaCksum(cksum, calcCksum, "EC metadata")
   144  	}
   145  	return err
   146  }
   147  
   148  func (md *Metadata) unpackLastVersion(unpacker *cos.ByteUnpack) (err error) {
   149  	var i16 uint16
   150  	if md.Generation, err = unpacker.ReadInt64(); err != nil {
   151  		return
   152  	}
   153  	if md.Size, err = unpacker.ReadInt64(); err != nil {
   154  		return
   155  	}
   156  	if i16, err = unpacker.ReadUint16(); err != nil {
   157  		return
   158  	}
   159  	md.Data = int(i16)
   160  	if i16, err = unpacker.ReadUint16(); err != nil {
   161  		return
   162  	}
   163  	md.Parity = int(i16)
   164  	if i16, err = unpacker.ReadUint16(); err != nil {
   165  		return
   166  	}
   167  	md.SliceID = int(i16)
   168  	if md.IsCopy, err = unpacker.ReadBool(); err != nil {
   169  		return
   170  	}
   171  	if md.FullReplica, err = unpacker.ReadString(); err != nil {
   172  		return
   173  	}
   174  	if md.ObjCksum, err = unpacker.ReadString(); err != nil {
   175  		return
   176  	}
   177  	if md.ObjVersion, err = unpacker.ReadString(); err != nil {
   178  		return
   179  	}
   180  	if md.CksumType, err = unpacker.ReadString(); err != nil {
   181  		return
   182  	}
   183  	if md.CksumValue, err = unpacker.ReadString(); err != nil {
   184  		return
   185  	}
   186  	md.Daemons, err = unpacker.ReadMapStrUint16()
   187  	return
   188  }
   189  
   190  func (md *Metadata) Pack(packer *cos.BytePack) {
   191  	packer.WriteUint32(md.MDVersion)
   192  	packer.WriteInt64(md.Generation)
   193  	packer.WriteInt64(md.Size)
   194  	packer.WriteUint16(uint16(md.Data))
   195  	packer.WriteUint16(uint16(md.Parity))
   196  	packer.WriteUint16(uint16(md.SliceID))
   197  	packer.WriteBool(md.IsCopy)
   198  	packer.WriteString(md.FullReplica)
   199  	packer.WriteString(md.ObjCksum)
   200  	packer.WriteString(md.ObjVersion)
   201  	packer.WriteString(md.CksumType)
   202  	packer.WriteString(md.CksumValue)
   203  	packer.WriteMapStrUint16(md.Daemons)
   204  	h := xxhash.Checksum64S(packer.Bytes(), cos.MLCG32)
   205  	packer.WriteUint64(h)
   206  }
   207  
   208  func (md *Metadata) PackedSize() int {
   209  	daemonListSz := cos.SizeofLen
   210  	for k := range md.Daemons {
   211  		daemonListSz += cos.PackedStrLen(k) + cos.SizeofI16
   212  	}
   213  	return cos.SizeofI32 + cos.SizeofI64*2 + cos.SizeofI16*3 + 1 /*isCopy*/ +
   214  		cos.PackedStrLen(md.ObjCksum) + cos.PackedStrLen(md.ObjVersion) +
   215  		cos.PackedStrLen(md.CksumType) + cos.PackedStrLen(md.CksumValue) +
   216  		cos.PackedStrLen(md.FullReplica) + daemonListSz + cos.SizeofI64 /*md cksum*/
   217  }