parent
086be07bf5
commit
83ccae6c8b
@ -0,0 +1,165 @@ |
||||
/* |
||||
* MinIO Cloud Storage, (C) 2020 MinIO, Inc. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package cmd |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/binary" |
||||
"errors" |
||||
"fmt" |
||||
"path" |
||||
"time" |
||||
|
||||
"github.com/minio/minio/cmd/logger" |
||||
) |
||||
|
||||
const ( |
||||
legacyBucketObjectLockEnabledConfigFile = "object-lock-enabled.json" |
||||
legacyBucketObjectLockEnabledConfig = `{"x-amz-bucket-object-lock-enabled":true}` |
||||
|
||||
bucketMetadataFile = ".metadata.bin" |
||||
bucketMetadataFormat = 1 |
||||
bucketMetadataVersion = 1 |
||||
) |
||||
|
||||
//go:generate msgp -file $GOFILE -unexported
|
||||
|
||||
// bucketMetadata contains bucket metadata.
|
||||
// When adding/removing fields, regenerate the marshal code using the go generate above.
|
||||
// Only changing meaning of fields requires a version bump.
|
||||
// bucketMetadataFormat refers to the format.
|
||||
// bucketMetadataVersion can be used to track a rolling upgrade of a field.
|
||||
type bucketMetadata struct { |
||||
Name string |
||||
Created time.Time |
||||
LockEnabled bool |
||||
} |
||||
|
||||
// newBucketMetadata creates bucketMetadata with the supplied name and Created to Now.
|
||||
func newBucketMetadata(name string) bucketMetadata { |
||||
return bucketMetadata{ |
||||
Name: name, |
||||
Created: UTCNow(), |
||||
} |
||||
} |
||||
|
||||
// loadBucketMeta loads the metadata of bucket by name from ObjectLayer o.
|
||||
// If an error is returned the returned metadata will be default initialized.
|
||||
func loadBucketMeta(ctx context.Context, o ObjectLayer, name string) (bucketMetadata, error) { |
||||
b := newBucketMetadata(name) |
||||
configFile := path.Join(bucketConfigPrefix, name, bucketMetadataFile) |
||||
data, err := readConfig(ctx, o, configFile) |
||||
if err != nil { |
||||
return b, err |
||||
} |
||||
if len(data) <= 4 { |
||||
return b, fmt.Errorf("loadBucketMetadata: no data") |
||||
} |
||||
// Read header
|
||||
switch binary.LittleEndian.Uint16(data[0:2]) { |
||||
case bucketMetadataFormat: |
||||
default: |
||||
return b, fmt.Errorf("loadBucketMetadata: unknown format: %d", binary.LittleEndian.Uint16(data[0:2])) |
||||
} |
||||
switch binary.LittleEndian.Uint16(data[2:4]) { |
||||
case bucketMetadataVersion: |
||||
default: |
||||
return b, fmt.Errorf("loadBucketMetadata: unknown version: %d", binary.LittleEndian.Uint16(data[2:4])) |
||||
} |
||||
|
||||
// OK, parse data.
|
||||
_, err = b.UnmarshalMsg(data[4:]) |
||||
return b, err |
||||
} |
||||
|
||||
var errMetaDataConverted = errors.New("metadata converted") |
||||
|
||||
// loadBucketMetadata loads and migrates to bucket metadata.
|
||||
func loadBucketMetadata(ctx context.Context, objectAPI ObjectLayer, bucket string) (bucketMetadata, error) { |
||||
meta, err := loadBucketMeta(ctx, objectAPI, bucket) |
||||
if err == nil { |
||||
return meta, nil |
||||
} |
||||
|
||||
if err != errConfigNotFound { |
||||
return meta, err |
||||
} |
||||
|
||||
// Control here means old bucket without bucket metadata. Hence we migrate existing settings.
|
||||
if err = meta.convertLegacyLockconfig(ctx, objectAPI); err != nil { |
||||
return meta, err |
||||
} |
||||
|
||||
return meta, errMetaDataConverted |
||||
} |
||||
|
||||
func (b *bucketMetadata) convertLegacyLockconfig(ctx context.Context, objectAPI ObjectLayer) error { |
||||
configFile := path.Join(bucketConfigPrefix, b.Name, legacyBucketObjectLockEnabledConfigFile) |
||||
|
||||
save := func() error { |
||||
if err := b.save(ctx, objectAPI); err != nil { |
||||
return err |
||||
} |
||||
|
||||
logger.LogIf(ctx, deleteConfig(ctx, objectAPI, configFile)) |
||||
return nil |
||||
} |
||||
|
||||
configData, err := readConfig(ctx, objectAPI, configFile) |
||||
if err != nil { |
||||
if err != errConfigNotFound { |
||||
return err |
||||
} |
||||
|
||||
return save() |
||||
} |
||||
|
||||
if string(configData) != legacyBucketObjectLockEnabledConfig { |
||||
return fmt.Errorf("content mismatch in config file %v", configFile) |
||||
} |
||||
|
||||
b.LockEnabled = true |
||||
return save() |
||||
} |
||||
|
||||
// save config to supplied ObjectLayer o.
|
||||
func (b *bucketMetadata) save(ctx context.Context, o ObjectLayer) error { |
||||
data := make([]byte, 4, b.Msgsize()+4) |
||||
// Put header
|
||||
binary.LittleEndian.PutUint16(data[0:2], bucketMetadataFormat) |
||||
binary.LittleEndian.PutUint16(data[2:4], bucketMetadataVersion) |
||||
|
||||
// Add data
|
||||
data, err := b.MarshalMsg(data) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile) |
||||
return saveConfig(ctx, o, configFile, data) |
||||
} |
||||
|
||||
// delete the config metadata.
|
||||
// If config does not exist no error is returned.
|
||||
func (b bucketMetadata) delete(ctx context.Context, o ObjectLayer) error { |
||||
configFile := path.Join(bucketConfigPrefix, b.Name, bucketMetadataFile) |
||||
err := deleteConfig(ctx, o, configFile) |
||||
if err == errConfigNotFound { |
||||
// We don't care
|
||||
err = nil |
||||
} |
||||
return err |
||||
} |
@ -0,0 +1,160 @@ |
||||
package cmd |
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import ( |
||||
"github.com/tinylib/msgp/msgp" |
||||
) |
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *bucketMetadata) DecodeMsg(dc *msgp.Reader) (err error) { |
||||
var field []byte |
||||
_ = field |
||||
var zb0001 uint32 |
||||
zb0001, err = dc.ReadMapHeader() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
for zb0001 > 0 { |
||||
zb0001-- |
||||
field, err = dc.ReadMapKeyPtr() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
switch msgp.UnsafeString(field) { |
||||
case "Name": |
||||
z.Name, err = dc.ReadString() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Name") |
||||
return |
||||
} |
||||
case "Created": |
||||
z.Created, err = dc.ReadTime() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Created") |
||||
return |
||||
} |
||||
case "LockEnabled": |
||||
z.LockEnabled, err = dc.ReadBool() |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "LockEnabled") |
||||
return |
||||
} |
||||
default: |
||||
err = dc.Skip() |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z bucketMetadata) EncodeMsg(en *msgp.Writer) (err error) { |
||||
// map header, size 3
|
||||
// write "Name"
|
||||
err = en.Append(0x83, 0xa4, 0x4e, 0x61, 0x6d, 0x65) |
||||
if err != nil { |
||||
return |
||||
} |
||||
err = en.WriteString(z.Name) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Name") |
||||
return |
||||
} |
||||
// write "Created"
|
||||
err = en.Append(0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64) |
||||
if err != nil { |
||||
return |
||||
} |
||||
err = en.WriteTime(z.Created) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Created") |
||||
return |
||||
} |
||||
// write "LockEnabled"
|
||||
err = en.Append(0xab, 0x4c, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64) |
||||
if err != nil { |
||||
return |
||||
} |
||||
err = en.WriteBool(z.LockEnabled) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "LockEnabled") |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z bucketMetadata) MarshalMsg(b []byte) (o []byte, err error) { |
||||
o = msgp.Require(b, z.Msgsize()) |
||||
// map header, size 3
|
||||
// string "Name"
|
||||
o = append(o, 0x83, 0xa4, 0x4e, 0x61, 0x6d, 0x65) |
||||
o = msgp.AppendString(o, z.Name) |
||||
// string "Created"
|
||||
o = append(o, 0xa7, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64) |
||||
o = msgp.AppendTime(o, z.Created) |
||||
// string "LockEnabled"
|
||||
o = append(o, 0xab, 0x4c, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64) |
||||
o = msgp.AppendBool(o, z.LockEnabled) |
||||
return |
||||
} |
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *bucketMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { |
||||
var field []byte |
||||
_ = field |
||||
var zb0001 uint32 |
||||
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
for zb0001 > 0 { |
||||
zb0001-- |
||||
field, bts, err = msgp.ReadMapKeyZC(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
switch msgp.UnsafeString(field) { |
||||
case "Name": |
||||
z.Name, bts, err = msgp.ReadStringBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Name") |
||||
return |
||||
} |
||||
case "Created": |
||||
z.Created, bts, err = msgp.ReadTimeBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "Created") |
||||
return |
||||
} |
||||
case "LockEnabled": |
||||
z.LockEnabled, bts, err = msgp.ReadBoolBytes(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err, "LockEnabled") |
||||
return |
||||
} |
||||
default: |
||||
bts, err = msgp.Skip(bts) |
||||
if err != nil { |
||||
err = msgp.WrapError(err) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
o = bts |
||||
return |
||||
} |
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z bucketMetadata) Msgsize() (s int) { |
||||
s = 1 + 5 + msgp.StringPrefixSize + len(z.Name) + 8 + msgp.TimeSize + 12 + msgp.BoolSize |
||||
return |
||||
} |
@ -0,0 +1,123 @@ |
||||
package cmd |
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
"github.com/tinylib/msgp/msgp" |
||||
) |
||||
|
||||
func TestMarshalUnmarshalbucketMetadata(t *testing.T) { |
||||
v := bucketMetadata{} |
||||
bts, err := v.MarshalMsg(nil) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
left, err := v.UnmarshalMsg(bts) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if len(left) > 0 { |
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) |
||||
} |
||||
|
||||
left, err = msgp.Skip(bts) |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if len(left) > 0 { |
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkMarshalMsgbucketMetadata(b *testing.B) { |
||||
v := bucketMetadata{} |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
v.MarshalMsg(nil) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkAppendMsgbucketMetadata(b *testing.B) { |
||||
v := bucketMetadata{} |
||||
bts := make([]byte, 0, v.Msgsize()) |
||||
bts, _ = v.MarshalMsg(bts[0:0]) |
||||
b.SetBytes(int64(len(bts))) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
bts, _ = v.MarshalMsg(bts[0:0]) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkUnmarshalbucketMetadata(b *testing.B) { |
||||
v := bucketMetadata{} |
||||
bts, _ := v.MarshalMsg(nil) |
||||
b.ReportAllocs() |
||||
b.SetBytes(int64(len(bts))) |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
_, err := v.UnmarshalMsg(bts) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestEncodeDecodebucketMetadata(t *testing.T) { |
||||
v := bucketMetadata{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
|
||||
m := v.Msgsize() |
||||
if buf.Len() > m { |
||||
t.Log("WARNING: TestEncodeDecodebucketMetadata Msgsize() is inaccurate") |
||||
} |
||||
|
||||
vn := bucketMetadata{} |
||||
err := msgp.Decode(&buf, &vn) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
|
||||
buf.Reset() |
||||
msgp.Encode(&buf, &v) |
||||
err = msgp.NewReader(&buf).Skip() |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkEncodebucketMetadata(b *testing.B) { |
||||
v := bucketMetadata{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
b.SetBytes(int64(buf.Len())) |
||||
en := msgp.NewWriter(msgp.Nowhere) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
v.EncodeMsg(en) |
||||
} |
||||
en.Flush() |
||||
} |
||||
|
||||
func BenchmarkDecodebucketMetadata(b *testing.B) { |
||||
v := bucketMetadata{} |
||||
var buf bytes.Buffer |
||||
msgp.Encode(&buf, &v) |
||||
b.SetBytes(int64(buf.Len())) |
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b) |
||||
dc := msgp.NewReader(rd) |
||||
b.ReportAllocs() |
||||
b.ResetTimer() |
||||
for i := 0; i < b.N; i++ { |
||||
err := v.DecodeMsg(dc) |
||||
if err != nil { |
||||
b.Fatal(err) |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue