From ef42f096671ed83869cf7a42a94d71ee00b39373 Mon Sep 17 00:00:00 2001 From: Max Asnaashari Date: Thu, 12 Sep 2024 12:08:27 -0700 Subject: [PATCH] Fetch current OSD pool configuration over the API (#409) --- .github/workflows/tests.yml | 3 ++ microceph/api/pool.go | 20 +++++++++++- microceph/api/servers.go | 1 + microceph/api/types/pool.go | 9 ++++++ microceph/ceph/osd.go | 40 ++++++++++++++++++++++-- microceph/client/pool.go | 14 +++++++++ microceph/cmd/microceph/pool.go | 54 +++++++++++++++++++++++++++++++++ 7 files changed, 138 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7682bd61..f0ab8a2e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -214,10 +214,13 @@ jobs: sudo microceph.ceph osd pool create mypool sudo microceph pool set-rf --size 1 "" sudo microceph.ceph config get osd.1 osd_pool_default_size | fgrep -x "1" + sudo microceph pool list | fgrep "mypool" | fgrep -q " 3 " sudo microceph pool set-rf --size 3 mypool sudo microceph.ceph osd pool get mypool size | fgrep -x "size: 3" + sudo microceph pool list | fgrep "mypool" | fgrep -q " 3 " sudo microceph pool set-rf --size 1 "*" sudo microceph.ceph osd pool get mypool size | fgrep -x "size: 1" + sudo microceph pool list | fgrep "mypool" | fgrep -q " 1 " - name: Test log operations run: | diff --git a/microceph/api/pool.go b/microceph/api/pool.go index a98cdca2..cc8439c1 100644 --- a/microceph/api/pool.go +++ b/microceph/api/pool.go @@ -15,11 +15,29 @@ import ( ) // /1.0/pools-op endpoint. -var poolsCmd = rest.Endpoint{ +var poolsOpCmd = rest.Endpoint{ Path: "pools-op", Put: rest.EndpointAction{Handler: cmdPoolsPut, ProxyTarget: true}, } +// /1.0/pools endpoint. +var poolsCmd = rest.Endpoint{ + Path: "pools", + Get: rest.EndpointAction{Handler: cmdPoolsGet, ProxyTarget: true}, +} + +func cmdPoolsGet(s state.State, r *http.Request) response.Response { + logger.Debug("cmdPoolGet") + pools, err := ceph.GetOSDPools() + if err != nil { + return response.SmartError(err) + } + + logger.Debug("cmdPoolGet done") + + return response.SyncResponse(true, pools) +} + func cmdPoolsPut(s state.State, r *http.Request) response.Response { var req types.PoolPut diff --git a/microceph/api/servers.go b/microceph/api/servers.go index b943eefc..28c8fa2a 100644 --- a/microceph/api/servers.go +++ b/microceph/api/servers.go @@ -24,6 +24,7 @@ var Servers = map[string]rest.Server{ mdsServiceCmd, mgrServiceCmd, monServiceCmd, + poolsOpCmd, poolsCmd, clientCmd, clientConfigsCmd, diff --git a/microceph/api/types/pool.go b/microceph/api/types/pool.go index 1838ed34..0653b029 100644 --- a/microceph/api/types/pool.go +++ b/microceph/api/types/pool.go @@ -5,3 +5,12 @@ type PoolPut struct { Pools []string `json:"pools" yaml:"pools"` Size int64 `json:"size" yaml:"size"` } + +// Pool represents information about an OSD pool. +type Pool struct { + Pool string `json:"pool" yaml:"pool"` + PoolID int64 `json:"pool_id" yaml:"pool_id"` + Size int64 `json:"size" yaml:"size"` + MinSize int64 `json:"min_size" yaml:"min_size"` + CrushRule string `json:"crush_rule" yaml:"crush_rule"` +} diff --git a/microceph/ceph/osd.go b/microceph/ceph/osd.go index c42a0087..71595be4 100644 --- a/microceph/ceph/osd.go +++ b/microceph/ceph/osd.go @@ -1096,12 +1096,15 @@ func SetReplicationFactor(pools []string, size int64) error { if len(pools) == 1 && pools[0] == "*" { // Apply setting to all existing pools. - out, err := processExec.RunCommand("ceph", "osd", "pool", "ls") + out, err := processExec.RunCommand("ceph", "osd", "pool", "ls", "--format", "json") if err != nil { return fmt.Errorf("failed to list pools: %w", err) } - pools = strings.Split(out, "\n") + err = json.Unmarshal([]byte(out), &pools) + if err != nil { + return fmt.Errorf("Failed to parse OSD pool names: %w", err) + } } for _, pool := range pools { @@ -1109,6 +1112,7 @@ func SetReplicationFactor(pools []string, size int64) error { if pool == "" { continue } + _, err := processExec.RunCommand("ceph", "osd", "pool", "set", pool, "size", ssize, "--yes-i-really-mean-it") if err != nil { return fmt.Errorf("failed to set pool size for %s: %w", pool, err) @@ -1117,3 +1121,35 @@ func SetReplicationFactor(pools []string, size int64) error { return nil } + +// GetOSDPools returns a list of OSD Pools and their configurations. +func GetOSDPools() ([]types.Pool, error) { + out, err := processExec.RunCommand("ceph", "osd", "pool", "ls", "--format", "json") + if err != nil { + return nil, fmt.Errorf("failed to list pools: %w", err) + } + + var poolNames []string + err = json.Unmarshal([]byte(out), &poolNames) + if err != nil { + return nil, fmt.Errorf("Failed to parse OSD pool names: %w", err) + } + + pools := make([]types.Pool, 0, len(poolNames)) + for _, name := range poolNames { + out, err := processExec.RunCommand("ceph", "osd", "pool", "get", name, "all", "--format", "json") + if err != nil { + return nil, fmt.Errorf("Failed to fetch configuration for OSD pool %q: %w", name, err) + } + + var pool types.Pool + err = json.Unmarshal([]byte(out), &pool) + if err != nil { + return nil, fmt.Errorf("Failed to parse %q OSD pool configuration: %w", name, err) + } + + pools = append(pools, pool) + } + + return pools, nil +} diff --git a/microceph/client/pool.go b/microceph/client/pool.go index 7c68f983..341e32c7 100644 --- a/microceph/client/pool.go +++ b/microceph/client/pool.go @@ -23,3 +23,17 @@ func PoolSetReplicationFactor(ctx context.Context, c *microCli.Client, data *typ return nil } + +func GetPools(ctx context.Context, c *microCli.Client) ([]types.Pool, error) { + queryCtx, cancel := context.WithTimeout(ctx, time.Second*120) + defer cancel() + + var pools []types.Pool + err := c.Query(queryCtx, "GET", types.ExtendedPathPrefix, api.NewURL().Path("pools"), nil, &pools) + if err != nil { + return nil, fmt.Errorf("Failed to fetch OSD pools: %w", err) + } + + return pools, nil + +} diff --git a/microceph/cmd/microceph/pool.go b/microceph/cmd/microceph/pool.go index b6a4bc21..b15aa649 100644 --- a/microceph/cmd/microceph/pool.go +++ b/microceph/cmd/microceph/pool.go @@ -2,9 +2,12 @@ package main import ( "context" + "sort" + "strconv" "github.com/spf13/cobra" + lxdCmd "github.com/canonical/lxd/shared/cmd" "github.com/canonical/microceph/microceph/api/types" "github.com/canonical/microceph/microceph/client" "github.com/canonical/microcluster/v2/microcluster" @@ -61,6 +64,53 @@ func (c *cmdPoolSetRF) Run(cmd *cobra.Command, args []string) error { return client.PoolSetReplicationFactor(context.Background(), cli, req) } +type cmdPoolList struct { + common *CmdControl +} + +func (c *cmdPoolList) Command() *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List information about OSD pools", + RunE: c.Run, + } + + return cmd +} + +func (c *cmdPoolList) Run(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return cmd.Help() + } + + m, err := microcluster.App(microcluster.Args{StateDir: c.common.FlagStateDir}) + if err != nil { + return err + } + + cli, err := m.LocalClient() + if err != nil { + return err + } + + pools, err := client.GetPools(cmd.Context(), cli) + if err != nil { + return err + } + + data := make([][]string, len(pools)) + for i, pool := range pools { + data[i] = []string{pool.Pool, strconv.Itoa(int(pool.Size)), pool.CrushRule} + } + + header := []string{"NAME", "SIZE", "CRUSH RULE"} + sort.Sort(lxdCmd.SortColumnsNaturally(data)) + + return lxdCmd.RenderTable(lxdCmd.TableFormatTable, header, data, pools) + +} + func (c *cmdPool) Command() *cobra.Command { cmd := &cobra.Command{ Use: "pool", @@ -71,6 +121,10 @@ func (c *cmdPool) Command() *cobra.Command { poolSetRFCmd := cmdPoolSetRF{common: c.common, poolRF: c} cmd.AddCommand(poolSetRFCmd.Command()) + // list. + poolListCmd := cmdPoolList{common: c.common} + cmd.AddCommand(poolListCmd.Command()) + // Workaround for subcommand usage errors. See: https://github.com/spf13/cobra/issues/706 cmd.Args = cobra.NoArgs cmd.Run = func(cmd *cobra.Command, args []string) { _ = cmd.Usage() }