diff --git a/cmd/common-main.go b/cmd/common-main.go index fc1501e18..9dbdd9cba 100644 --- a/cmd/common-main.go +++ b/cmd/common-main.go @@ -102,6 +102,11 @@ func init() { PersistOnFailure: false, } + t, _ := minioVersionToReleaseTime(Version) + if !t.IsZero() { + globalVersionUnix = uint64(t.Unix()) + } + globalIsCICD = env.Get("MINIO_CI_CD", "") != "" || env.Get("CI", "") != "" containers := IsKubernetes() || IsDocker() || IsBOSH() || IsDCOS() || IsPCFTile() diff --git a/cmd/erasure-multipart.go b/cmd/erasure-multipart.go index 2c940a85a..eb9c185a3 100644 --- a/cmd/erasure-multipart.go +++ b/cmd/erasure-multipart.go @@ -326,6 +326,7 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string, partsMetadata := make([]FileInfo, len(onlineDisks)) fi := newFileInfo(pathJoin(bucket, object), dataDrives, parityDrives) + fi.WrittenByVersion = globalVersionUnix fi.VersionID = opts.VersionID if opts.Versioned && fi.VersionID == "" { fi.VersionID = mustGetUUID() diff --git a/cmd/erasure-object.go b/cmd/erasure-object.go index 998ee87f5..3e96fcdab 100644 --- a/cmd/erasure-object.go +++ b/cmd/erasure-object.go @@ -944,6 +944,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st partsMetadata := make([]FileInfo, len(storageDisks)) fi := newFileInfo(pathJoin(bucket, object), dataDrives, parityDrives) + fi.WrittenByVersion = globalVersionUnix fi.VersionID = opts.VersionID if opts.Versioned && fi.VersionID == "" { fi.VersionID = mustGetUUID() diff --git a/cmd/erasure-single-drive.go b/cmd/erasure-single-drive.go index 8b12f7376..58d6c59a7 100644 --- a/cmd/erasure-single-drive.go +++ b/cmd/erasure-single-drive.go @@ -941,6 +941,7 @@ func (es *erasureSingle) putObject(ctx context.Context, bucket string, object st partsMetadata := make([]FileInfo, len(storageDisks)) fi := newFileInfo(pathJoin(bucket, object), dataDrives, parityDrives) + fi.WrittenByVersion = globalVersionUnix fi.VersionID = opts.VersionID if opts.Versioned && fi.VersionID == "" { fi.VersionID = mustGetUUID() @@ -2070,6 +2071,7 @@ func (es *erasureSingle) newMultipartUpload(ctx context.Context, bucket string, partsMetadata := make([]FileInfo, len(onlineDisks)) fi := newFileInfo(pathJoin(bucket, object), dataDrives, parityDrives) + fi.WrittenByVersion = globalVersionUnix fi.VersionID = opts.VersionID if opts.Versioned && fi.VersionID == "" { fi.VersionID = mustGetUUID() diff --git a/cmd/globals.go b/cmd/globals.go index 5ae76edb4..305474da8 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -373,6 +373,9 @@ var ( globalObjectPerfBucket = "minio-perf-test-tmp-bucket" globalObjectPerfUserMetadata = "X-Amz-Meta-Minio-Object-Perf" // Clients can set this to bypass S3 API service freeze. Used by object pref tests. + // MinIO version unix timestamp + globalVersionUnix uint64 + // Add new variable global values here. ) diff --git a/cmd/storage-datatypes.go b/cmd/storage-datatypes.go index 38cf181f2..658331a88 100644 --- a/cmd/storage-datatypes.go +++ b/cmd/storage-datatypes.go @@ -176,6 +176,10 @@ type FileInfo struct { // File mode bits. Mode uint32 `msg:"m"` + // WrittenByVersion is the unix time stamp of the MinIO + // version that created this version of the object. + WrittenByVersion uint64 `msg:"wv"` + // File metadata Metadata map[string]string `msg:"meta"` diff --git a/cmd/storage-datatypes_gen.go b/cmd/storage-datatypes_gen.go index 949df878e..f8fb103a5 100644 --- a/cmd/storage-datatypes_gen.go +++ b/cmd/storage-datatypes_gen.go @@ -550,8 +550,8 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) { err = msgp.WrapError(err) return } - if zb0001 != 26 { - err = msgp.ArrayError{Wanted: 26, Got: zb0001} + if zb0001 != 27 { + err = msgp.ArrayError{Wanted: 27, Got: zb0001} return } z.Volume, err = dc.ReadString() @@ -629,6 +629,11 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) { err = msgp.WrapError(err, "Mode") return } + z.WrittenByVersion, err = dc.ReadUint64() + if err != nil { + err = msgp.WrapError(err, "WrittenByVersion") + return + } var zb0002 uint32 zb0002, err = dc.ReadMapHeader() if err != nil { @@ -726,8 +731,8 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) { // EncodeMsg implements msgp.Encodable func (z *FileInfo) EncodeMsg(en *msgp.Writer) (err error) { - // array header, size 26 - err = en.Append(0xdc, 0x0, 0x1a) + // array header, size 27 + err = en.Append(0xdc, 0x0, 0x1b) if err != nil { return } @@ -806,6 +811,11 @@ func (z *FileInfo) EncodeMsg(en *msgp.Writer) (err error) { err = msgp.WrapError(err, "Mode") return } + err = en.WriteUint64(z.WrittenByVersion) + if err != nil { + err = msgp.WrapError(err, "WrittenByVersion") + return + } err = en.WriteMapHeader(uint32(len(z.Metadata))) if err != nil { err = msgp.WrapError(err, "Metadata") @@ -886,8 +896,8 @@ func (z *FileInfo) EncodeMsg(en *msgp.Writer) (err error) { // MarshalMsg implements msgp.Marshaler func (z *FileInfo) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) - // array header, size 26 - o = append(o, 0xdc, 0x0, 0x1a) + // array header, size 27 + o = append(o, 0xdc, 0x0, 0x1b) o = msgp.AppendString(o, z.Volume) o = msgp.AppendString(o, z.Name) o = msgp.AppendString(o, z.VersionID) @@ -903,6 +913,7 @@ func (z *FileInfo) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.AppendTime(o, z.ModTime) o = msgp.AppendInt64(o, z.Size) o = msgp.AppendUint32(o, z.Mode) + o = msgp.AppendUint64(o, z.WrittenByVersion) o = msgp.AppendMapHeader(o, uint32(len(z.Metadata))) for za0001, za0002 := range z.Metadata { o = msgp.AppendString(o, za0001) @@ -944,8 +955,8 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err) return } - if zb0001 != 26 { - err = msgp.ArrayError{Wanted: 26, Got: zb0001} + if zb0001 != 27 { + err = msgp.ArrayError{Wanted: 27, Got: zb0001} return } z.Volume, bts, err = msgp.ReadStringBytes(bts) @@ -1023,6 +1034,11 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) { err = msgp.WrapError(err, "Mode") return } + z.WrittenByVersion, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "WrittenByVersion") + return + } var zb0002 uint32 zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) if err != nil { @@ -1121,7 +1137,7 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) { // Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message func (z *FileInfo) Msgsize() (s int) { - s = 3 + msgp.StringPrefixSize + len(z.Volume) + msgp.StringPrefixSize + len(z.Name) + msgp.StringPrefixSize + len(z.VersionID) + msgp.BoolSize + msgp.BoolSize + msgp.StringPrefixSize + len(z.TransitionStatus) + msgp.StringPrefixSize + len(z.TransitionedObjName) + msgp.StringPrefixSize + len(z.TransitionTier) + msgp.StringPrefixSize + len(z.TransitionVersionID) + msgp.BoolSize + msgp.StringPrefixSize + len(z.DataDir) + msgp.BoolSize + msgp.TimeSize + msgp.Int64Size + msgp.Uint32Size + msgp.MapHeaderSize + s = 3 + msgp.StringPrefixSize + len(z.Volume) + msgp.StringPrefixSize + len(z.Name) + msgp.StringPrefixSize + len(z.VersionID) + msgp.BoolSize + msgp.BoolSize + msgp.StringPrefixSize + len(z.TransitionStatus) + msgp.StringPrefixSize + len(z.TransitionedObjName) + msgp.StringPrefixSize + len(z.TransitionTier) + msgp.StringPrefixSize + len(z.TransitionVersionID) + msgp.BoolSize + msgp.StringPrefixSize + len(z.DataDir) + msgp.BoolSize + msgp.TimeSize + msgp.Int64Size + msgp.Uint32Size + msgp.Uint64Size + msgp.MapHeaderSize if z.Metadata != nil { for za0001, za0002 := range z.Metadata { _ = za0002 diff --git a/cmd/storage-rest-common.go b/cmd/storage-rest-common.go index 364991cf4..dd857e35a 100644 --- a/cmd/storage-rest-common.go +++ b/cmd/storage-rest-common.go @@ -1,4 +1,4 @@ -// Copyright (c) 2015-2021 MinIO, Inc. +// Copyright (c) 2015-2022 MinIO, Inc. // // This file is part of MinIO Object Storage stack // @@ -18,7 +18,7 @@ package cmd const ( - storageRESTVersion = "v45" // Added ReadXL API + storageRESTVersion = "v46" // Added MinIO version to FileInfo storageRESTVersionPrefix = SlashSeparator + storageRESTVersion storageRESTPrefix = minioReservedBucketPath + "/storage" ) diff --git a/cmd/xl-storage-format-v2.go b/cmd/xl-storage-format-v2.go index cfa38d09b..aaba3054d 100644 --- a/cmd/xl-storage-format-v2.go +++ b/cmd/xl-storage-format-v2.go @@ -178,10 +178,11 @@ type xlMetaV2Object struct { // on what Type field carries, it is imperative for the caller // to verify which journal type first before accessing rest of the fields. type xlMetaV2Version struct { - Type VersionType `json:"Type" msg:"Type"` - ObjectV1 *xlMetaV1Object `json:"V1Obj,omitempty" msg:"V1Obj,omitempty"` - ObjectV2 *xlMetaV2Object `json:"V2Obj,omitempty" msg:"V2Obj,omitempty"` - DeleteMarker *xlMetaV2DeleteMarker `json:"DelObj,omitempty" msg:"DelObj,omitempty"` + Type VersionType `json:"Type" msg:"Type"` + ObjectV1 *xlMetaV1Object `json:"V1Obj,omitempty" msg:"V1Obj,omitempty"` + ObjectV2 *xlMetaV2Object `json:"V2Obj,omitempty" msg:"V2Obj,omitempty"` + DeleteMarker *xlMetaV2DeleteMarker `json:"DelObj,omitempty" msg:"DelObj,omitempty"` + WrittenByVersion uint64 `msg:"v"` // Tracks written by MinIO version } // xlFlags contains flags on the object. @@ -409,16 +410,22 @@ func (j xlMetaV2Version) getVersionID() [16]byte { } // ToFileInfo returns FileInfo of the underlying type. -func (j *xlMetaV2Version) ToFileInfo(volume, path string) (FileInfo, error) { +func (j *xlMetaV2Version) ToFileInfo(volume, path string) (fi FileInfo, err error) { + if j == nil { + return fi, errFileNotFound + } switch j.Type { case ObjectType: - return j.ObjectV2.ToFileInfo(volume, path) + fi, err = j.ObjectV2.ToFileInfo(volume, path) case DeleteType: - return j.DeleteMarker.ToFileInfo(volume, path) + fi, err = j.DeleteMarker.ToFileInfo(volume, path) case LegacyType: - return j.ObjectV1.ToFileInfo(volume, path) + fi, err = j.ObjectV1.ToFileInfo(volume, path) + default: + return fi, errFileNotFound } - return FileInfo{}, errFileNotFound + fi.WrittenByVersion = j.WrittenByVersion + return fi, err } const ( @@ -1424,7 +1431,9 @@ func (x *xlMetaV2) AddVersion(fi FileInfo) error { } } - ventry := xlMetaV2Version{} + ventry := xlMetaV2Version{ + WrittenByVersion: fi.WrittenByVersion, + } if fi.Deleted { ventry.Type = DeleteType diff --git a/cmd/xl-storage-format-v2_gen.go b/cmd/xl-storage-format-v2_gen.go index 0ee5d38df..222d096f1 100644 --- a/cmd/xl-storage-format-v2_gen.go +++ b/cmd/xl-storage-format-v2_gen.go @@ -1774,6 +1774,12 @@ func (z *xlMetaV2Version) DecodeMsg(dc *msgp.Reader) (err error) { return } } + case "v": + z.WrittenByVersion, err = dc.ReadUint64() + if err != nil { + err = msgp.WrapError(err, "WrittenByVersion") + return + } default: err = dc.Skip() if err != nil { @@ -1788,8 +1794,8 @@ func (z *xlMetaV2Version) DecodeMsg(dc *msgp.Reader) (err error) { // EncodeMsg implements msgp.Encodable func (z *xlMetaV2Version) EncodeMsg(en *msgp.Writer) (err error) { // omitempty: check for empty values - zb0001Len := uint32(4) - var zb0001Mask uint8 /* 4 bits */ + zb0001Len := uint32(5) + var zb0001Mask uint8 /* 5 bits */ if z.ObjectV1 == nil { zb0001Len-- zb0001Mask |= 0x2 @@ -1877,6 +1883,16 @@ func (z *xlMetaV2Version) EncodeMsg(en *msgp.Writer) (err error) { } } } + // write "v" + err = en.Append(0xa1, 0x76) + if err != nil { + return + } + err = en.WriteUint64(z.WrittenByVersion) + if err != nil { + err = msgp.WrapError(err, "WrittenByVersion") + return + } return } @@ -1884,8 +1900,8 @@ func (z *xlMetaV2Version) EncodeMsg(en *msgp.Writer) (err error) { func (z *xlMetaV2Version) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) // omitempty: check for empty values - zb0001Len := uint32(4) - var zb0001Mask uint8 /* 4 bits */ + zb0001Len := uint32(5) + var zb0001Mask uint8 /* 5 bits */ if z.ObjectV1 == nil { zb0001Len-- zb0001Mask |= 0x2 @@ -1945,6 +1961,9 @@ func (z *xlMetaV2Version) MarshalMsg(b []byte) (o []byte, err error) { } } } + // string "v" + o = append(o, 0xa1, 0x76) + o = msgp.AppendUint64(o, z.WrittenByVersion) return } @@ -2027,6 +2046,12 @@ func (z *xlMetaV2Version) UnmarshalMsg(bts []byte) (o []byte, err error) { return } } + case "v": + z.WrittenByVersion, bts, err = msgp.ReadUint64Bytes(bts) + if err != nil { + err = msgp.WrapError(err, "WrittenByVersion") + return + } default: bts, err = msgp.Skip(bts) if err != nil { @@ -2059,6 +2084,7 @@ func (z *xlMetaV2Version) Msgsize() (s int) { } else { s += z.DeleteMarker.Msgsize() } + s += 2 + msgp.Uint64Size return }