diff --git a/pkg/meta/base.go b/pkg/meta/base.go index ceb55211e3f9..db6b610b69ac 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -71,6 +71,9 @@ type engine interface { doCleanupDelayedSlices(edge int64) (int, error) doDeleteSlice(id uint64, size uint32) error + doGetQuota(ctx Context, inode Ino) (*Quota, error) + doSetQuota(ctx Context, inode Ino, quota *Quota, create bool) error + doDelQuota(ctx Context, inode Ino) error doLoadQuotas(ctx Context) (map[Ino]*Quota, error) doFlushQuota(ctx Context, inode Ino, space, inodes int64) error @@ -572,10 +575,6 @@ func (m *baseMeta) checkQuota(ctx Context, space, inodes int64, parent Ino) bool return m.checkDirQuota(ctx, parent, space, inodes) } -func (m *baseMeta) GetDirRecStat(ctx Context, inode Ino) (space, inodes int64, err error) { - return 0, 0, nil // FIXME: use FastGetSummary -} - func (m *baseMeta) loadQuotas() { quotas, err := m.en.doLoadQuotas(Background) if err == nil { @@ -714,6 +713,82 @@ func (m *baseMeta) flushQuotas() { } } +func (m *baseMeta) HandleQuota(ctx Context, cmd uint8, dpath string, quotas map[string]*Quota) error { + var inode Ino + if cmd != QuotaList { + if st := m.resolve(ctx, dpath, &inode); st != 0 { + return st + } + if isTrash(inode) { + return errors.New("no quota for any trash directory") + } + } + + switch cmd { + case QuotaSet: + q, err := m.en.doGetQuota(ctx, inode) + if err != nil { + return err + } + quota := quotas[dpath] + if q == nil { + var sum Summary + if st := m.FastGetSummary(ctx, inode, &sum, true); st != 0 { + return st + } + quota.UsedSpace = int64(sum.Size) - align4K(0) + quota.UsedInodes = int64(sum.Dirs+sum.Files) - 1 + if quota.MaxSpace < 0 { + quota.MaxSpace = 0 + } + if quota.MaxInodes < 0 { + quota.MaxInodes = 0 + } + return m.en.doSetQuota(ctx, inode, quota, true) + } else { + quota.UsedSpace, quota.UsedInodes = q.UsedSpace, q.UsedInodes + if quota.MaxSpace < 0 { + quota.MaxSpace = q.MaxSpace + } + if quota.MaxInodes < 0 { + quota.MaxInodes = q.MaxInodes + } + if quota.MaxSpace == q.MaxSpace && quota.MaxInodes == q.MaxInodes { + return nil // nothing to update + } + return m.en.doSetQuota(ctx, inode, quota, false) + } + case QuotaGet: + q, err := m.en.doGetQuota(ctx, inode) + if err != nil { + return err + } + if q == nil { + return fmt.Errorf("no quota for inode %d path %s", inode, dpath) + } + quotas[dpath] = q + case QuotaDel: + return m.en.doDelQuota(ctx, inode) + case QuotaList: + quotaMap, err := m.en.doLoadQuotas(ctx) + if err != nil { + return err + } + var p string + for ino, quota := range quotaMap { + if ps := m.GetPaths(ctx, ino); len(ps) > 0 { + p = ps[0] + } else { + p = fmt.Sprintf("inode:%d", ino) + } + quotas[p] = quota + } + default: // FIXME: QuotaCheck + return fmt.Errorf("invalid quota command: %d", cmd) + } + return nil +} + func (m *baseMeta) cleanupDeletedFiles() { for { utils.SleepWithJitter(time.Minute) @@ -1229,12 +1304,14 @@ func (m *baseMeta) Rename(ctx Context, parentSrc Ino, nameSrc string, parentDst if st := m.Lookup(ctx, parentSrc, nameSrc, inode, attr); st != 0 { return st } - var err error if attr.Typ == TypeDirectory { - space, inodes, err = m.GetDirRecStat(ctx, *inode) - if err != nil { - return errno(err) + var sum Summary + logger.Debugf("Start to get summary of inode %d", *inode) + if st := m.FastGetSummary(ctx, *inode, &sum, true); st != 0 { + logger.Warnf("Get summary of inode %d: %s", *inode, st) + return st } + space, inodes = int64(sum.Size), int64(sum.Dirs+sum.Files) } else { space, inodes = align4K(attr.Length), 1 } diff --git a/pkg/meta/interface.go b/pkg/meta/interface.go index f1559a36e939..85c688cd37f3 100644 --- a/pkg/meta/interface.go +++ b/pkg/meta/interface.go @@ -375,8 +375,6 @@ type Meta interface { GetParents(ctx Context, inode Ino) map[Ino]int // GetDirStat returns the space and inodes usage of a directory. GetDirStat(ctx Context, inode Ino) (st *dirStat, err error) - // GetDirRecStat returns the space and inodes usage (recursive) of a directory. - GetDirRecStat(ctx Context, inode Ino) (space, inodes int64, err error) // GetXattr returns the value of extended attribute for given name. GetXattr(ctx Context, inode Ino, name string, vbuff *[]byte) syscall.Errno diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index a3d3b5b0b01c..fcb9454eb46b 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -3285,109 +3285,60 @@ func (m *redisMeta) doRemoveXattr(ctx Context, inode Ino, name string) syscall.E } } -func (m *redisMeta) HandleQuota(ctx Context, cmd uint8, dpath string, quotas map[string]*Quota) error { - var inode Ino - if cmd != QuotaList { - if st := m.resolve(ctx, dpath, &inode); st != 0 { - return st - } - if isTrash(inode) { - return errors.New("no quota for any trash directory") - } +func (m *redisMeta) doGetQuota(ctx Context, inode Ino) (*Quota, error) { + field := inode.String() + cmds, err := m.rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error { + pipe.HGet(ctx, m.dirQuotaKey(), field) + pipe.HGet(ctx, m.dirQuotaUsedSpaceKey(), field) + pipe.HGet(ctx, m.dirQuotaUsedInodesKey(), field) + return nil + }) + if err == redis.Nil { + return nil, nil + } else if err != nil { + return nil, err } - var err error - field := inode.String() - switch cmd { - case QuotaSet: - quota := quotas[dpath] - err = m.txn(ctx, func(tx *redis.Tx) error { - rawQ, e := tx.HGet(ctx, m.dirQuotaKey(), field).Bytes() - if e != nil && e != redis.Nil { - return e - } - if len(rawQ) != 0 && len(rawQ) != 16 { - return fmt.Errorf("invalid quota value: %v", rawQ) - } - maxSpace, maxInodes := m.parseQuota(rawQ) - if quota.MaxSpace < 0 { - quota.MaxSpace = maxSpace - } - if quota.MaxInodes < 0 { - quota.MaxInodes = maxInodes - } - if maxSpace == quota.MaxSpace && maxInodes == quota.MaxInodes { - return nil // nothing to update - } - create := e == redis.Nil - var usedSpace, usedInodes int64 - if create { - usedSpace, usedInodes, e = m.GetDirRecStat(ctx, inode) - if e != nil { - return e - } - } - _, e = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { + buf, _ := cmds[0].(*redis.StringCmd).Bytes() + if len(buf) != 16 { + return nil, fmt.Errorf("invalid quota value: %v", buf) + } + var quota Quota + quota.MaxSpace, quota.MaxInodes = m.parseQuota(buf) + if quota.UsedSpace, err = cmds[1].(*redis.StringCmd).Int64(); err != nil { + return nil, err + } + if quota.UsedInodes, err = cmds[2].(*redis.StringCmd).Int64(); err != nil { + return nil, err + } + return "a, nil +} + +func (m *redisMeta) doSetQuota(ctx Context, inode Ino, quota *Quota, create bool) error { + return m.txn(ctx, func(tx *redis.Tx) error { + field := inode.String() + if create { + _, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { pipe.HSet(ctx, m.dirQuotaKey(), field, m.packQuota(quota.MaxSpace, quota.MaxInodes)) - if create { - pipe.HSet(ctx, m.dirQuotaUsedSpaceKey(), field, usedSpace) - pipe.HSet(ctx, m.dirQuotaUsedInodesKey(), field, usedInodes) - } + pipe.HSet(ctx, m.dirQuotaUsedSpaceKey(), field, quota.UsedSpace) + pipe.HSet(ctx, m.dirQuotaUsedInodesKey(), field, quota.UsedInodes) return nil }) - return e - }, m.dirQuotaKey()) - case QuotaGet: - var quota Quota - var cmds []redis.Cmder - cmds, err = m.rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error { - pipe.HGet(ctx, m.dirQuotaKey(), field) - pipe.HGet(ctx, m.dirQuotaUsedSpaceKey(), field) - pipe.HGet(ctx, m.dirQuotaUsedInodesKey(), field) - return nil - }) - if err == redis.Nil { - err = errors.New("no quota") - } - if err != nil { return err + } else { + return tx.HSet(ctx, m.dirQuotaKey(), field, m.packQuota(quota.MaxSpace, quota.MaxInodes)).Err() } - rawQ, _ := cmds[0].(*redis.StringCmd).Bytes() - if len(rawQ) != 16 { - return fmt.Errorf("invalid quota value: %v", rawQ) - } - quota.MaxSpace, quota.MaxInodes = m.parseQuota(rawQ) - if quota.UsedSpace, err = cmds[1].(*redis.StringCmd).Int64(); err != nil { - return err - } - if quota.UsedInodes, err = cmds[2].(*redis.StringCmd).Int64(); err != nil { - return err - } - quotas[dpath] = "a - case QuotaDel: - _, err = m.rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error { - pipe.HDel(ctx, m.dirQuotaKey(), field) - pipe.HDel(ctx, m.dirQuotaUsedSpaceKey(), field) - pipe.HDel(ctx, m.dirQuotaUsedInodesKey(), field) - return nil - }) - case QuotaList: - var quotaMap map[Ino]*Quota - quotaMap, err = m.doLoadQuotas(ctx) - if err == nil { - var p string - for ino, quota := range quotaMap { - if ps := m.GetPaths(ctx, ino); len(ps) > 0 { - p = ps[0] - } else { - p = fmt.Sprintf("inode:%d", ino) - } - quotas[p] = quota - } - } - default: // FIXME: QuotaCheck - err = fmt.Errorf("invalid quota command: %d", cmd) - } + }, m.dirQuotaKey()) +} + +func (m *redisMeta) doDelQuota(ctx Context, inode Ino) error { + field := inode.String() + _, err := m.rdb.TxPipelined(ctx, func(pipe redis.Pipeliner) error { + pipe.HDel(ctx, m.dirQuotaKey(), field) + pipe.HDel(ctx, m.dirQuotaUsedSpaceKey(), field) + pipe.HDel(ctx, m.dirQuotaUsedInodesKey(), field) + return nil + }) return err } diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 5f0193a8cf83..9a6d802b8c9f 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -3008,90 +3008,51 @@ func (m *dbMeta) doRemoveXattr(ctx Context, inode Ino, name string) syscall.Errn })) } -func (m *dbMeta) HandleQuota(ctx Context, cmd uint8, dpath string, quotas map[string]*Quota) error { - var inode Ino - if cmd != QuotaList { - if st := m.resolve(ctx, dpath, &inode); st != 0 { - return st - } - if isTrash(inode) { - return errors.New("no quota for any trash directory") - } - } +func (m *dbMeta) doGetQuota(ctx Context, inode Ino) (*Quota, error) { + var quota *Quota + return quota, m.roTxn(func(s *xorm.Session) error { + q := dirQuota{Inode: inode} + ok, e := s.Get(&q) + if e == nil && ok { + quota = &Quota{ + MaxSpace: q.MaxSpace, + MaxInodes: q.MaxInodes, + UsedSpace: q.UsedSpace, + UsedInodes: q.UsedInodes} + } + return e + }) +} - var err error - switch cmd { - case QuotaSet: - quota := quotas[dpath] - err = m.txn(func(s *xorm.Session) error { - q := dirQuota{Inode: inode} - ok, e := s.ForUpdate().Get(&q) - if e != nil { - return e - } - if quota.MaxSpace < 0 { - quota.MaxSpace = q.MaxSpace - } - if quota.MaxInodes < 0 { - quota.MaxInodes = q.MaxInodes - } - if q.MaxSpace == quota.MaxSpace && q.MaxInodes == quota.MaxInodes { - return nil // nothing to update - } - q.MaxSpace = quota.MaxSpace - q.MaxInodes = quota.MaxInodes - if ok { - _, e = s.Cols("max_space", "max_inodes").Update(&q, &dirQuota{Inode: inode}) - } else { - q.UsedSpace, q.UsedInodes, e = m.GetDirRecStat(ctx, inode) - if e == nil { - e = mustInsert(s, &q) - } - } - return e - }) - case QuotaGet: - var quota Quota - err = m.roTxn(func(s *xorm.Session) error { - q := dirQuota{Inode: inode} - ok, e := s.Get(&q) - if e == nil && !ok { - e = errors.New("no quota") - } - if e == nil { - quota.MaxSpace = q.MaxSpace - quota.MaxInodes = q.MaxInodes - quota.UsedSpace = q.UsedSpace - quota.UsedInodes = q.UsedInodes - } +func (m *dbMeta) doSetQuota(ctx Context, inode Ino, quota *Quota, create bool) error { + return m.txn(func(s *xorm.Session) error { + q := dirQuota{Inode: inode} + ok, e := s.ForUpdate().Get(&q) + if e != nil { return e - }) - if err == nil { - quotas[dpath] = "a } - case QuotaDel: - err = m.txn(func(s *xorm.Session) error { - _, e := s.Delete(&dirQuota{Inode: inode}) - return e - }) - case QuotaList: - var quotaMap map[Ino]*Quota - quotaMap, err = m.doLoadQuotas(ctx) - if err == nil { - var p string - for ino, quota := range quotaMap { - if ps := m.GetPaths(ctx, ino); len(ps) > 0 { - p = ps[0] - } else { - p = fmt.Sprintf("inode:%d", ino) - } - quotas[p] = quota + q.MaxSpace, q.MaxInodes = quota.MaxSpace, quota.MaxInodes + if ok { + if create { + q.UsedSpace, q.UsedInodes = quota.UsedSpace, quota.UsedInodes + _, e = s.Cols("max_space", "max_inodes", "used_space", "used_inodes").Update(&q, &dirQuota{Inode: inode}) + } else { + quota.UsedSpace, quota.UsedInodes = q.UsedSpace, q.UsedInodes + _, e = s.Cols("max_space", "max_inodes").Update(&q, &dirQuota{Inode: inode}) } + } else { + q.UsedSpace, q.UsedInodes = quota.UsedSpace, quota.UsedInodes + e = mustInsert(s, &q) } - default: // FIXME: QuotaCheck - err = fmt.Errorf("invalid quota command: %d", cmd) - } - return err + return e + }) +} + +func (m *dbMeta) doDelQuota(ctx Context, inode Ino) error { + return m.txn(func(s *xorm.Session) error { + _, e := s.Delete(&dirQuota{Inode: inode}) + return e + }) } func (m *dbMeta) doLoadQuotas(ctx Context) (map[Ino]*Quota, error) { diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 45e0e369cc2f..1403f4af6d0a 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -345,13 +345,6 @@ func (m *kvMeta) packQuota(q *Quota) []byte { } func (m *kvMeta) parseQuota(buf []byte) *Quota { - if len(buf) == 0 { - return nil - } - if len(buf) != 32 { - logger.Errorf("Invalid quota value: %v", buf) - return nil - } b := utils.FromBuffer(buf) return &Quota{ MaxSpace: int64(b.Get64()), @@ -2632,82 +2625,35 @@ func (m *kvMeta) doRemoveXattr(ctx Context, inode Ino, name string) syscall.Errn })) } -func (m *kvMeta) HandleQuota(ctx Context, cmd uint8, dpath string, quotas map[string]*Quota) error { - var inode Ino - if cmd != QuotaList { - if st := m.resolve(ctx, dpath, &inode); st != 0 { - return st - } - if isTrash(inode) { - return errors.New("no quota for any trash directory") - } +func (m *kvMeta) doGetQuota(ctx Context, inode Ino) (*Quota, error) { + buf, err := m.get(m.dirQuotaKey(inode)) + if err != nil { + return nil, err + } else if buf == nil { + return nil, nil + } else if len(buf) != 32 { + return nil, fmt.Errorf("invalid quota value: %v", buf) } + return m.parseQuota(buf), nil +} - var err error - switch cmd { - case QuotaSet: - quota := quotas[dpath] - err = m.txn(func(tx *kvTxn) error { - rawQ := tx.get(m.dirQuotaKey(inode)) - if rawQ != nil && len(rawQ) != 32 { - return fmt.Errorf("invalid quota value: %v", rawQ) - } - q := m.parseQuota(rawQ) - if q == nil { - q = &Quota{} - } - if quota.MaxSpace < 0 { - quota.MaxSpace = q.MaxSpace - } - if quota.MaxInodes < 0 { - quota.MaxInodes = q.MaxInodes - } - if q.MaxSpace == quota.MaxSpace && q.MaxInodes == quota.MaxInodes { - return nil // nothing to update - } - q.MaxSpace = quota.MaxSpace - q.MaxInodes = quota.MaxInodes - if rawQ == nil { - var e error - q.UsedSpace, q.UsedInodes, e = m.GetDirRecStat(ctx, inode) - if e != nil { - return e - } - } - tx.set(m.dirQuotaKey(inode), m.packQuota(q)) - return nil - }, inode) - case QuotaGet: - var rawQ []byte - rawQ, err = m.get(m.dirQuotaKey(inode)) - if rawQ == nil { - err = errors.New("no quota") - } else if len(rawQ) != 32 { - err = fmt.Errorf("invalid quota value: %v", rawQ) - } - if err == nil { - quotas[dpath] = m.parseQuota(rawQ) - } - case QuotaDel: - err = m.deleteKeys(m.dirQuotaKey(inode)) - case QuotaList: - var quotaMap map[Ino]*Quota - quotaMap, err = m.doLoadQuotas(ctx) - if err == nil { - var p string - for ino, quota := range quotaMap { - if ps := m.GetPaths(ctx, ino); len(ps) > 0 { - p = ps[0] - } else { - p = fmt.Sprintf("inode:%d", ino) - } - quotas[p] = quota +func (m *kvMeta) doSetQuota(ctx Context, inode Ino, quota *Quota, create bool) error { + return m.txn(func(tx *kvTxn) error { + if create { + tx.set(m.dirQuotaKey(inode), m.packQuota(quota)) + } else { + buf := tx.get(m.dirQuotaKey(inode)) + if len(buf) == 32 { + q := m.parseQuota(buf) + quota.UsedSpace, quota.UsedInodes = q.UsedSpace, q.UsedInodes } + tx.set(m.dirQuotaKey(inode), m.packQuota(quota)) } - default: // FIXME: QuotaCheck - err = fmt.Errorf("invalid quota command: %d", cmd) - } - return err + return nil + }) +} +func (m *kvMeta) doDelQuota(ctx Context, inode Ino) error { + return m.deleteKeys(m.dirQuotaKey(inode)) } func (m *kvMeta) doLoadQuotas(ctx Context) (map[Ino]*Quota, error) {