mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-31 05:07:01 +00:00
* filer: cover the storage rule TTL on the object transaction write path An object written through ObjectTransaction used to land with ttlSec 0 even under an fs.configure TTL rule, while the same object written through CreateEntry got the rule's TTL. Guard the shared stamping so the two paths cannot drift apart again. * filer: apply the path's storage rule to an appended entry AppendToEntry resolved the storage option from the path - so its chunks land on a TTL volume under an fs.configure TTL rule - but never stamped the rule's TTL on the entry it creates, leaving an entry that outlives its data. Route it through applyStorageDefaultsToEntry, which now feeds the entry's own TTL into the option so the placement an existing entry's appended chunks get is unchanged. * filer: apply the path's storage rule to a completed TUS upload The PATCH path resolves the storage option from the target, so a TUS upload into an fs.configure TTL prefix writes its chunks to a TTL volume, but completion built the final entry with ttlSec 0 - the entry outlived the data it pointed at. Stamp it through applyStorageDefaultsToEntry, which also subsumes the hand-rolled read-only check and supplies the rule's name-length limit. * filer: apply the destination's storage option TTL to a copied entry The copy handler re-uploads the source's chunks under the destination's storage option, so a copy into an fs.configure TTL prefix already lands its data on a TTL volume. The entry, though, carried the source's ttlSec - 0 for a source outside the prefix, or the source's own TTL where the two rules differ - so it never expired with the data it pointed at. Take the TTL from the same option the chunks were placed with, after the data-only copy has restored the destination's metadata.
233 lines
5.4 KiB
Go
233 lines
5.4 KiB
Go
package filer
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
type Attr struct {
|
|
Mtime time.Time // time of last modification
|
|
Crtime time.Time // time of creation (OS X only)
|
|
Ctime time.Time // time of last inode change
|
|
Atime time.Time // time of last access
|
|
Mode os.FileMode // file mode
|
|
Uid uint32 // owner uid
|
|
Gid uint32 // group gid
|
|
Mime string // mime type
|
|
TtlSec int32 // ttl in seconds
|
|
UserName string
|
|
GroupNames []string
|
|
SymlinkTarget string
|
|
Md5 []byte
|
|
FileSize uint64
|
|
Rdev uint32
|
|
Inode uint64
|
|
}
|
|
|
|
func (attr Attr) IsDirectory() bool {
|
|
return attr.Mode&os.ModeDir > 0
|
|
}
|
|
|
|
type Entry struct {
|
|
util.FullPath
|
|
|
|
Attr
|
|
Extended map[string][]byte
|
|
|
|
// the following is for files
|
|
Chunks []*filer_pb.FileChunk `json:"chunks,omitempty"`
|
|
|
|
HardLinkId HardLinkId
|
|
HardLinkCounter int32
|
|
Content []byte
|
|
Remote *filer_pb.RemoteEntry
|
|
Quota int64
|
|
WORMEnforcedAtTsNs int64
|
|
}
|
|
|
|
func (entry *Entry) Size() uint64 {
|
|
return maxUint64(maxUint64(TotalSize(entry.GetChunks()), entry.FileSize), uint64(len(entry.Content)))
|
|
}
|
|
|
|
func (entry *Entry) Timestamp() time.Time {
|
|
if entry.IsDirectory() {
|
|
return entry.Crtime
|
|
} else {
|
|
return entry.Mtime
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) ShallowClone() *Entry {
|
|
if entry == nil {
|
|
return nil
|
|
}
|
|
newEntry := &Entry{}
|
|
newEntry.FullPath = entry.FullPath
|
|
newEntry.Attr = entry.Attr
|
|
newEntry.Chunks = entry.Chunks
|
|
newEntry.Extended = entry.Extended
|
|
newEntry.HardLinkId = entry.HardLinkId
|
|
newEntry.HardLinkCounter = entry.HardLinkCounter
|
|
newEntry.Content = entry.Content
|
|
newEntry.Remote = entry.Remote
|
|
newEntry.Quota = entry.Quota
|
|
|
|
return newEntry
|
|
}
|
|
|
|
func (entry *Entry) ToProtoEntry() *filer_pb.Entry {
|
|
if entry == nil {
|
|
return nil
|
|
}
|
|
message := &filer_pb.Entry{}
|
|
message.Name = entry.FullPath.Name()
|
|
entry.ToExistingProtoEntry(message)
|
|
return message
|
|
}
|
|
|
|
func (entry *Entry) ToExistingProtoEntry(message *filer_pb.Entry) {
|
|
if entry == nil {
|
|
return
|
|
}
|
|
message.IsDirectory = entry.IsDirectory()
|
|
// Reuse pre-allocated attributes if available, otherwise allocate
|
|
if message.Attributes != nil {
|
|
EntryAttributeToExistingPb(entry, message.Attributes)
|
|
} else {
|
|
message.Attributes = EntryAttributeToPb(entry)
|
|
}
|
|
message.Chunks = entry.GetChunks()
|
|
message.Extended = entry.Extended
|
|
message.HardLinkId = entry.HardLinkId
|
|
message.HardLinkCounter = entry.HardLinkCounter
|
|
message.Content = entry.Content
|
|
message.RemoteEntry = entry.Remote
|
|
message.Quota = entry.Quota
|
|
message.WormEnforcedAtTsNs = entry.WORMEnforcedAtTsNs
|
|
}
|
|
|
|
func FromPbEntryToExistingEntry(message *filer_pb.Entry, fsEntry *Entry) {
|
|
fsEntry.Attr = PbToEntryAttribute(message.Attributes)
|
|
fsEntry.Chunks = message.Chunks
|
|
fsEntry.Extended = message.Extended
|
|
fsEntry.HardLinkId = HardLinkId(message.HardLinkId)
|
|
fsEntry.HardLinkCounter = message.HardLinkCounter
|
|
fsEntry.Content = message.Content
|
|
fsEntry.Remote = message.RemoteEntry
|
|
fsEntry.Quota = message.Quota
|
|
fsEntry.FileSize = FileSize(message)
|
|
fsEntry.WORMEnforcedAtTsNs = message.WormEnforcedAtTsNs
|
|
}
|
|
|
|
func (entry *Entry) ToProtoFullEntry() *filer_pb.FullEntry {
|
|
if entry == nil {
|
|
return nil
|
|
}
|
|
dir, _ := entry.FullPath.DirAndName()
|
|
return &filer_pb.FullEntry{
|
|
Dir: dir,
|
|
Entry: entry.ToProtoEntry(),
|
|
}
|
|
}
|
|
|
|
func (entry *Entry) GetChunks() []*filer_pb.FileChunk {
|
|
return entry.Chunks
|
|
}
|
|
|
|
func FromPbEntry(dir string, entry *filer_pb.Entry) *Entry {
|
|
t := &Entry{}
|
|
t.FullPath = util.NewFullPath(dir, entry.Name)
|
|
FromPbEntryToExistingEntry(entry, t)
|
|
return t
|
|
}
|
|
|
|
func maxUint64(x, y uint64) uint64 {
|
|
if x > y {
|
|
return x
|
|
}
|
|
return y
|
|
}
|
|
|
|
func (entry *Entry) IsExpireS3Enabled() (exist bool) {
|
|
if entry.Extended != nil {
|
|
_, exist = entry.Extended[s3_constants.SeaweedFSExpiresS3]
|
|
}
|
|
return exist
|
|
}
|
|
|
|
func (entry *Entry) isS3Entry() bool {
|
|
if entry.Extended == nil {
|
|
return false
|
|
}
|
|
|
|
if _, found := entry.Extended[s3_constants.ExtAmzOwnerKey]; found {
|
|
return true
|
|
}
|
|
|
|
if _, found := entry.Extended[s3_constants.ExtETagKey]; found {
|
|
return true
|
|
}
|
|
|
|
for key := range entry.Extended {
|
|
if strings.HasPrefix(key, s3_constants.ExtAmzPrefix) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ApplyStorageTtl stamps the TTL the entry's data was placed with. Remote
|
|
// entries never expire locally; the remote storage owns their lifecycle.
|
|
func (entry *Entry) ApplyStorageTtl(ttlSec int32) {
|
|
if entry.Remote != nil {
|
|
ttlSec = 0
|
|
}
|
|
entry.TtlSec = ttlSec
|
|
entry.ApplyS3ExpiryMetadata()
|
|
}
|
|
|
|
func (entry *Entry) ApplyS3ExpiryMetadata() {
|
|
if entry.TtlSec == 0 {
|
|
return
|
|
}
|
|
|
|
if _, found := entry.Extended[s3_constants.SeaweedFSExpiresS3]; found {
|
|
return
|
|
}
|
|
|
|
// The s3 expiry path skips versioned entries, so stamping one would drop its
|
|
// expiry entirely instead of moving it onto mtime.
|
|
if entry.IsS3Versioning() {
|
|
return
|
|
}
|
|
|
|
if !entry.isS3Entry() {
|
|
return
|
|
}
|
|
|
|
entry.Extended[s3_constants.SeaweedFSExpiresS3] = []byte("true")
|
|
|
|
return
|
|
}
|
|
|
|
func (entry *Entry) IsS3Versioning() (exist bool) {
|
|
if entry.Extended != nil {
|
|
_, exist = entry.Extended[s3_constants.ExtVersionIdKey]
|
|
}
|
|
return exist
|
|
}
|
|
|
|
func (entry *Entry) GetS3ExpireTime() (expireTime time.Time) {
|
|
if entry.Mtime.IsZero() {
|
|
expireTime = entry.Crtime
|
|
} else {
|
|
expireTime = entry.Mtime
|
|
}
|
|
return expireTime.Add(time.Duration(entry.TtlSec) * time.Second)
|
|
}
|