1
// Copyright 2023 The CubeFS Authors.
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
7
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12
// implied. See the License for the specific language governing
13
// permissions and limitations under the License.
24
"github.com/cubefs/cubefs/proto"
25
"github.com/cubefs/cubefs/sdk/master"
26
"github.com/cubefs/cubefs/sdk/meta"
27
"github.com/spf13/cobra"
31
cmdQuotaUse = "quota [COMMAND]"
32
cmdQuotaShort = "Manage cluster quota"
33
cmdQuotaCreateUse = "create [volname] [fullpath1,fullpath2]"
34
cmdQuotaCreateShort = "create paths quota"
35
cmdQuotaListUse = "list [volname]"
36
cmdQuotaListShort = "list volname all quota"
37
cmdQuotaUpdateUse = "update [volname] [quotaId]"
38
cmdQuotaUpdateShort = "update quota files or bytes by id"
39
cmdQuotaDeleteUse = "delete [volname] [quotaId]"
40
cmdQUotaDeleteShort = "delete quota by id"
41
cmdQuotaGetInodeUse = "getInode [volname] [inode]"
42
cmdQuotaGetInodeShort = "get inode quotaInfo"
43
cmdQuotaListAllUse = "listAll"
44
cmdQuotaListAllShort = "list all volname has quota"
45
cmdQuotaApplyUse = "apply [volname] [quotaId]"
46
cmdQuotaApplyShort = "apply quota"
47
cmdQuotaRevokeUse = "revoke [volname] [quotaId]"
48
cmdQuotaRevokeShort = "revoke quota"
52
cmdQuotaDefaultMaxFiles = math.MaxUint64
53
cmdQuotaDefaultMaxBytes = math.MaxUint64
56
func newQuotaCmd(client *master.MasterClient) *cobra.Command {
57
cmd := &cobra.Command{
60
Args: cobra.MinimumNArgs(0),
61
Aliases: []string{"quota"},
63
proto.InitBufferPool(32768)
65
newQuotaListCmd(client),
66
newQuotaCreateCmd(client),
67
newQuotaUpdateCmd(client),
68
newQuotaDelete(client),
69
newQuotaGetInode(client),
70
newQuotaListAllCmd(client),
71
newQuotaApplyCmd(client),
72
newQuotaRevokeCmd(client),
77
func newQuotaCreateCmd(client *master.MasterClient) *cobra.Command {
81
cmd := &cobra.Command{
82
Use: cmdQuotaCreateUse,
83
Short: cmdQuotaCreateShort,
84
Args: cobra.MinimumNArgs(2),
85
Run: func(cmd *cobra.Command, args []string) {
90
metaConfig := &meta.MetaConfig{
92
Masters: client.Nodes(),
94
metaWrapper, err := meta.NewMetaWrapper(metaConfig)
96
stdout("NewMetaWrapper failed: %v\n", err)
99
fullPaths := strings.Split(fullPath, ",")
100
err = checkNestedDirectories(fullPaths)
102
stdout("create quota failed, fullPaths %v error %v.\n", fullPaths, err)
105
if len(fullPaths) > 5 {
106
stdout("create quota failed, fullPath %v has more than 5 path.\n", fullPaths)
109
quotaPathInofs := make([]proto.QuotaPathInfo, 0)
110
for _, path := range fullPaths {
111
var quotaPathInfo proto.QuotaPathInfo
112
quotaPathInfo.FullPath = path
114
if !strings.HasPrefix(path, "/") {
115
stdout("create quota failed, path %v does not start with / \n", path)
119
inodeId, err := metaWrapper.LookupPath(path)
121
stdout("get inode by fullPath %v fail %v\n", path, err)
124
quotaPathInfo.RootInode = inodeId
125
inodeInfo, err := metaWrapper.InodeGet_ll(inodeId)
127
stdout("get inode %v info fail %v\n", inodeId, err)
131
if !proto.IsDir(inodeInfo.Mode) {
132
stdout("create quota failed, inode [%v] is not dir\n", inodeId)
136
mp := metaWrapper.GetPartitionByInodeId_ll(inodeId)
138
stdout("can not find mp by inodeId: %v\n", inodeId)
141
quotaPathInfo.PartitionId = mp.PartitionID
142
quotaPathInofs = append(quotaPathInofs, quotaPathInfo)
145
if quotaId, err = client.AdminAPI().CreateQuota(volName, quotaPathInofs, maxFiles, maxBytes); err != nil {
146
stdout("volName %v path %v quota create failed(%v)\n", volName, fullPath, err)
149
stdout("createQuota: volName %v path %v maxFiles %v maxBytes %v quotaId %v success.\n",
150
volName, fullPath, maxFiles, maxBytes, quotaId)
153
cmd.Flags().Uint64Var(&maxFiles, CliFlagMaxFiles, cmdQuotaDefaultMaxFiles, "Specify quota max files")
154
cmd.Flags().Uint64Var(&maxBytes, CliFlagMaxBytes, cmdQuotaDefaultMaxBytes, "Specify quota max bytes")
158
func newQuotaListCmd(client *master.MasterClient) *cobra.Command {
159
cmd := &cobra.Command{
160
Use: cmdQuotaListUse,
161
Short: cmdQuotaListShort,
162
Args: cobra.MinimumNArgs(1),
163
Run: func(cmd *cobra.Command, args []string) {
164
var quotas []*proto.QuotaInfo
167
if quotas, err = client.AdminAPI().ListQuota(volName); err != nil {
168
stdout("volName %v quota list failed(%v)\n", volName, err)
171
sort.Slice(quotas, func(i, j int) bool {
172
return quotas[i].QuotaId < quotas[j].QuotaId
175
stdout("%v\n", formatQuotaTableHeader())
176
for _, quotaInfo := range quotas {
177
stdout("%v\n", formatQuotaInfo(quotaInfo))
184
func newQuotaListAllCmd(client *master.MasterClient) *cobra.Command {
185
cmd := &cobra.Command{
186
Use: cmdQuotaListAllUse,
187
Short: cmdQuotaListAllShort,
188
Args: cobra.MinimumNArgs(0),
189
Run: func(cmd *cobra.Command, args []string) {
191
var vols []*proto.VolInfo
193
if vols, err = client.AdminAPI().ListQuotaAll(); err != nil {
194
stdout("quota list all failed(%v)\n", err)
197
stdout("%v\n", volumeInfoTableHeader)
198
for _, vol := range vols {
199
stdout("%v\n", formatVolInfoTableRow(vol))
207
func newQuotaUpdateCmd(client *master.MasterClient) *cobra.Command {
211
cmd := &cobra.Command{
212
Use: cmdQuotaUpdateUse,
213
Short: cmdQuotaUpdateShort,
214
Args: cobra.MinimumNArgs(2),
215
Run: func(cmd *cobra.Command, args []string) {
219
quotaInfo, err := client.AdminAPI().GetQuota(volName, quotaId)
221
stdout("get quota vol: %v ,quotaId: %v failed err %v.\n", volName, quotaId, err)
225
maxFiles = quotaInfo.MaxFiles
228
maxBytes = quotaInfo.MaxBytes
230
if err = client.AdminAPI().UpdateQuota(volName, quotaId, maxFiles, maxBytes); err != nil {
231
stdout("volName %v quotaId %v quota update failed(%v)\n", volName, quotaId, err)
234
stdout("updateQuota: volName %v quotaId %v maxFiles %v maxBytes %v success.\n",
235
volName, quotaId, maxFiles, maxBytes)
238
cmd.Flags().Uint64Var(&maxFiles, CliFlagMaxFiles, 0, "Specify quota max files")
239
cmd.Flags().Uint64Var(&maxBytes, CliFlagMaxBytes, 0, "Specify quota max bytes")
243
func newQuotaDelete(client *master.MasterClient) *cobra.Command {
245
cmd := &cobra.Command{
246
Use: cmdQuotaDeleteUse,
247
Short: cmdQUotaDeleteShort,
248
Args: cobra.MinimumNArgs(2),
249
Run: func(cmd *cobra.Command, args []string) {
254
stdout("Before deleting the quota, please confirm that the quota of the inode in the subdirectory has been cleared\n")
255
stdout("ensure that the quota list %v usedFiles is displayed as 0\n", volName)
256
stdout("\nConfirm (yes/no)[yes]:")
257
var userConfirm string
258
_, _ = fmt.Scanln(&userConfirm)
259
if userConfirm != "yes" {
260
stdout("Abort by user.\n")
264
if err = client.AdminAPI().DeleteQuota(volName, quotaId); err != nil {
265
stdout("volName %v quotaId %v quota delete failed(%v)\n", volName, quotaId, err)
268
stdout("deleteQuota: volName %v quotaId %v success.\n", volName, quotaId)
271
cmd.Flags().BoolVarP(&optYes, "yes", "y", false, "Do not prompt to clear the quota of inodes")
275
func newQuotaGetInode(client *master.MasterClient) *cobra.Command {
276
cmd := &cobra.Command{
277
Use: cmdQuotaGetInodeUse,
278
Short: cmdQuotaGetInodeShort,
279
Args: cobra.MinimumNArgs(2),
280
Run: func(cmd *cobra.Command, args []string) {
282
inodeId, err := strconv.ParseUint(args[1], 10, 64)
284
stdout("inodeId %v is illegal", args[2])
288
metaConfig := &meta.MetaConfig{
290
Masters: client.Nodes(),
292
metaWrapper, err := meta.NewMetaWrapper(metaConfig)
294
stdout("NewMetaWrapper failed: %v\n", err)
298
quotaInfos, err := metaWrapper.GetInodeQuota_ll(inodeId)
300
stdout("get indoe quota failed %v\n", err)
303
for quotaId, quotaInfo := range quotaInfos {
304
stdout("quotaId [%v] quotaInfo [%v] \n", quotaId, quotaInfo)
311
func newQuotaApplyCmd(client *master.MasterClient) *cobra.Command {
312
var maxConcurrencyInode uint64
313
cmd := &cobra.Command{
314
Use: cmdQuotaApplyUse,
315
Short: cmdQuotaApplyShort,
316
Args: cobra.MinimumNArgs(2),
317
Run: func(cmd *cobra.Command, args []string) {
321
var quotaInfo *proto.QuotaInfo
323
if quotaInfo, err = client.AdminAPI().GetQuota(volName, quotaId); err != nil {
324
stdout("volName %v get quota %v failed(%v)\n", volName, quotaId, err)
328
metaConfig := &meta.MetaConfig{
330
Masters: client.Nodes(),
333
metaWrapper, err := meta.NewMetaWrapper(metaConfig)
335
stdout("NewMetaWrapper failed: %v\n", err)
339
var quotaIdNum uint32
340
tmp, err := strconv.ParseUint(quotaId, 10, 32)
342
stdout("quotaId %v is illegal", quotaId)
345
quotaIdNum = uint32(tmp)
346
for _, pathInfo := range quotaInfo.PathInfos {
347
inodeNums, err := metaWrapper.ApplyQuota_ll(pathInfo.RootInode, quotaIdNum, maxConcurrencyInode)
349
stdout("apply quota failed: %v\n", err)
352
totalNums += inodeNums
354
stdout("apply quota num [%v] success.\n", totalNums)
357
cmd.Flags().Uint64Var(&maxConcurrencyInode, CliFlagMaxConcurrencyInode, 1000, "max concurrency set Inodes")
361
func newQuotaRevokeCmd(client *master.MasterClient) *cobra.Command {
362
var maxConcurrencyInode uint64
363
var forceInode uint64
364
cmd := &cobra.Command{
365
Use: cmdQuotaRevokeUse,
366
Short: cmdQuotaRevokeShort,
367
Args: cobra.MinimumNArgs(2),
368
Run: func(cmd *cobra.Command, args []string) {
372
var quotaInfo *proto.QuotaInfo
375
metaConfig := &meta.MetaConfig{
377
Masters: client.Nodes(),
380
metaWrapper, err := meta.NewMetaWrapper(metaConfig)
382
stdout("NewMetaWrapper failed: %v\n", err)
385
var quotaIdNum uint32
386
tmp, err := strconv.ParseUint(quotaId, 10, 32)
388
stdout("quotaId %v is illegal", quotaId)
391
quotaIdNum = uint32(tmp)
393
if quotaInfo, err = client.AdminAPI().GetQuota(volName, quotaId); err != nil {
394
stdout("volName %v get quota %v failed(%v)\n", volName, quotaId, err)
398
for _, pathInfo := range quotaInfo.PathInfos {
399
inodeNums, err := metaWrapper.RevokeQuota_ll(pathInfo.RootInode, quotaIdNum, maxConcurrencyInode)
401
stdout("revoke quota inodeNums %v failed %v\n", inodeNums, err)
403
totalNums += inodeNums
406
totalNums, err = metaWrapper.RevokeQuota_ll(forceInode, quotaIdNum, maxConcurrencyInode)
408
stdout("revoke quota inodeNums %v failed %v\n", totalNums, err)
411
stdout("revoke num [%v] success.\n", totalNums)
414
cmd.Flags().Uint64Var(&maxConcurrencyInode, CliFlagMaxConcurrencyInode, 1000, "max concurrency delete Inodes")
415
cmd.Flags().Uint64Var(&forceInode, CliFlagForceInode, 0, "force revoke quota inode")
419
func checkNestedDirectories(paths []string) error {
420
for i, path := range paths {
421
for j := i + 1; j < len(paths); j++ {
422
if path == paths[j] {
423
return fmt.Errorf("the same directories found: %s", path)
426
if proto.IsAncestor(path, paths[j]) {
427
return fmt.Errorf("Nested directories found: %s and %s", path, paths[j])
430
if proto.IsAncestor(paths[j], path) {
431
return fmt.Errorf("Nested directories found: %s and %s", path, paths[j])