feat(remotecache): add config tool

close:#1000151163

Signed-off-by: chihe <chihe@oppo.com>
(cherry picked from commit 037348725c)
This commit is contained in:
chihe 2025-07-04 16:45:38 +08:00
parent 54da526123
commit 58771c8dc2
11 changed files with 1611 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"
)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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...)
}

View File

@ -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()
}