diff --git a/cmd/admin-replicate-add.go b/cmd/admin-replicate-add.go index f1db1db7bc..23d5ef41cf 100644 --- a/cmd/admin-replicate-add.go +++ b/cmd/admin-replicate-add.go @@ -28,13 +28,20 @@ import ( "github.com/minio/pkg/v2/console" ) +var adminReplicateAddFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "replicate-ilm-expiry", + Usage: "replicate ILM expiry rules", + }, +} + var adminReplicateAddCmd = cli.Command{ Name: "add", Usage: "add one or more sites for replication", Action: mainAdminReplicateAdd, OnUsageError: onUsageError, Before: setGlobalsFromContext, - Flags: globalFlags, + Flags: append(globalFlags, adminReplicateAddFlags...), CustomHelpTemplate: `NAME: {{.HelpName}} - {{.Usage}} @@ -48,6 +55,9 @@ FLAGS: EXAMPLES: 1. Add a site for cluster-level replication: {{.Prompt}} {{.HelpName}} minio1 minio2 + + 2. Add a site for cluster-level replication with replication of ILM expiry rules: + {{.Prompt}} {{.HelpName}} minio1 minio2 --replicate-ilm-expiry `, } @@ -106,7 +116,9 @@ func mainAdminReplicateAdd(ctx *cli.Context) error { }) } - res, e := client.SiteReplicationAdd(globalContext, ps, madmin.SRAddOptions{}) + var opts madmin.SRAddOptions + opts.ReplicateILMExpiry = ctx.Bool("replicate-ilm-expiry") + res, e := client.SiteReplicationAdd(globalContext, ps, opts) fatalIf(probe.NewError(e).Trace(args...), "Unable to add sites for replication") printMsg(successMessage(res)) diff --git a/cmd/admin-replicate-info.go b/cmd/admin-replicate-info.go index 3eac3a50e6..5a606c00db 100644 --- a/cmd/admin-replicate-info.go +++ b/cmd/admin-replicate-info.go @@ -19,6 +19,7 @@ package cmd import ( "fmt" + "strconv" "strings" humanize "github.com/dustin/go-humanize" @@ -75,7 +76,8 @@ func (i srInfo) String() string { Field{"Endpoint", 46}, Field{"Sync", 4}, Field{"Bandwidth", 10}, - ).buildRow("Deployment ID", "Site Name", "Endpoint", "Sync", "Bandwidth")) + Field{"ILM Expiry Replication", 25}, + ).buildRow("Deployment ID", "Site Name", "Endpoint", "Sync", "Bandwidth", "ILM Expiry Replication")) messages = append(messages, r) r = console.Colorize("THeaders", newPrettyTable(" | ", @@ -85,7 +87,8 @@ func (i srInfo) String() string { Field{"Endpoint", 46}, Field{"Sync", 4}, Field{"Bandwidth", 10}, - ).buildRow("", "", "", "", "Per Bucket")) + Field{"ILM Expiry Replication", 25}, + ).buildRow("", "", "", "", "Per Bucket", "")) messages = append(messages, r) for _, peer := range info.Sites { var chk string @@ -103,7 +106,8 @@ func (i srInfo) String() string { Field{"Endpoint", 46}, Field{"Sync", 4}, Field{"Bandwidth", 10}, - ).buildRow(peer.DeploymentID, peer.Name, peer.Endpoint, chk, limit)) + Field{"ILM Expiry Replication", 25}, + ).buildRow(peer.DeploymentID, peer.Name, peer.Endpoint, chk, limit, strconv.FormatBool(peer.ReplicateILMExpiry))) messages = append(messages, r) } } else { diff --git a/cmd/admin-replicate-status.go b/cmd/admin-replicate-status.go index 3dcce1af80..6c21521e29 100644 --- a/cmd/admin-replicate-status.go +++ b/cmd/admin-replicate-status.go @@ -50,6 +50,10 @@ var adminReplicateStatusFlags = []cli.Flag{ Name: "groups", Usage: "display only groups", }, + cli.BoolFlag{ + Name: "ilm-expiry-rules", + Usage: "display only ilm expiry rules", + }, cli.BoolFlag{ Name: "all", Usage: "display all available site replication status", @@ -70,6 +74,10 @@ var adminReplicateStatusFlags = []cli.Flag{ Name: "group", Usage: "display group sync status", }, + cli.StringFlag{ + Name: "ilm-expiry-rule", + Usage: "display ILM expiry rule sync status", + }, } // Some cell values @@ -312,6 +320,42 @@ func (i srStatus) String() string { } } } + if i.opts.ILMExpiryRules { + messages = append(messages, + console.Colorize("SummaryHdr", "ILM Expiry Rules replication status:")) + switch { + case i.MaxILMExpiryRules == 0: + messages = append(messages, console.Colorize("Summary", "No ILM Expiry Rules present\n")) + default: + msg := console.Colorize(i.getTheme(len(info.ILMExpiryStats) == 0), fmt.Sprintf("%d/%d ILM Expiry Rules in sync", info.MaxILMExpiryRules-len(info.ILMExpiryStats), info.MaxILMExpiryRules)) + "\n" + messages = append(messages, fmt.Sprintf("%s %s", coloredDot, msg)) + if len(i.ILMExpiryStats) > 0 { + messages = append(messages, i.siteHeader(siteNames, "ILM Expiry Rules")) + } + var detailFields []Field + for b, ssMap := range i.ILMExpiryStats { + var details []string + details = append(details, b) + detailFields = append(detailFields, legendFields[0]) + for _, sname := range siteNames { + detailFields = append(detailFields, legendFields[0]) + dID := nameIDMap[sname] + ss := ssMap[dID] + switch { + case !ss.HasILMExpiryRules: + details = append(details, blankCell) + case ss.ILMExpiryRuleMismatch: + details = append(details, fmt.Sprintf("%s in-sync", crossTickCell)) + default: + details = append(details, fmt.Sprintf("%s in-sync", tickCell)) + } + } + messages = append(messages, newPrettyTable(" | ", + detailFields...).buildRow(details...)) + messages = append(messages, "") + } + } + } switch i.opts.Entity { case madmin.SRBucketEntity: @@ -322,7 +366,8 @@ func (i srStatus) String() string { messages = append(messages, i.getUserStatusSummary(siteNames, nameIDMap, "User")...) case madmin.SRGroupEntity: messages = append(messages, i.getGroupStatusSummary(siteNames, nameIDMap, "Group")...) - + case madmin.SRILMExpiryRuleEntity: + messages = append(messages, i.getILMExpiryStatusSummary(siteNames, nameIDMap, "ILMExpiryRule")...) } if i.opts.Metrics { uiFn := func(theme string) func(string) string { @@ -716,29 +761,85 @@ func (i srStatus) getGroupStatusSummary(siteNames []string, nameIDMap map[string return messages } +func (i srStatus) getILMExpiryStatusSummary(siteNames []string, nameIDMap map[string]string, legend string) []string { + var messages []string + coloredDot := console.Colorize("Status", dot) + var found bool + for _, st := range i.SRStatusInfo.ILMExpiryStats[i.opts.EntityValue] { + if st.HasILMExpiryRules { + found = true + break + } + } + if !found { + messages = append(messages, console.Colorize("Summary", fmt.Sprintf("ILM Expiry Rule %s not found\n", i.opts.EntityValue))) + return messages + } + + rowLegend := []string{"ILM Expiry Rule"} + detailFields := make([][]Field, len(rowLegend)) + + var rules []string + detailFields[0] = make([]Field, len(siteNames)+1) + detailFields[0][0] = Field{"Entity", 15} + rules = append(rules, "ILM Expiry Rule") + rows := make([]string, len(rowLegend)) + for j, sname := range siteNames { + dID := nameIDMap[sname] + ss := i.SRStatusInfo.ILMExpiryStats[i.opts.EntityValue][dID] + var theme, msgStr string + for r := range rowLegend { + switch r { + case 0: + theme, msgStr = syncStatus(ss.ILMExpiryRuleMismatch, ss.HasILMExpiryRules) + rules = append(rules, msgStr) + detailFields[r][j+1] = Field{theme, fieldLen} + } + } + } + for r := range rowLegend { + switch r { + case 0: + rows[r] = newPrettyTable(" | ", + detailFields[r]...).buildRow(rules...) + } + } + messages = append(messages, + console.Colorize("SummaryHdr", fmt.Sprintf("%s %s\n", coloredDot, console.Colorize("Summary", "ILM Expiry Rule replication summary for: ")+console.Colorize("UserMessage", i.opts.EntityValue)))) + siteHdr := i.siteHeader(siteNames, legend) + messages = append(messages, siteHdr) + + messages = append(messages, rows...) + return messages +} + // Calculate srstatus options for command line flags func srStatusOpts(ctx *cli.Context) (opts madmin.SRStatusOptions) { if !(ctx.IsSet("buckets") || ctx.IsSet("users") || ctx.IsSet("groups") || ctx.IsSet("policies") || + ctx.IsSet("ilm-expiry-rules") || ctx.IsSet("bucket") || ctx.IsSet("user") || ctx.IsSet("group") || ctx.IsSet("policy") || + ctx.IsSet("ilm-expiry-rule") || ctx.IsSet("all")) || ctx.IsSet("all") { opts.Buckets = true opts.Users = true opts.Groups = true opts.Policies = true opts.Metrics = true + opts.ILMExpiryRules = true return } opts.Buckets = ctx.Bool("buckets") opts.Policies = ctx.Bool("policies") opts.Users = ctx.Bool("users") opts.Groups = ctx.Bool("groups") - for _, name := range []string{"bucket", "user", "group", "policy"} { + opts.ILMExpiryRules = ctx.Bool("ilm-expiry-rules") + for _, name := range []string{"bucket", "user", "group", "policy", "ilm-expiry-rule"} { if ctx.IsSet(name) { opts.Entity = madmin.GetSREntityType(name) opts.EntityValue = ctx.String(name) @@ -756,13 +857,13 @@ func mainAdminReplicationStatus(ctx *cli.Context) error { fatalIf(errInvalidArgument().Trace(ctx.Args().Tail()...), "Need exactly one alias argument.") } - groupStatus := ctx.IsSet("buckets") || ctx.IsSet("groups") || ctx.IsSet("users") || ctx.IsSet("policies") - indivStatus := ctx.IsSet("bucket") || ctx.IsSet("group") || ctx.IsSet("user") || ctx.IsSet("policy") + groupStatus := ctx.IsSet("buckets") || ctx.IsSet("groups") || ctx.IsSet("users") || ctx.IsSet("policies") || ctx.IsSet("ilm-expiry-rules") + indivStatus := ctx.IsSet("bucket") || ctx.IsSet("group") || ctx.IsSet("user") || ctx.IsSet("policy") || ctx.IsSet("ilm-expiry-rule") if groupStatus && indivStatus { fatalIf(errInvalidArgument().Trace(ctx.Args().Tail()...), - "Cannot specify both (bucket|group|policy|user) flag and one or more of buckets|groups|policies|users) flag(s)") + "Cannot specify both (bucket|group|policy|user|ilm-expiry-rule) flag and one or more of buckets|groups|policies|users|ilm-expiry-rules) flag(s)") } - setSlc := []bool{ctx.IsSet("bucket"), ctx.IsSet("user"), ctx.IsSet("group"), ctx.IsSet("policy")} + setSlc := []bool{ctx.IsSet("bucket"), ctx.IsSet("user"), ctx.IsSet("group"), ctx.IsSet("policy"), ctx.IsSet("ilm-expiry-rule")} count := 0 for _, s := range setSlc { if s { @@ -771,7 +872,7 @@ func mainAdminReplicationStatus(ctx *cli.Context) error { } if count > 1 { fatalIf(errInvalidArgument().Trace(ctx.Args().Tail()...), - "Cannot specify more than one of --bucket, --policy, --user, --group flags at the same time") + "Cannot specify more than one of --bucket, --policy, --user, --group, --ilm-expiry-rule flags at the same time") } } diff --git a/cmd/admin-replicate-update.go b/cmd/admin-replicate-update.go index c224dd116c..679a4b09fc 100644 --- a/cmd/admin-replicate-update.go +++ b/cmd/admin-replicate-update.go @@ -53,6 +53,14 @@ var adminReplicateUpdateFlags = []cli.Flag{ Name: "bucket-bandwidth", Usage: "Set default bandwidth limit for bucket in bits per second (K,B,G,T for metric and Ki,Bi,Gi,Ti for IEC units)", }, + cli.BoolFlag{ + Name: "disable-ilm-expiry-replication", + Usage: "disable ILM expiry rules replication", + }, + cli.BoolFlag{ + Name: "enable-ilm-expiry-replication", + Usage: "enable ILM expiry rules replication", + }, } var adminReplicateUpdateCmd = cli.Command{ @@ -80,6 +88,12 @@ EXAMPLES: 2. Edit a site in cluster-level replication to set default bandwidth limit for bucket: {{.Prompt}} {{.HelpName}} myminio --deployment-id c1758167-4426-454f-9aae-5c3dfdf6df64 --bucket-bandwidth "2G" + + 3. Disable replication of ILM expiry in cluster-level replication: + {{.Prompt}} {{.HelpName}} myminio --disable-ilm-expiry-replication + + 4. Enable replication of ILM expiry in cluster-level replication: + {{.Prompt}} {{.HelpName}} myminio --enable-ilm-expiry-replication `, } @@ -125,15 +139,21 @@ func mainAdminReplicateUpdate(ctx *cli.Context) error { client, err := newAdminClient(aliasedURL) fatalIf(err, "Unable to initialize admin connection.") - if !ctx.IsSet("deployment-id") { + if !ctx.IsSet("deployment-id") && !ctx.IsSet("disable-ilm-expiry-replication") && !ctx.IsSet("enable-ilm-expiry-replication") { fatalIf(errInvalidArgument(), "--deployment-id is a required flag") } - if !ctx.IsSet("endpoint") && !ctx.IsSet("mode") && !ctx.IsSet("sync") && !ctx.IsSet("bucket-bandwidth") { - fatalIf(errInvalidArgument(), "--endpoint, --mode or --bucket-bandwidth is a required flag") + if !ctx.IsSet("endpoint") && !ctx.IsSet("mode") && !ctx.IsSet("sync") && !ctx.IsSet("bucket-bandwidth") && !ctx.IsSet("disable-ilm-expiry-replication") && !ctx.IsSet("enable-ilm-expiry-replication") { + fatalIf(errInvalidArgument(), "--endpoint, --mode, --bucket-bandwidth, --disable-ilm-expiry-replication or --enable-ilm-expiry-replication is a required flag") } if ctx.IsSet("mode") && ctx.IsSet("sync") { fatalIf(errInvalidArgument(), "either --sync or --mode flag should be specified") } + if ctx.IsSet("disable-ilm-expiry-replication") && ctx.IsSet("enable-ilm-expiry-replication") { + fatalIf(errInvalidArgument(), "either --disable-ilm-expiry-replication or --enable-ilm-expiry-replication flag should be specified") + } + if (ctx.IsSet("disable-ilm-expiry-replication") || ctx.IsSet("enable-ilm-expiry-replication")) && ctx.IsSet("deployment-id") { + fatalIf(errInvalidArgument(), "--deployment-id should not be set with --disable-ilm-expiry-replication or --enable-ilm-expiry-replication") + } var syncState string if ctx.IsSet("sync") { // for backward compatibility - deprecated Jul 2023 @@ -175,12 +195,15 @@ func mainAdminReplicateUpdate(ctx *cli.Context) error { } ep = u.String() } + var opts madmin.SREditOptions + opts.DisableILMExpiryReplication = ctx.Bool("disable-ilm-expiry-replication") + opts.EnableILMExpiryReplication = ctx.Bool("enable-ilm-expiry-replication") res, e := client.SiteReplicationEdit(globalContext, madmin.PeerInfo{ DeploymentID: ctx.String("deployment-id"), Endpoint: ep, SyncState: madmin.SyncStatus(syncState), DefaultBandwidth: bwDefaults, - }, madmin.SREditOptions{}) + }, opts) fatalIf(probe.NewError(e).Trace(args...), "Unable to edit cluster replication site endpoint") printMsg(updateSuccessMessage(res)) diff --git a/go.mod b/go.mod index 9fba4a7850..b4489f89b5 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/gdamore/tcell/v2 v2.6.0 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/juju/ratelimit v1.0.2 - github.com/minio/madmin-go/v3 v3.0.29 + github.com/minio/madmin-go/v3 v3.0.32 github.com/minio/pkg/v2 v2.0.2 github.com/muesli/reflow v0.3.0 github.com/navidys/tvxwidgets v0.3.0 diff --git a/go.sum b/go.sum index 957371e64e..e1d8fd250b 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,8 @@ github.com/minio/colorjson v1.0.6 h1:m7TUvpvt0u7FBmVIEQNIa0T4NBQlxrcMBp4wJKsg2Ik github.com/minio/colorjson v1.0.6/go.mod h1:LUXwS5ZGNb6Eh9f+t+3uJiowD3XsIWtsvTriUBeqgYs= github.com/minio/filepath v1.0.0 h1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY= github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEXx9T/Bw= -github.com/minio/madmin-go/v3 v3.0.29 h1:3bNLArtxIFud5wyb5/DnF5DGLBvcSJyzCA44EclX1Ow= -github.com/minio/madmin-go/v3 v3.0.29/go.mod h1:4QN2NftLSV7MdlT50dkrenOMmNVHluxTvlqJou3hte8= +github.com/minio/madmin-go/v3 v3.0.32 h1:2O0Up8V/R3eLRQl7wNj+fwoUCRiagYmsGVNUm5V72UA= +github.com/minio/madmin-go/v3 v3.0.32/go.mod h1:4QN2NftLSV7MdlT50dkrenOMmNVHluxTvlqJou3hte8= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ=