From 58771c8dc20992f7ef3a951da3d73ae3b583734a Mon Sep 17 00:00:00 2001 From: chihe Date: Fri, 4 Jul 2025 16:45:38 +0800 Subject: [PATCH] feat(remotecache): add config tool close:#1000151163 Signed-off-by: chihe (cherry picked from commit 037348725c264da5b66dd89096209500b53547d1) --- Makefile | 3 + build/build.sh | 11 + tool/remotecache-config/cmd/cluster.go | 48 ++ tool/remotecache-config/cmd/config.go | 156 ++++++ tool/remotecache-config/cmd/const.go | 179 +++++++ tool/remotecache-config/cmd/flashgroup.go | 574 ++++++++++++++++++++++ tool/remotecache-config/cmd/flashnode.go | 345 +++++++++++++ tool/remotecache-config/cmd/fmt.go | 95 ++++ tool/remotecache-config/cmd/root.go | 81 +++ tool/remotecache-config/cmd/util.go | 71 +++ tool/remotecache-config/main.go | 48 ++ 11 files changed, 1611 insertions(+) create mode 100644 tool/remotecache-config/cmd/cluster.go create mode 100644 tool/remotecache-config/cmd/config.go create mode 100644 tool/remotecache-config/cmd/const.go create mode 100644 tool/remotecache-config/cmd/flashgroup.go create mode 100644 tool/remotecache-config/cmd/flashnode.go create mode 100644 tool/remotecache-config/cmd/fmt.go create mode 100644 tool/remotecache-config/cmd/root.go create mode 100644 tool/remotecache-config/cmd/util.go create mode 100644 tool/remotecache-config/main.go diff --git a/Makefile b/Makefile index ff368d72b..2b472be1f 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,9 @@ bcache: rctest: @build/build.sh rctest $(GOMOD) --threads=$(threads) +rcconfig: + @build/build.sh rcconfig $(GOMOD) --threads=$(threads) + phony += clean clean: @$(RM) -rf build/bin diff --git a/build/build.sh b/build/build.sh index cb4453d29..b01f395da 100755 --- a/build/build.sh +++ b/build/build.sh @@ -556,6 +556,13 @@ build_rctest(){ popd >/dev/null } +build_rcconfig(){ + pushd $SrcPath >/dev/null + echo -n "build cfs-remotecache-config " + CGO_ENABLED=0 go build ${MODFLAGS} -gcflags=all=-trimpath=${SrcPath} -asmflags=all=-trimpath=${SrcPath} -ldflags="${LDFlags}" -o ${BuildBinPath}/cfs-remotecache-config ${SrcPath}/tool/remotecache-config/*.go && echo "success" || echo "failed" + popd >/dev/null +} + clean() { $RM -rf ${BuildBinPath} } @@ -613,6 +620,7 @@ case "$cmd" in build_libsdk build_bcache build_rctest + build_rcconfig ;; "test") run_test @@ -668,6 +676,9 @@ case "$cmd" in "rctest") build_rctest ;; + "rcconfig") + build_rcconfig + ;; *) ;; esac diff --git a/tool/remotecache-config/cmd/cluster.go b/tool/remotecache-config/cmd/cluster.go new file mode 100644 index 000000000..4e7154dff --- /dev/null +++ b/tool/remotecache-config/cmd/cluster.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "github.com/cubefs/cubefs/proto" + "github.com/cubefs/cubefs/sdk/master" + "github.com/spf13/cobra" +) + +const ( + cmdClusterUse = "cluster [COMMAND]" + cmdClusterShort = "Manage cluster components" + + cmdClusterInfoShort = "Show cluster summary information" +) + +func newClusterCmd(client *master.MasterClient) *cobra.Command { + clusterCmd := &cobra.Command{ + Use: cmdClusterUse, + Short: cmdClusterShort, + } + clusterCmd.AddCommand( + newClusterInfoCmd(client), + //TODO + //newClusterStatCmd(client), + //newClusterFreezeCmd(client), + //newClusterSetThresholdCmd(client), + //newClusterSetParasCmd(client), + ) + return clusterCmd +} + +func newClusterInfoCmd(client *master.MasterClient) *cobra.Command { + cmd := &cobra.Command{ + Use: CliOpInfo, + Short: cmdClusterInfoShort, + Run: func(cmd *cobra.Command, args []string) { + var err error + var cv *proto.ClusterView + if cv, err = client.AdminAPI().GetCluster(false); err != nil { + errout(err) + } + stdout("[Cluster]\n") + stdout("%v", formatClusterView(cv)) + stdout("\n") + }, + } + return cmd +} diff --git a/tool/remotecache-config/cmd/config.go b/tool/remotecache-config/cmd/config.go new file mode 100644 index 000000000..3a6e9db4e --- /dev/null +++ b/tool/remotecache-config/cmd/config.go @@ -0,0 +1,156 @@ +package cmd + +import ( + "encoding/json" + "math" + "os" + "path" + "strconv" + "strings" + + "github.com/spf13/cobra" +) + +const ( + cmdConfigShort = "Manage global config file" + cmdConfigSetShort = "set value of config file" + cmdConfigInfoShort = "show info of config file" +) + +type Config struct { + MasterAddr []string `json:"masterAddr"` + Timeout uint16 `json:"timeout"` + ClientIDKey string `json:"clientIDKey"` +} + +var ( + defaultHomeDir, _ = os.UserHomeDir() + defaultConfigName = ".remotecache-config.json" + defaultConfigPath = path.Join(defaultHomeDir, defaultConfigName) + defaultConfigData = []byte(` +{ + "masterAddr": [ + "master.cube.io" + ], + "timeout": 60 +} +`) + defaultConfigTimeout uint16 = 60 +) + +func LoadConfig() (*Config, error) { + var err error + var configData []byte + if configData, err = os.ReadFile(defaultConfigPath); err != nil && !os.IsNotExist(err) { + return nil, err + } + if os.IsNotExist(err) { + if err = os.WriteFile(defaultConfigPath, defaultConfigData, 0o600); err != nil { + return nil, err + } + configData = defaultConfigData + } + config := &Config{} + if err = json.Unmarshal(configData, config); err != nil { + return nil, err + } + if config.Timeout == 0 { + config.Timeout = defaultConfigTimeout + } + return config, nil +} + +func newConfigCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: CliResourceConfig, + Short: cmdConfigShort, + } + cmd.AddCommand(newConfigSetCmd()) + cmd.AddCommand(newConfigInfoCmd()) + return cmd +} + +func newConfigSetCmd() *cobra.Command { + var optMasterHosts string + var optTimeout string + cmd := &cobra.Command{ + Use: CliOpSet, + Short: cmdConfigSetShort, + Long: `Set the config file`, + Run: func(cmd *cobra.Command, args []string) { + var err error + defer func() { + errout(err) + }() + tmp, _ := strconv.Atoi(optTimeout) + if tmp > math.MaxUint16 { + stdoutln("Please reset timeout. Input less than math.MaxUint16") + return + } + timeOut := uint16(tmp) + if optMasterHosts == "" { + stdout("Please set addr. Input 'cfs-cli config set -h' for help.\n") + return + } + + if timeOut == 0 { + stdout("timeOut %v is invalid.\n", timeOut) + return + } + + if err = setConfig(optMasterHosts, timeOut); err != nil { + return + } + stdout("Config has been set successfully!\n") + }, + } + cmd.Flags().StringVar(&optMasterHosts, "addr", "", + "Specify master address {HOST}:{PORT}[,{HOST}:{PORT}]") + cmd.Flags().StringVar(&optTimeout, "timeout", "60", "Specify timeout for requests [Unit: s]") + return cmd +} + +func setConfig(masterHosts string, timeout uint16) (err error) { + var config *Config + if config, err = LoadConfig(); err != nil { + return + } + hosts := strings.Split(masterHosts, ",") + if masterHosts != "" && len(hosts) > 0 { + config.MasterAddr = hosts + } + if timeout != 0 { + config.Timeout = timeout + } + var configData []byte + if configData, err = json.Marshal(config); err != nil { + return + } + if err = os.WriteFile(defaultConfigPath, configData, 0o600); err != nil { + return + } + return nil +} + +func newConfigInfoCmd() *cobra.Command { + var optFilterStatus string + var optFilterWritable string + cmd := &cobra.Command{ + Use: CliOpInfo, + Short: cmdConfigInfoShort, + Run: func(cmd *cobra.Command, args []string) { + config, err := LoadConfig() + errout(err) + printConfigInfo(config) + }, + } + cmd.Flags().StringVar(&optFilterWritable, "filter-writable", "", "Filter node writable status") + cmd.Flags().StringVar(&optFilterStatus, "filter-status", "", "Filter node status [Active, Inactive]") + return cmd +} + +func printConfigInfo(config *Config) { + stdout("Config info:\n") + stdout(" Master Address : %v\n", config.MasterAddr) + stdout(" Request Timeout [s]: %v\n", config.Timeout) +} diff --git a/tool/remotecache-config/cmd/const.go b/tool/remotecache-config/cmd/const.go new file mode 100644 index 000000000..b9062499f --- /dev/null +++ b/tool/remotecache-config/cmd/const.go @@ -0,0 +1,179 @@ +package cmd + +const ( + // List of operation name for cli + CliOpGet = "get" + CliOpList = "list" + CliOpStatus = "stat" + CliOpCreate = "create" + CliOpDelete = "delete" + CliOpRemove = "remove" + CliOpInfo = "info" + CliOpAdd = "add" + CliOpSet = "set" + CliOpUpdate = "update" + CliOpDecommission = "decommission" + CliOpRecommission = "recommission" + CliOpQueryProgress = "query-progress" + CliOpQueryDiskStat = "query-disk-stat" + CliOpQueryNodeStat = "query-node-stat" + CliOpAbortDecommission = "abort-decommission" + CliOpMigrate = "migrate" + CliOpDownloadZip = "load" + CliOpMetaCompatibility = "meta" + CliOpFreeze = "freeze" + CliOpSetThreshold = "threshold" + CliOpSetVolDeletionDelayTime = "volDeletionDelayTime" + CliOpSetCluster = "set" + CliOpCheck = "check" + CliOpReset = "reset" + CliOpReplicate = "add-replica" + CliOpDelReplica = "del-replica" + CliOpExpand = "expand" + CliOpShrink = "shrink" + CliOpGetDiscard = "get-discard" + CliOpSetDiscard = "set-discard" + CliOpForbidMpDecommission = "forbid-mp-decommission" + CliOpQueryDecommissionedDisk = "query-decommissioned-disk" + CliOpQueryDecommissionSuccessDisk = "query-decommissionSuccess-disk" + CliOpEnableAutoDecommission = "enable-auto-decommission" + CliOpQueryDecommissionFailedDisk = "query-decommission-failed-disk" + CliOpSetDecommissionDiskLimit = "set-decommission-disk-limit" + CliOpResetRestoreStatus = "reset-restore-status" + CliOpCancelDecommission = "cancel-decommission" + CliOpDiskOp = "diskop" + CliOpDpOp = "dpop" + CliOpDataNodeOp = "datanodeop" + CliOpVolOp = "volop" + + CliOpSetDecommissionLimit = "set-decommission-limit" + CliOpQueryDecommissionStatus = "query-decommission-status" + + // Shorthand format of operation name + CliOpDecommissionShortHand = "dec" + CliOpOffline = "offline" + + // resource name + CliResourceDataNode = "datanode [COMMAND]" + CliResourceMetaNode = "metanode" + CliResourceDataPartition = "datapartition" + CliResourceMetaPartition = "metapartition" + CliResourceTopology = "topology" + CliResourceRaftNode = "raftnode" + CliResourceDisk = "disk" + CliResourceConfig = "config" + + // Flags + CliFlagName = "name" + CliFlagOnwer = "user" + CliFlagDataPartitionSize = "dp-size" + CliFlagDataPartitionCount = "dp-count" + CliFlagMetaPartitionCount = "mp-count" + CliFlagReplicas = "replicas" + CliFlagEnable = "enable" + CliFlagEnableFollowerRead = "follower-read" + CliFlagAuthenticate = "authenticate" + CliFlagCapacity = "capacity" + CliFlagBusiness = "description" + CliFlagMPCount = "mp-count" + CliFlagDPCount = "dp-count" + CliFlagReplicaNum = "replica-num" + CliFlagSize = "size" + CliFlagVolType = "vol-type" + CliFlagFollowerRead = "follower-read" + CliFlagMetaFollowerRead = "meta-follower-read" + CliFlagMaximallyRead = "maximally-read" + CliFlagCacheRuleKey = "cache-rule-key" + CliFlagEbsBlkSize = "ebs-blk-size" + CliFlagCacheThreshold = "cache-threshold" + CliFlagCacheRule = "cache-rule" + CliFlagThreshold = "threshold" + CliFlagAddress = "addr" + CliFlagDiskPath = "path" + CliFlagAuthKey = "authkey" + CliFlagINodeStartID = "inode-start" + CliFlagId = "id" + CliFlagZoneName = "zone-name" + CliFlagFlashZoneName = "zoneName" + CliFlagDescription = "description" + CliFlagAutoRepairRate = "autoRepairRate" + CliFlagDelBatchCount = "batchCount" + CliFlagDelWorkerSleepMs = "deleteWorkerSleepMs" + CliFlagLoadFactor = "loadFactor" + CliFlagMarkDelRate = "markDeleteRate" + CliFlagMaxDpCntLimit = "maxDpCntLimit" + CliFlagMaxMpCntLimit = "maxMpCntLimit" + CliFlagDataNodeSelector = "dataNodeSelector" + CliFlagMetaNodeSelector = "metaNodeSelector" + CliFlagDataNodesetSelector = "dataNodesetSelector" + CliFlagMetaNodesetSelector = "metaNodesetSelector" + CliFlagCrossZone = "crossZone" + CliNormalZonesFirst = "normalZonesFirst" + CliFlagCount = "count" + CliDpReadOnlyWhenVolFull = "readonly-when-full" + CliTxMask = "transaction-mask" + CliTxTimeout = "transaction-timeout" + CliTxOpLimit = "transaction-limit" + CliTxConflictRetryNum = "tx-conflict-retry-num" + CliTxConflictRetryInterval = "tx-conflict-retry-Interval" + CliTxForceReset = "transaction-force-reset" + CliFlagMaxFiles = "maxFiles" + CliFlagMaxBytes = "maxBytes" + CliFlagMaxConcurrencyInode = "maxConcurrencyInode" + CliFlagForceInode = "forceInode" + CliFlagEnableQuota = "enableQuota" + CliFlagDeleteLockTime = "delete-lock-time" + CliFlagClientIDKey = "clientIDKey" + CliFlagMarkDiskBrokenThreshold = "markBrokenDiskThreshold" + CliFlagForce = "force" + CliFlagEnableCrossZone = "cross-zone" + CliFlagAutoDpMetaRepair = "autoDpMetaRepair" + CliFlagAutoDpMetaRepairParallelCnt = "autoDpMetaRepairParallelCnt" + CliFlagDpRepairTimeout = "dpRepairTimeout" + CliFlagDpTimeout = "dpHeartbeatTimeout" + CliFlagMpTimeout = "mpHeartbeatTimeout" + CliFlagAutoDecommissionDisk = "autoDecommissionDisk" + CliFlagAutoDecommissionDiskInterval = "autoDecommissionDiskInterval" + CliFlagDpBackupTimeout = "dpBackupTimeout" + CliFlagDecommissionDpLimit = "decommissionDpLimit" + CliFlagDecommissionDiskLimit = "decommissionDiskLimit" + CliFlagTrashInterval = "trashInterval" + CliFlagAccessTimeValidInterval = "accessTimeValidInterval" + CliFlagEnablePersistAccessTime = "enablePersistAccessTime" + CliFlagDecommissionRaftForce = "raftForceDel" + CliFLagDecommissionWeight = "decommissionWeight" + CliFlagAllowedStorageClass = "allowedStorageClass" + CliFlagVolStorageClass = "volStorageClass" + CliFlagMediaType = "mediaType" + CliForbidWriteOpOfProtoVersion0 = "forbidWriteOpOfProtoVersion0" + CliFlagVolQuotaClass = "quotaClass" + CliFlagVolQuotaOfClass = "quotaOfStorageClass" + + CliFlagRemoteCacheEnable = "remoteCacheEnable" + CliFlagRemoteCachePath = "remoteCachePath" + CliFlagRemoteCacheAutoPrepare = "remoteCacheAutoPrepare" + CliFlagRemoteCacheTTL = "remoteCacheTTL" + CliFlagRemoteCacheReadTimeout = "remoteCacheReadTimeout" + CliFlagRemoteCacheMaxFileSizeGB = "remoteCacheMaxFileSizeGB" + CliFlagRemoteCacheOnlyForNotSSD = "remoteCacheOnlyForNotSSD" + CliFlagRemoteCacheMultiRead = "remoteCacheMultiRead" + CliFlagFlashNodeTimeoutCount = "flashNodeTimeoutCount" + CliFlagRemoteCacheSameZoneTimeout = "remoteCacheSameZoneTimeout" + CliFlagRemoteCacheSameRegionTimeout = "remoteCacheSameRegionTimeout" + + // CliFlagSetDataPartitionCount = "count" use dp-count instead + + // Shorthand format of resource name + ResourceDataNodeShortHand = "dn" + ResourceMetaNodeShortHand = "mn" + ResourceDataPartitionShortHand = "dp" + ResourceMetaPartitionShortHand = "mp" + + // Usages + CliUsageClientIDKey = "needed if cluster authentication is on" + // version op + CliFlagVersionCreate = "verCreate" + CliFlagVersionList = "verList" + CliFlagVersionDel = "verDel" + CliFlagVersionSetStrategy = "verSetStrategy" +) diff --git a/tool/remotecache-config/cmd/flashgroup.go b/tool/remotecache-config/cmd/flashgroup.go new file mode 100644 index 000000000..f22df9c84 --- /dev/null +++ b/tool/remotecache-config/cmd/flashgroup.go @@ -0,0 +1,574 @@ +package cmd + +import ( + "fmt" + "math" + "sort" + "strconv" + "strings" + + "github.com/cubefs/cubefs/proto" + "github.com/cubefs/cubefs/sdk/master" + "github.com/spf13/cobra" +) + +const _flashgroupID = " [FlashGroupID]" + +type slotInfo struct { + fgID uint64 + slot uint32 + percent float64 +} + +func newFlashGroupCmd(client *master.MasterClient) *cobra.Command { + cmd := &cobra.Command{ + Use: "flashgroup [COMMAND]", + Short: "cluster flashgroup management", + } + cmd.AddCommand( + newCmdFlashGroupTurn(client), + newCmdFlashGroupCreate(client), + newCmdFlashGroupSet(client), + newCmdFlashGroupRemove(client), + newCmdFlashGroupNodeAdd(client), + newCmdFlashGroupNodeRemove(client), + newCmdFlashGroupGet(client), + newCmdFlashGroupList(client), + newCmdFlashGroupClient(client), + newCmdFlashGroupSearch(client), + newCmdFlashGroupGraph(client), + ) + return cmd +} + +func newCmdFlashGroupTurn(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: "turn [IsEnable]", + Short: "turn flash group cache", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + enabled, err := strconv.ParseBool(args[0]) + if err != nil { + return + } + result, err := client.AdminAPI().TurnFlashGroup(enabled) + if err != nil { + return + } + stdoutln(result) + return + }, + } +} + +func newCmdFlashGroupCreate(client *master.MasterClient) *cobra.Command { + var optSlots string + var optWeight int + var optGradualFlag bool + var optStep uint32 + cmd := &cobra.Command{ + Use: CliOpCreate, + Short: "create a new flash group", + Args: cobra.MinimumNArgs(0), + RunE: func(cmd *cobra.Command, args []string) (err error) { + if optSlots != "" { + numbers := strings.Split(optSlots, ",") + for _, numStr := range numbers { + _, err = strconv.Atoi(numStr) + if err != nil { + return + } + } + } + if optWeight <= 0 || optWeight > proto.FlashGroupMaxWeight { + err = fmt.Errorf("param weight(%v) must greater than 0 and not greater than %v", optWeight, proto.FlashGroupMaxWeight) + return + } + + if optGradualFlag { + if optStep <= 0 { + err = fmt.Errorf("param step(%v) must greater than 0", optStep) + return + } + } + + fgView, err := client.AdminAPI().CreateFlashGroup(optSlots, optWeight, optGradualFlag, optStep) + if err != nil { + return + } + stdoutln(formatFlashGroupView(&fgView)) + return + }, + } + cmd.Flags().StringVar(&optSlots, "slots", "", "set group in which slots, --slots=slot1,slot2,...") + cmd.Flags().IntVar(&optWeight, "weight", proto.FlashGroupDefaultWeight, "set group weight(default 1, must 1<=weight<=30), if it was specified slots count equal to 32*weight") + cmd.Flags().BoolVar(&optGradualFlag, "gradualFlag", false, "set whether the group's slots are created gradually or not(default false)") + cmd.Flags().Uint32Var(&optStep, "step", 1, "set the step size(default 1) for slot gradual creation") + return cmd +} + +func newCmdFlashGroupSet(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: CliOpSet + _flashgroupID + " [IsActive]", + Short: "set flash group active or not", + Args: cobra.MinimumNArgs(2), + RunE: func(cmd *cobra.Command, args []string) (err error) { + flashGroupID, err := parseFlashGroupID(args[0]) + if err != nil { + return + } + isActive, err := strconv.ParseBool(args[1]) + if err != nil { + return + } + fgView, err := client.AdminAPI().SetFlashGroup(flashGroupID, isActive) + if err != nil { + return + } + stdoutln(formatFlashGroupView(&fgView)) + return + }, + } +} + +func newCmdFlashGroupRemove(client *master.MasterClient) *cobra.Command { + var optYes bool + var optGradualFlag bool + var optStep uint32 + cmd := &cobra.Command{ + Use: CliOpRemove + _flashgroupID, + Short: "remove flash group by id", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + flashGroupID, err := parseFlashGroupID(args[0]) + if err != nil { + return + } + // ask user for confirm + if !optYes { + fmt.Printf("remove flash group by id[%d]\n", flashGroupID) + stdout("\nConfirm (yes/no)[no]: ") + var userConfirm string + _, _ = fmt.Scanln(&userConfirm) + if userConfirm != "yes" { + err = fmt.Errorf("Abort by user.\n") + return + } + } + + if optGradualFlag { + if optStep <= 0 { + err = fmt.Errorf("param step(%v) must greater than 0", optStep) + return + } + } + + result, err := client.AdminAPI().RemoveFlashGroup(flashGroupID, optGradualFlag, optStep) + if err != nil { + return + } + stdoutln(result) + return + }, + } + cmd.Flags().BoolVarP(&optYes, "yes", "y", false, "Answer yes for all questions") + cmd.Flags().BoolVar(&optGradualFlag, "gradualFlag", false, "set whether the group's slots are deleted gradually or not(default false)") + cmd.Flags().Uint32Var(&optStep, "step", 1, "set the step size(default 1) for slot gradual deletion") + return cmd +} + +func newCmdFlashGroupNodeAdd(client *master.MasterClient) *cobra.Command { + var ( + optAddr string + optZoneName string + optCount int + ) + cmd := &cobra.Command{ + Use: "nodeAdd" + _flashgroupID, + Short: "add flash node to given flash group", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + flashGroupID, err := parseFlashGroupID(args[0]) + if err != nil { + return + } + fgView, err := client.AdminAPI().FlashGroupAddFlashNode(flashGroupID, optCount, optZoneName, optAddr) + if err != nil { + return + } + stdoutln(formatFlashGroupView(&fgView)) + return + }, + } + cmd.Flags().StringVar(&optAddr, CliFlagAddress, "", "add flash node of given addr") + cmd.Flags().StringVar(&optZoneName, CliFlagFlashZoneName, "", "add flash node from given zone") + cmd.Flags().IntVar(&optCount, CliFlagCount, 0, "add given count flash node from zone") + return cmd +} + +func newCmdFlashGroupNodeRemove(client *master.MasterClient) *cobra.Command { + var ( + optAddr string + optZoneName string + optCount int + optYes bool + ) + cmd := &cobra.Command{ + Use: "nodeRemove" + _flashgroupID, + Short: "remove flash node to given flash group", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + flashGroupID, err := parseFlashGroupID(args[0]) + if err != nil { + return + } + // ask user for confirm + if !optYes { + fmt.Printf("remove flash node to given flash groupid[%d]\n", flashGroupID) + if optAddr != "" { + stdout(" FlashNode Addr : %v\n", optAddr) + } else { + stdout(" Zone : %v\n", optZoneName) + stdout(" Count : %d\n", optCount) + } + stdout("\nConfirm (yes/no)[no]: ") + var userConfirm string + _, _ = fmt.Scanln(&userConfirm) + if userConfirm != "yes" { + err = fmt.Errorf("Abort by user.\n") + return + } + } + fgView, err := client.AdminAPI().FlashGroupRemoveFlashNode(flashGroupID, optCount, optZoneName, optAddr) + if err != nil { + return + } + stdoutln(formatFlashGroupView(&fgView)) + return + }, + } + cmd.Flags().StringVar(&optAddr, CliFlagAddress, "", "remove flash node of given addr") + cmd.Flags().StringVar(&optZoneName, CliFlagFlashZoneName, "", "remove flash node from given zone") + cmd.Flags().IntVar(&optCount, CliFlagCount, 0, "remove given count flash node from zone") + cmd.Flags().BoolVarP(&optYes, "yes", "y", false, "Answer yes for all questions") + return cmd +} + +func newCmdFlashGroupGet(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: CliOpInfo + _flashgroupID + " [showHitRate ture/false] ", + Short: "get flash group by id, default don't show hit rate", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + flashGroupID, err := parseFlashGroupID(args[0]) + if err != nil { + return + } + fgView, err := client.AdminAPI().GetFlashGroup(flashGroupID) + if err != nil { + return + } + stdoutln(formatFlashGroupView(&fgView)) + + showHitRate := false + if len(args) > 1 { + showHitRate, _ = strconv.ParseBool(args[1]) + } + + stdoutln("[Flash Nodes]") + var tbl table + if showHitRate { + tbl = table{formatFlashNodeViewTableTitle} + } else { + tbl = table{formatFlashNodeSimpleViewTableTitle} + } + for _, flashNodeViewInfos := range fgView.ZoneFlashNodes { + tbl = showFlashNodesView(flashNodeViewInfos, showHitRate, nil, tbl) + } + stdoutln(alignTable(tbl...)) + return + }, + } +} + +func newCmdFlashGroupList(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: CliOpList + " [IsActive]", + Short: "list active or inactive flash groups", + Args: cobra.MinimumNArgs(0), + RunE: func(cmd *cobra.Command, args []string) (err error) { + var fgView proto.FlashGroupsAdminView + var isActive bool + if len(args) > 0 { + if isActive, err = strconv.ParseBool(args[0]); err != nil { + return + } + fgView, err = client.AdminAPI().ListFlashGroup(isActive) + } else { + fgView, err = client.AdminAPI().ListFlashGroups() + } + if err != nil { + return + } + sort.Slice(fgView.FlashGroups, func(i, j int) bool { + return fgView.FlashGroups[i].ID < fgView.FlashGroups[j].ID + }) + + stdoutln("[Flash Groups]") + slots := make([]*slotInfo, 0) + reservedSlots := make([]*slotInfo, 0) + tbl := table{formatFlashGroupViewTile} + for _, group := range fgView.FlashGroups { + sort.Slice(group.Slots, func(i, j int) bool { + return group.Slots[i] < group.Slots[j] + }) + for _, slot := range group.Slots { + slots = append(slots, &slotInfo{ + fgID: group.ID, + slot: slot, + }) + } + sort.Slice(group.ReservedSlots, func(i, j int) bool { + return group.ReservedSlots[i] < group.ReservedSlots[j] + }) + for _, slot := range group.ReservedSlots { + reservedSlots = append(reservedSlots, &slotInfo{ + fgID: group.ID, + slot: slot, + }) + } + tbl = tbl.append(arow(group.ID, group.Weight, len(group.Slots), len(group.ReservedSlots), group.Status, group.SlotStatus, len(group.PendingSlots), group.Step, group.FlashNodeCount, group.IsReducingSlots)) + } + stdoutln(alignTable(tbl...)) + + sort.Slice(slots, func(i, j int) bool { + return slots[i].slot < slots[j].slot + }) + stdoutln("Slots:") + for i, info := range slots { + if i < len(slots)-1 { + info.percent = float64(slots[i+1].slot-info.slot) * 100 / math.MaxUint32 + } else { + info.percent = float64(math.MaxUint32-info.slot) * 100 / math.MaxUint32 + } + stdoutlnf("num:%d slot:%d fg:%d percent:%0.5f%%", i+1, info.slot, info.fgID, info.percent) + } + stdoutln("ReservedSlots:") + for i, info := range reservedSlots { + if i < len(reservedSlots)-1 { + info.percent = float64(reservedSlots[i+1].slot-info.slot) * 100 / math.MaxUint32 + } else { + info.percent = float64(math.MaxUint32-info.slot) * 100 / math.MaxUint32 + } + stdoutlnf("num:%d slot:%d fg:%d percent:%0.5f%%", i+1, info.slot, info.fgID, info.percent) + } + return + }, + } +} + +func newCmdFlashGroupClient(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: "client", + Short: "show flash group response passed from master to client", + Args: cobra.MinimumNArgs(0), + RunE: func(cmd *cobra.Command, args []string) (err error) { + fgv, err := client.AdminAPI().ClientFlashGroups() + if err != nil { + return + } + stdoutln("Flash Group Response:") + stdoutln(formatIndent(fgv)) + return + }, + } +} + +func newCmdFlashGroupSearch(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: "search [volume] [inode] [offset]", + Short: "search flash group by volume inode offset", + Args: cobra.MinimumNArgs(3), + RunE: func(cmd *cobra.Command, args []string) (err error) { + volume := args[0] + if volume == "" { + err = fmt.Errorf("volume is empty") + return + } + inode, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return + } + offset, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + return + } + slotKey := proto.ComputeCacheBlockSlot(volume, inode, offset) + + fgView, err := client.AdminAPI().ListFlashGroups() + if err != nil { + return + } + set := make(map[uint32]struct{}) + slots := make([]slotInfo, 0) + for _, fg := range fgView.FlashGroups { + if fg.Status != proto.FlashGroupStatus_Active { + continue + } + for _, slot := range fg.Slots { + if _, in := set[slot]; in { + continue + } + slots = append(slots, slotInfo{ + fgID: fg.ID, + slot: slot, + }) + } + } + sort.Slice(slots, func(i, j int) bool { + return slots[i].slot < slots[j].slot + }) + + var whichGroup uint64 + for _, slot := range slots { + if slotKey >= slot.slot { + whichGroup = slot.fgID + } + } + for _, fg := range fgView.FlashGroups { + if fg.ID == whichGroup { + stdoutlnf("Found in FlashGroup:%d", whichGroup) + tbl := table{formatFlashNodeSimpleViewTableTitle} + for _, fnNodes := range fg.ZoneFlashNodes { + tbl = showFlashNodesView(fnNodes, false, nil, tbl) + } + stdoutln(alignTable(tbl...)) + return + } + } + stdoutlnf("Not found (%s %d %d) -> %d", volume, inode, offset, slotKey) + return + }, + } +} + +func newCmdFlashGroupGraph(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: "graph", + Short: "show flash group and node", + Args: cobra.MinimumNArgs(0), + RunE: func(cmd *cobra.Command, args []string) (err error) { + fgView, err := client.AdminAPI().ListFlashGroups() + if err != nil { + return + } + set := make(map[uint32]struct{}) + groups := make(map[uint64]proto.FlashGroupAdminView) + groupn := make(map[uint64]int) + groupStatusMap := make(map[uint64]string) + slots := make([]slotInfo, 0) + reservedSlots := make([]slotInfo, 0) + for _, fg := range fgView.FlashGroups { + groups[fg.ID] = fg + groupn[fg.ID] = 0 + groupStatusMap[fg.ID] = fg.Status.String() + for _, slot := range fg.Slots { + if _, in := set[slot]; in { + continue + } + groupn[fg.ID]++ + slots = append(slots, slotInfo{ + fgID: fg.ID, + slot: slot, + }) + } + for _, slot := range fg.ReservedSlots { + if _, in := set[slot]; in { + continue + } + groupn[fg.ID]++ + reservedSlots = append(reservedSlots, slotInfo{ + fgID: fg.ID, + slot: slot, + }) + } + } + sort.Slice(slots, func(i, j int) bool { + return slots[i].slot < slots[j].slot + }) + stdoutln("[Flash Groups]") + stdoutln("[Slots]") + tbl := table{arow("Slot", "ID", "Status", "Count", "Ref", "Proportion")} + for idx, slot := range slots { + g := groups[slot.fgID] + var p string + if idx == len(slots)-1 { + p = proportion(slot.slot, math.MaxUint32) + } else { + p = proportion(slot.slot, slots[idx+1].slot) + } + tbl = tbl.append(arow(slot.slot, g.ID, g.Status.String(), g.FlashNodeCount, groupn[g.ID], p)) + } + stdoutln(alignTable(tbl...)) + + sort.Slice(reservedSlots, func(i, j int) bool { + return reservedSlots[i].slot < reservedSlots[j].slot + }) + stdoutln("[ReservedSlots]") + tbl1 := table{arow("Slot", "ID", "Status", "Count", "Ref", "Proportion")} + for idx, slot := range reservedSlots { + g := groups[slot.fgID] + var p string + if idx == len(reservedSlots)-1 { + p = proportion(slot.slot, math.MaxUint32) + } else { + p = proportion(slot.slot, reservedSlots[idx+1].slot) + } + tbl1 = tbl1.append(arow(slot.slot, g.ID, g.Status.String(), g.FlashNodeCount, groupn[g.ID], p)) + } + + stdoutln(alignTable(tbl1...)) + + fnView, err := client.NodeAPI().ListFlashNodes(-1) + if err != nil { + return + } + busyNodes := make([]*proto.FlashNodeViewInfo, 0) + idleNodes := make([]*proto.FlashNodeViewInfo, 0) + for _, nodes := range fnView { + for _, node := range nodes { + if node.FlashGroupID == 0 { + idleNodes = append(idleNodes, node) + } else { + busyNodes = append(busyNodes, node) + } + } + } + graphFlashNodeTitle := make([]interface{}, len(formatFlashNodeViewTableTitle)+1) + copy(graphFlashNodeTitle, formatFlashNodeViewTableTitle[:6]) + graphFlashNodeTitle[6] = "GroupStatus" + copy(graphFlashNodeTitle[7:], formatFlashNodeViewTableTitle[6:]) + stdoutln("[FlashNodes Busy]") + tbl = showFlashNodesView(busyNodes, true, groupStatusMap, table{graphFlashNodeTitle}) + stdoutln(alignTable(tbl...)) + stdoutln("[FlashNodes Idle]") + tbl = showFlashNodesView(idleNodes, true, nil, table{formatFlashNodeViewTableTitle}) + stdoutln(alignTable(tbl...)) + return + }, + } +} + +func parseFlashGroupID(id string) (uint64, error) { + return strconv.ParseUint(id, 10, 64) +} + +const fullDot = ".................................................." + +func proportion(s, e uint32) string { + p := "." + if n := int(float64(e-s) * float64(len(fullDot)) / float64(math.MaxUint32)); n > 0 { + p = fullDot[:n] + } + return p +} diff --git a/tool/remotecache-config/cmd/flashnode.go b/tool/remotecache-config/cmd/flashnode.go new file mode 100644 index 000000000..655c2f230 --- /dev/null +++ b/tool/remotecache-config/cmd/flashnode.go @@ -0,0 +1,345 @@ +package cmd + +import ( + "fmt" + "sort" + "strconv" + "strings" + + "github.com/cubefs/cubefs/proto" + "github.com/cubefs/cubefs/sdk/httpclient" + "github.com/cubefs/cubefs/sdk/master" + "github.com/spf13/cobra" +) + +const _flashnodeAddr = " [FlashNodeAddr]" + +func newFlashNodeCmd(client *master.MasterClient) *cobra.Command { + cmd := &cobra.Command{ + Use: "flashnode [COMMAND]", + Short: "cluster flashnode management", + } + cmd.AddCommand( + newCmdFlashNodeSet(client), + newCmdFlashNodeRemove(client), + newCmdFlashNodeGet(client), + newCmdFlashNodeList(client), + newCmdFlashNodeRemoveAllInactive(client), + + newCmdFlashNodeHTTPStat(client), + newCmdFlashNodeHTTPStatAll(client), + newCmdFlashNodeHTTPEvict(client), + newCmdFlashNodeHTTPInactiveDisk(client), + newCmdFlashNodeHTTPSlotStat(client), + ) + return cmd +} + +func newCmdFlashNodeSet(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: CliOpSet + _flashnodeAddr + " [IsEnable]", + Short: "set flash node enable or not", + Args: cobra.MinimumNArgs(2), + RunE: func(_ *cobra.Command, args []string) (err error) { + addr := args[0] + enable, err := strconv.ParseBool(args[1]) + if err != nil { + return + } + if err = client.NodeAPI().SetFlashNode(addr, enable); err != nil { + return + } + stdoutlnf("set flashnode:%s enable:%v success", addr, enable) + return + }, + } +} + +func newCmdFlashNodeRemove(client *master.MasterClient) *cobra.Command { + var optYes bool + cmd := &cobra.Command{ + Use: CliOpRemove + _flashnodeAddr, + Short: "remove flash node by addr", + Args: cobra.MinimumNArgs(1), + RunE: func(_ *cobra.Command, args []string) (err error) { + if !optYes { + fmt.Printf("decommission flashnode:[%v]\n", args[0]) + stdout("\nConfirm (yes/no)[no]: ") + var userConfirm string + _, _ = fmt.Scanln(&userConfirm) + if userConfirm != "yes" { + err = fmt.Errorf("Abort by user.\n") + return + } + } + result, err := client.NodeAPI().RemoveFlashNode(args[0]) + if err != nil { + return + } + stdoutlnf("decommission flashnode:%s %s", args[0], result) + return + }, + } + cmd.Flags().BoolVarP(&optYes, "yes", "y", false, "Answer yes for all questions") + return cmd +} + +func newCmdFlashNodeRemoveAllInactive(client *master.MasterClient) *cobra.Command { + var optYes bool + cmd := &cobra.Command{ + Use: "removeAllInactive", + Short: "remove all inactive flash nodes", + Args: cobra.MinimumNArgs(0), + RunE: func(_ *cobra.Command, args []string) (err error) { + if !optYes { + fmt.Printf("remove all inactive flash nodes") + stdout("\nConfirm (yes/no)[no]: ") + var userConfirm string + _, _ = fmt.Scanln(&userConfirm) + if userConfirm != "yes" { + err = fmt.Errorf("Abort by user.\n") + return + } + } + var rmNodes []string + rmNodes, err = client.NodeAPI().RemoveAllInactiveFlashNodes() + if err != nil { + return + } + + stdoutlnf("total remove %v flash nodes", len(rmNodes)) + for _, rmNode := range rmNodes { + stdoutlnf("remove flashnode:%s", rmNode) + } + return + }, + } + cmd.Flags().BoolVarP(&optYes, "yes", "y", false, "Answer yes for all questions") + return cmd +} + +func newCmdFlashNodeGet(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: CliOpInfo + _flashnodeAddr, + Short: "get flash node by addr", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) (err error) { + fn, err := client.NodeAPI().GetFlashNode(args[0]) + if err != nil { + return + } + stdoutln(formatFlashNodeView(&fn)) + return + }, + } +} + +func newCmdFlashNodeList(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: CliOpList, + Short: "list all flash nodes or [active true/false] flash nodes", + Args: cobra.MinimumNArgs(0), + RunE: func(cmd *cobra.Command, args []string) (err error) { + var active bool + activeFilter := -1 + filterStr := "all" + if len(args) == 1 { + if active, err = strconv.ParseBool(args[0]); err != nil { + err = fmt.Errorf("Parse bool fail: %v\n", err) + return + } + if active { + activeFilter = 1 + filterStr = "active:true" + } else { + activeFilter = 0 + filterStr = "active:false" + } + } + zoneFlashNodes, err := client.NodeAPI().ListFlashNodes(activeFilter) + if err != nil { + return + } + stdoutln(fmt.Sprintf("[FlashNodes] %s", filterStr)) + tbl := table{formatFlashNodeViewTableTitle} + for _, flashNodeViewInfos := range zoneFlashNodes { + tbl = showFlashNodesView(flashNodeViewInfos, true, nil, tbl) + } + stdoutln(alignTable(tbl...)) + return + }, + } +} + +func newCmdFlashNodeHTTPStat(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: "httpStat" + _flashnodeAddr, + Short: "show flashnode stat", + Args: cobra.MinimumNArgs(1), + RunE: func(_ *cobra.Command, args []string) (err error) { + // check flashnode whether exist + _, err = client.NodeAPI().GetFlashNode(args[0]) + if err != nil { + return + } + stat, err := httpclient.New().Addr(addr2Prof(args[0])).FlashNode().Stat() + if err != nil { + return + } + stdoutln(formatIndent(stat)) + return + }, + } +} + +func newCmdFlashNodeHTTPStatAll(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: "httpStatAll" + _flashnodeAddr, + Short: "show flashnode stat all(key with expired time)", + Args: cobra.MinimumNArgs(1), + RunE: func(_ *cobra.Command, args []string) (err error) { + // check flashnode whether exist + _, err = client.NodeAPI().GetFlashNode(args[0]) + if err != nil { + return + } + stat, err := httpclient.New().Addr(addr2Prof(args[0])).FlashNode().StatAll() + if err != nil { + return + } + stdoutln(formatIndent(stat)) + return + }, + } +} + +func newCmdFlashNodeHTTPSlotStat(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: "httpSlotStat" + _flashnodeAddr, + Short: "show flashnode slot stat", + Args: cobra.MinimumNArgs(1), + RunE: func(_ *cobra.Command, args []string) (err error) { + // check flashnode whether exist + _, err = client.NodeAPI().GetFlashNode(args[0]) + if err != nil { + return + } + stat, err := httpclient.New().Addr(addr2Prof(args[0])).FlashNode().SlotStat() + if err != nil { + return + } + + sort.SliceStable(stat.SlotStat, func(i, j int) bool { + return stat.SlotStat[i].SlotId < stat.SlotStat[j].SlotId + }) + stdout("%v\n", formatFlashNodeSlotStat(&stat)) + return + }, + } +} + +func newCmdFlashNodeHTTPEvict(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: "httpEvict" + _flashnodeAddr + " [volume]", + Short: "evict cache in flashnode", + Args: cobra.MinimumNArgs(1), + RunE: func(_ *cobra.Command, args []string) (err error) { + addr := args[0] + // check flashnode whether exist + _, err = client.NodeAPI().GetFlashNode(addr) + if err != nil { + return + } + if len(args) == 1 { + if err = httpclient.New().Addr(addr2Prof(addr)).FlashNode().EvictAll(); err == nil { + stdoutlnf("%s evicts all [OK]", addr) + } + return + } + volume := args[1] + if err = httpclient.New().Addr(addr2Prof(addr)).FlashNode().EvictVol(volume); err == nil { + stdoutlnf("%s evicts volume(%s) [OK]", addr, volume) + } + return + }, + } +} + +func newCmdFlashNodeHTTPInactiveDisk(client *master.MasterClient) *cobra.Command { + return &cobra.Command{ + Use: "httpInactiveDisk" + _flashnodeAddr + " [dataPath]", + Short: "inactive the disk in flashnode", + Args: cobra.MinimumNArgs(2), + RunE: func(_ *cobra.Command, args []string) (err error) { + addr := args[0] + // check flashnode whether exist + _, err = client.NodeAPI().GetFlashNode(addr) + if err != nil { + return + } + dataPath := args[1] + if err = httpclient.New().Addr(addr2Prof(addr)).FlashNode().InactiveDisk(dataPath); err == nil { + stdoutlnf("%s inactives dataPath(%s) [OK]", addr, dataPath) + } + return + }, + } +} + +func showFlashNodesView(flashNodeViewInfos []*proto.FlashNodeViewInfo, showStat bool, groupStats map[uint64]string, tbl table) table { + sort.Slice(flashNodeViewInfos, func(i, j int) bool { + return flashNodeViewInfos[i].ID < flashNodeViewInfos[j].ID + }) + var groupActiveInfo string + for _, fn := range flashNodeViewInfos { + groupActiveInfo = "" + nodeInfo := arow(fn.ZoneName, fn.ID, fn.Addr, formatYesNo(fn.IsActive), formatYesNo(fn.IsEnable), + fn.FlashGroupID, formatTimeToString(fn.ReportTime)) + if groupStats != nil { + if v, ok := groupStats[fn.FlashGroupID]; ok { + groupActiveInfo = v + } + nodeInfo = arow(fn.ZoneName, fn.ID, fn.Addr, formatYesNo(fn.IsActive), formatYesNo(fn.IsEnable), + fn.FlashGroupID, groupActiveInfo, formatTimeToString(fn.ReportTime)) + } + if !showStat { + tbl = tbl.append(nodeInfo) + continue + } + if len(fn.DiskStat) == 0 { + nodeInfo = append(nodeInfo, "N/A", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A") + tbl = tbl.append(nodeInfo) + continue + } + for index, stat := range fn.DiskStat { + dataPath, hitRate, evicts, limit, maxAlloc, hasAlloc, num, status := "N/A", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A", "N/A" + if fn.IsActive && fn.IsEnable { + dataPath = stat.DataPath + hitRate = fmt.Sprintf("%.2f%%", stat.HitRate*100) + evicts = strconv.Itoa(stat.Evicts) + limit = strconv.FormatUint(uint64(stat.ReadRps), 10) + maxAlloc = strconv.FormatInt(stat.MaxAlloc, 10) + hasAlloc = strconv.FormatInt(stat.HasAlloc, 10) + num = strconv.Itoa(stat.KeyNum) + status = strconv.Itoa(stat.Status) + } + if index != 0 { + if groupStats != nil { + nodeInfo = arow("", "", "", "", "", "", "", "") + } else { + nodeInfo = arow("", "", "", "", "", "", "") + } + } + nodeInfo = append(nodeInfo, dataPath, hitRate, evicts, limit, maxAlloc, hasAlloc, num, status) + tbl = tbl.append(nodeInfo) + } + } + return tbl +} + +// TODO: mandatory design prof http port is service port+1 +func addr2Prof(addr string) string { + arr := strings.SplitN(addr, ":", 2) + p, _ := strconv.ParseUint(arr[1], 10, 64) + return fmt.Sprintf("%s:%d", arr[0], p+1) +} diff --git a/tool/remotecache-config/cmd/fmt.go b/tool/remotecache-config/cmd/fmt.go new file mode 100644 index 000000000..93fb13fc1 --- /dev/null +++ b/tool/remotecache-config/cmd/fmt.go @@ -0,0 +1,95 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/cubefs/cubefs/proto" +) + +var ( + formatFlashNodeSimpleViewTableTitle = arow("Zone", "ID", "Address", "Active", "Enable", "FlashGroupID", "ReportTime") + formatFlashNodeViewTableTitle = append(formatFlashNodeSimpleViewTableTitle[:], "DataPath", "HitRate", "Evicts", "Limit", "MaxAlloc", "HasAlloc", "Num", "Status") + formatFlashGroupViewTile = arow("ID", "Weight", "Slots", "ReservedSlots", "Status", "SlotStatus", "PendingSlots", "Step", "FlashNodeCount", "ReducingSlots") +) + +func formatClusterView(cv *proto.ClusterView) string { + sb := strings.Builder{} + sb.WriteString(fmt.Sprintf(" Cluster name : %v\n", cv.Name)) + sb.WriteString(fmt.Sprintf(" Master leader : %v\n", cv.LeaderAddr)) + for _, master := range cv.MasterNodes { + sb.WriteString(fmt.Sprintf(" Master-%d : %v\n", master.ID, master.Addr)) + } + sb.WriteString(fmt.Sprintf(" FlashNodeHandleReadTimeout : %v ms\n", cv.FlashNodeHandleReadTimeout)) + sb.WriteString(fmt.Sprintf(" FlashNodeReadDataNodeTimeout : %v ms\n", cv.FlashNodeReadDataNodeTimeout)) + sb.WriteString(fmt.Sprintf(" RemoteCacheTTL : %v s\n", cv.RemoteCacheTTL)) + sb.WriteString(fmt.Sprintf(" RemoteCacheReadTimeout : %v ms\n", cv.RemoteCacheReadTimeout)) + sb.WriteString(fmt.Sprintf(" RemoteCacheMultiRead : %v\n", cv.RemoteCacheMultiRead)) + sb.WriteString(fmt.Sprintf(" FlashNodeTimeoutCount : %v\n", cv.FlashNodeTimeoutCount)) + sb.WriteString(fmt.Sprintf(" RemoteCacheSameZoneTimeout : %v microsecond\n", cv.RemoteCacheSameZoneTimeout)) + sb.WriteString(fmt.Sprintf(" RemoteCacheSameRegionTimeout : %v millisecond\n", cv.RemoteCacheSameRegionTimeout)) + return sb.String() +} + +func formatFlashNodeView(fn *proto.FlashNodeViewInfo) string { + return "[FlashNode]\n" + alignColumn( + arow(" ID", fn.ID), + arow(" Address", fn.Addr), + arow(" Version", fn.Version), + arow(" ZoneName", fn.ZoneName), + arow(" FlashGroupID", fn.FlashGroupID), + arow(" ReportTime", formatTimeToString(fn.ReportTime)), + arow(" IsActive", fn.IsActive), + arow(" IsEnable", fn.IsEnable), + ) +} + +func formatTimeToString(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} + +func formatIndent(v interface{}) string { + b, _ := json.MarshalIndent(v, "", " ") + return string(b) +} + +var ( + flashnodeSlotStatTablePattern = "%-12v %-12v %-12v %-12v %-12v\n" + flashnodeSlotStatTableHeader = fmt.Sprintf(flashnodeSlotStatTablePattern, + "SlotId", "OwnerSlotId", "HitCount", "HitRate", "RecentTime") +) + +func formatFlashNodeSlotStat(stat *proto.FlashNodeSlotStat) string { + sb := strings.Builder{} + sb.WriteString(flashnodeSlotStatTableHeader) + for _, slot := range stat.SlotStat { + hitrate := fmt.Sprintf("%.2f%%", slot.HitRate*100) + recentTime := formatTimeToString(slot.RecentTime) + sb.WriteString(fmt.Sprintf(flashnodeSlotStatTablePattern, slot.SlotId, slot.OwnerSlotId, slot.HitCount, hitrate, recentTime)) + } + + return sb.String() +} + +func formatYesNo(b bool) string { + if b { + return "Yes" + } + return "No" +} + +func formatFlashGroupView(fg *proto.FlashGroupAdminView) string { + return "[FlashGroup]\n" + + fmt.Sprintf(" ID:%v\n", fg.ID) + + fmt.Sprintf(" Weight:%v\n", fg.Weight) + + fmt.Sprintf(" Slots:%v\n", fg.Slots) + + fmt.Sprintf(" ReservedSlots:%v\n", fg.ReservedSlots) + + fmt.Sprintf(" IsReducingSlots:%v\n", fg.IsReducingSlots) + + fmt.Sprintf(" Status:%v\n", fg.Status) + + fmt.Sprintf(" SlotStatus:%v\n", fg.SlotStatus) + + fmt.Sprintf(" PedningSlots:%v\n", fg.PendingSlots) + + fmt.Sprintf(" Step:%v\n", fg.Step) + + fmt.Sprintf(" FlashNodeCount:%v\n", fg.FlashNodeCount) +} diff --git a/tool/remotecache-config/cmd/root.go b/tool/remotecache-config/cmd/root.go new file mode 100644 index 000000000..a55d222b4 --- /dev/null +++ b/tool/remotecache-config/cmd/root.go @@ -0,0 +1,81 @@ +package cmd + +import ( + "fmt" + "os" + "path" + + "github.com/cubefs/cubefs/proto" + "github.com/cubefs/cubefs/sdk/master" + "github.com/cubefs/cubefs/util/log" + "github.com/spf13/cobra" +) + +type CubeFSCmd struct { + CFSCmd *cobra.Command +} + +func NewRootCmd(client *master.MasterClient) *CubeFSCmd { + var optShowVersion bool + cmd := &CubeFSCmd{ + CFSCmd: &cobra.Command{ + Use: path.Base(os.Args[0]), + Short: "CubeFS Command Line Interface (CLI)", + Args: cobra.MinimumNArgs(0), + Run: func(cmd *cobra.Command, args []string) { + if optShowVersion { + stdout("%v", proto.DumpVersion("CLI")) + return + } + if len(args) == 0 { + cmd.Help() + return + } + suggestionsString := "" + if suggestions := cmd.SuggestionsFor(args[0]); len(suggestions) > 0 { + suggestionsString += "\nDid you mean this?\n" + for _, s := range suggestions { + suggestionsString += fmt.Sprintf("\t%v\n", s) + } + } + errout(fmt.Errorf("cfs-cli: unknown command %q\n%s", args[0], suggestionsString)) + }, + SilenceErrors: true, + SilenceUsage: true, + }, + } + cmd.CFSCmd.Flags().BoolVarP(&optShowVersion, "version", "v", false, "Show version information") + + cmd.CFSCmd.AddCommand( + newConfigCmd(), + newClusterCmd(client), + newFlashNodeCmd(client), + newFlashGroupCmd(client), + ) + return cmd +} + +var stdout = stdoutf + +func stdoutf(format string, a ...interface{}) { + fmt.Fprintf(os.Stdout, format, a...) +} + +func errout(err error) { + if err == nil { + return + } + fmt.Fprintln(os.Stderr, "Error:", err) + log.LogError("Error:", err) + log.LogFlush() + os.Exit(1) +} + +func stdoutln(a ...interface{}) { + fmt.Fprintln(os.Stdout, a...) +} + +func stdoutlnf(format string, a ...interface{}) { + stdoutf(format, a...) + fmt.Fprintln(os.Stdout) +} diff --git a/tool/remotecache-config/cmd/util.go b/tool/remotecache-config/cmd/util.go new file mode 100644 index 000000000..742a57403 --- /dev/null +++ b/tool/remotecache-config/cmd/util.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "fmt" + "strings" + + "github.com/cubefs/cubefs/util" +) + +type table [][]interface{} + +func (t table) append(rows ...[]interface{}) table { + return append(t, rows...) +} + +func rowStr(r []interface{}) []string { + s := make([]string, len(r)) + for idx := range s { + s[idx] = util.Any2String(r[idx]) + } + return s +} + +func align(rows [][]interface{}) (pattern []string, table [][]string) { + table = make([][]string, len(rows)) + lens := make([]int, len(rows[0])) + for idx := range table { + str := rowStr(rows[idx]) + for ii := range str { + if l := len(str[ii]); l > lens[ii] { + lens[ii] = l + } + } + table[idx] = rowStr(rows[idx]) + } + pattern = make([]string, len(rows[0])) + for idx := range lens { + pattern[idx] = fmt.Sprintf("%%-%ds", lens[idx]) + } + return +} + +func str2Any(strs []string) []interface{} { + r := make([]interface{}, len(strs)) + for idx := range strs { + r[idx] = strs[idx] + } + return r +} + +func alignTableSep(sep string, rows ...[]interface{}) string { + pattern, table := align(rows) + pt := strings.Join(pattern, sep) + "\n" + sb := strings.Builder{} + for idx := range table { + sb.WriteString(fmt.Sprintf(pt, str2Any(table[idx])...)) + } + return sb.String() +} + +func alignColumn(rows ...[]interface{}) string { + return alignTableSep(" : ", rows...) +} + +func arow(r ...interface{}) []interface{} { + return r +} + +func alignTable(rows ...[]interface{}) string { + return alignTableSep(" | ", rows...) +} diff --git a/tool/remotecache-config/main.go b/tool/remotecache-config/main.go new file mode 100644 index 000000000..02cc3ada2 --- /dev/null +++ b/tool/remotecache-config/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "os" + + "github.com/cubefs/cubefs/proto" + "github.com/cubefs/cubefs/sdk/master" + "github.com/cubefs/cubefs/tool/remotecache-config/cmd" + "github.com/cubefs/cubefs/util/log" + "github.com/spf13/cobra" +) + +func runCLI() (err error) { + var cfg *cmd.Config + if cfg, err = cmd.LoadConfig(); err != nil { + return fmt.Errorf("load config %v", err) + } + cfsCli := setupCommands(cfg) + return cfsCli.Execute() +} + +func setupCommands(cfg *cmd.Config) *cobra.Command { + mc := master.NewMasterClient(cfg.MasterAddr, false) + mc.SetTimeout(cfg.Timeout) + mc.SetClientIDKey(cfg.ClientIDKey) + cfsRootCmd := cmd.NewRootCmd(mc) + return cfsRootCmd.CFSCmd +} + +func main() { + var err error + _, err = log.InitLog("/tmp/remotecahce-config", "cli", log.DebugLevel, nil, log.DefaultLogLeftSpaceLimitRatio) + if err != nil { + fmt.Fprintln(os.Stderr, "Error:", err) + os.Exit(1) + } + proto.DumpVersion("cfs-cli") + master.CliPrint = true + if err = runCLI(); err != nil { + fmt.Fprintln(os.Stderr, "Error:", err) + log.LogError("Error:", err) + log.LogFlush() + os.Exit(1) + } + log.LogFlush() + +}