From 83c108d48db061604f331bfdcb12bed0d1485d44 Mon Sep 17 00:00:00 2001 From: Qu Xuan Date: Fri, 16 Apr 2021 11:36:52 +0800 Subject: [PATCH] fix: add rbdcli commond for debug --- cmd/rbdcli/main.go | 130 ++++++++++++++++++++++++++++ cmd/rbdcli/shell/cluster.go | 51 +++++++++++ cmd/rbdcli/shell/pool.go | 58 +++++++++++++ cmd/rbdcli/shell/printutils.go | 25 ++++++ pkg/util/rbdutils/doc.go | 1 + pkg/util/rbdutils/rbd.go | 154 +++++++++++++++++++++++++++++++++ 6 files changed, 419 insertions(+) create mode 100644 cmd/rbdcli/main.go create mode 100644 cmd/rbdcli/shell/cluster.go create mode 100644 cmd/rbdcli/shell/pool.go create mode 100644 cmd/rbdcli/shell/printutils.go create mode 100644 pkg/util/rbdutils/doc.go create mode 100644 pkg/util/rbdutils/rbd.go diff --git a/cmd/rbdcli/main.go b/cmd/rbdcli/main.go new file mode 100644 index 0000000000..5325d0a214 --- /dev/null +++ b/cmd/rbdcli/main.go @@ -0,0 +1,130 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "os" + + "yunion.io/x/pkg/errors" + "yunion.io/x/structarg" + + _ "yunion.io/x/onecloud/cmd/rbdcli/shell" + "yunion.io/x/onecloud/pkg/util/rbdutils" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +type BaseOptions struct { + Help bool `help:"Show help"` + Debug bool `help:"debug mode"` + MonHost string `help:"Ceph Mon Host" default:"$MON_HOST"` + Pool string `help:"Ceph pool" default:"$CEPH_POOL" metavar:"CEPH_POOL"` + Key string `help:"Secret" default:"$CEPH_KEY" metavar:"CEPH_KEY"` + SUBCOMMAND string `help:"rbdcli subcommand" subcommand:"true"` +} + +func getSubcommandParser() (*structarg.ArgumentParser, error) { + parse, e := structarg.NewArgumentParser(&BaseOptions{}, + "rbdcli", + "Command-line interface to rbd API.", + `See "rbdcli help COMMAND" for help on a specific command.`) + + if e != nil { + return nil, e + } + + subcmd := parse.GetSubcommand() + if subcmd == nil { + return nil, fmt.Errorf("No subcommand argument.") + } + type HelpOptions struct { + SUBCOMMAND string `help:"sub-command name"` + } + shellutils.R(&HelpOptions{}, "help", "Show help of a subcommand", func(args *HelpOptions) error { + helpstr, e := subcmd.SubHelpString(args.SUBCOMMAND) + if e != nil { + return e + } else { + fmt.Print(helpstr) + return nil + } + }) + for _, v := range shellutils.CommandTable { + _, e := subcmd.AddSubParser(v.Options, v.Command, v.Desc, v.Callback) + if e != nil { + return nil, e + } + } + return parse, nil +} + +func showErrorAndExit(e error) { + fmt.Fprintf(os.Stderr, "%s", e) + fmt.Fprintln(os.Stderr) + os.Exit(1) +} + +func newPool(opts *BaseOptions) (*rbdutils.SPool, error) { + cli, err := rbdutils.NewCluster(opts.MonHost, opts.Key) + if err != nil { + return nil, err + } + + pool, err := cli.GetPool(opts.Pool) + if err != nil { + return nil, errors.Wrapf(err, "GetPool(%s)", opts.Pool) + } + + return pool, nil +} + +func main() { + parser, e := getSubcommandParser() + if e != nil { + showErrorAndExit(e) + } + e = parser.ParseArgs(os.Args[1:], false) + options := parser.Options().(*BaseOptions) + + if options.Help { + fmt.Print(parser.HelpString()) + return + } + subcmd := parser.GetSubcommand() + subparser := subcmd.GetSubParser() + if e != nil { + if subparser != nil { + fmt.Print(subparser.Usage()) + } else { + fmt.Print(parser.Usage()) + } + showErrorAndExit(e) + return + } + suboptions := subparser.Options() + if options.SUBCOMMAND == "help" { + e = subcmd.Invoke(suboptions) + } else { + var pool *rbdutils.SPool + pool, e = newPool(options) + if e != nil { + showErrorAndExit(e) + } + e = subcmd.Invoke(pool, suboptions) + } + if e != nil { + showErrorAndExit(e) + } +} diff --git a/cmd/rbdcli/shell/cluster.go b/cmd/rbdcli/shell/cluster.go new file mode 100644 index 0000000000..466388f3a2 --- /dev/null +++ b/cmd/rbdcli/shell/cluster.go @@ -0,0 +1,51 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/util/rbdutils" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ClusterStatOption struct { + } + + shellutils.R(&ClusterStatOption{}, "cluster-stat", "Show Cluster State", func(cli *rbdutils.SPool, args *ClusterStatOption) error { + cluster := cli.GetCluster() + stat, err := cluster.GetClusterStats() + if err != nil { + return errors.Wrapf(err, "GetClusterStats") + } + printObject(stat) + return nil + }) + + type CmdOptions struct { + CMD string + } + + shellutils.R(&CmdOptions{}, "run", "Run Cluster command", func(cli *rbdutils.SPool, args *CmdOptions) error { + ret, err := cli.GetCluster().MonCommand([]byte(args.CMD)) + if err != nil { + return errors.Wrapf(err, "MonCommand") + } + printObject(ret) + return nil + }) + +} diff --git a/cmd/rbdcli/shell/pool.go b/cmd/rbdcli/shell/pool.go new file mode 100644 index 0000000000..cb4f1e5d68 --- /dev/null +++ b/cmd/rbdcli/shell/pool.go @@ -0,0 +1,58 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/util/rbdutils" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type PoolListOptions struct { + } + shellutils.R(&PoolListOptions{}, "pool-list", "List pools", func(cli *rbdutils.SPool, args *PoolListOptions) error { + cluster := cli.GetCluster() + pools, err := cluster.ListPools() + if err != nil { + return errors.Wrapf(err, "ListPools") + } + printObject(jsonutils.Marshal(map[string][]string{"pools": pools})) + return nil + }) + + type PoolDeleteOptions struct { + POOL string + } + + shellutils.R(&PoolDeleteOptions{}, "pool-delete", "Delete Cluster pool", func(cli *rbdutils.SPool, args *PoolDeleteOptions) error { + return cli.GetCluster().DeletePool(args.POOL) + }) + + type ImageListOptions struct { + } + + shellutils.R(&ImageListOptions{}, "image-list", "List Pool images", func(cli *rbdutils.SPool, args *ImageListOptions) error { + images, err := cli.ListImages() + if err != nil { + return err + } + printObject(jsonutils.Marshal(map[string][]string{"images": images})) + return nil + }) + +} diff --git a/cmd/rbdcli/shell/printutils.go b/cmd/rbdcli/shell/printutils.go new file mode 100644 index 0000000000..1b46ec704a --- /dev/null +++ b/cmd/rbdcli/shell/printutils.go @@ -0,0 +1,25 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import "yunion.io/x/onecloud/pkg/util/printutils" + +func printList(data interface{}, total, offset, limit int, columns []string) { + printutils.PrintInterfaceList(data, total, offset, limit, columns) +} + +func printObject(obj interface{}) { + printutils.PrintInterfaceObject(obj) +} diff --git a/pkg/util/rbdutils/doc.go b/pkg/util/rbdutils/doc.go new file mode 100644 index 0000000000..8a6e226835 --- /dev/null +++ b/pkg/util/rbdutils/doc.go @@ -0,0 +1 @@ +package rbdutils // import "yunion.io/x/onecloud/pkg/util/rbdutils" diff --git a/pkg/util/rbdutils/rbd.go b/pkg/util/rbdutils/rbd.go new file mode 100644 index 0000000000..443eff9ff7 --- /dev/null +++ b/pkg/util/rbdutils/rbd.go @@ -0,0 +1,154 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rbdutils + +import ( + "fmt" + + "github.com/ceph/go-ceph/rados" + "github.com/ceph/go-ceph/rbd" + + "yunion.io/x/pkg/errors" +) + +type SCluster struct { + conn *rados.Conn +} + +func (self *SCluster) withCluster(doFunc func(*rados.Conn) (interface{}, error)) (interface{}, error) { + defer self.conn.Shutdown() + return doFunc(self.conn) +} + +type SPool struct { + name string + cluster *SCluster +} + +func (self *SPool) withIOContext(doFunc func(*rados.IOContext) (interface{}, error)) (interface{}, error) { + return self.cluster.withCluster(func(conn *rados.Conn) (interface{}, error) { + ioctx, err := conn.OpenIOContext(self.name) + if err != nil { + return nil, errors.Wrapf(err, "OpenIOContext(%s)", self.name) + } + return doFunc(ioctx) + }) +} + +func (self *SPool) GetCluster() *SCluster { + return self.cluster +} + +func NewCluster(monHost, key string) (*SCluster, error) { + conn, err := rados.NewConn() + if err != nil { + return nil, err + } + for k, v := range map[string]string{"mon_host": monHost, "key": key} { + if len(v) > 0 { + err = conn.SetConfigOption(k, v) + if err != nil { + return nil, errors.Wrapf(err, "SetConfigOption %s %s", k, v) + } + } + } + + for k, v := range map[string]int64{ + "rados_osd_op_timeout": 20 * 60, + "rados_mon_op_timeout": 5, + "client_mount_timeout": 2 * 60, + } { + err = conn.SetConfigOption(k, fmt.Sprintf("%d", v)) + if err != nil { + return nil, errors.Wrapf(err, "SetConfigOption %s %d", k, v) + } + } + err = conn.Connect() + if err != nil { + return nil, errors.Wrapf(err, "conn.Connect") + } + + return &SCluster{conn: conn}, nil +} + +func (self *SCluster) GetPool(name string) (*SPool, error) { + return &SPool{name: name, cluster: self}, nil +} + +func (self *SCluster) ListPools() ([]string, error) { + pools, err := self.withCluster(func(conn *rados.Conn) (interface{}, error) { + return conn.ListPools() + }) + if err != nil { + return nil, errors.Wrapf(err, "ListPools") + } + return pools.([]string), nil +} + +func (self *SCluster) GetClusterStats() (rados.ClusterStat, error) { + stat, err := self.withCluster(func(conn *rados.Conn) (interface{}, error) { + return conn.GetClusterStats() + }) + if err != nil { + return rados.ClusterStat{}, errors.Wrapf(err, "GetClusterStats") + } + return stat.(rados.ClusterStat), nil +} + +func (self *SCluster) GetFSID() (string, error) { + fsid, err := self.withCluster(func(conn *rados.Conn) (interface{}, error) { + return conn.GetFSID() + }) + if err != nil { + return "", errors.Wrapf(err, "GetFSID") + } + return fsid.(string), nil +} + +func (self *SCluster) DeletePool(pool string) error { + _, err := self.withCluster(func(conn *rados.Conn) (interface{}, error) { + return nil, conn.DeletePool(pool) + }) + return errors.Wrapf(err, "DeletePool") +} + +type cmdOutput struct { + Buffer string + Info string +} + +func (self *SCluster) MonCommand(args []byte) (cmdOutput, error) { + result := cmdOutput{} + _, err := self.withCluster(func(conn *rados.Conn) (interface{}, error) { + buffer, info, err := conn.MonCommand(args) + if err != nil { + return nil, errors.Wrapf(err, "MonCommand") + } + result.Buffer = string(buffer) + result.Info = info + return nil, nil + }) + return result, errors.Wrapf(err, "DeletePool") +} + +func (self *SPool) ListImages() ([]string, error) { + images, err := self.withIOContext(func(ioctx *rados.IOContext) (interface{}, error) { + return rbd.GetImageNames(ioctx) + }) + if err != nil { + return nil, errors.Wrapf(err, "GetImageNames") + } + return images.([]string), nil +}