Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions rook/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ RUN HELM_VERSION=$(grep "^HELM_VERSION" ${ROOK_DIR}/build/makelib/helm.mk | cut
ln -s ${ROOK_DIR}/.cache/tools/linux_amd64/helm-${HELM_VERSION} ${ROOK_DIR}/.cache/tools/linux_amd64/helm

RUN git checkout v${ROOK_VERSION}

# NOTE: This patch should be removed after the following issue is resolved:
# https://tracker.ceph.com/issues/66215
COPY use-jq-for-ceph-osd-dump-json-output.patch .
RUN git apply use-jq-for-ceph-osd-dump-json-output.patch

RUN mkdir -p /tmp/rook
RUN make build IMAGES="ceph" BUILD_CONTAINER_IMAGE=false BUILD_CONTEXT_DIR=/tmp/rook SAVE_BUILD_CONTEXT_DIR=true
# Copy output artifacts to root directory to minimize differences between Stage2 and upstream Dockerfile.
Expand All @@ -38,6 +44,10 @@ COPY --from=build rook toolbox.sh set-ceph-debug-level /usr/local/bin/
COPY --from=build ceph-monitoring /etc/ceph-monitoring
COPY --from=build rook-external /etc/rook-external/

# NOTE: This check should be removed after the following issue is resolved:
# https://tracker.ceph.com/issues/66215
RUN jq --version

# create or modify owner and permissions to make a watch-active container of a MGR work properly
RUN groupadd rook -g 2016 && \
useradd rook -u 2016 -g rook
Expand Down
2 changes: 1 addition & 1 deletion rook/TAG
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.18.6.1
1.18.6.2
118 changes: 118 additions & 0 deletions rook/use-jq-for-ceph-osd-dump-json-output.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
diff --git a/pkg/daemon/ceph/client/osd.go b/pkg/daemon/ceph/client/osd.go
index 49d082e90881..95dbbbef3d66 100644
--- a/pkg/daemon/ceph/client/osd.go
+++ b/pkg/daemon/ceph/client/osd.go
@@ -16,9 +16,11 @@ limitations under the License.
package client

import (
+ "bytes"
"encoding/json"
"fmt"
"math"
+ "os/exec"
"strconv"
"strings"

@@ -303,8 +305,16 @@ func GetOSDDump(context *clusterd.Context, clusterInfo *ClusterInfo) (*OSDDump,
return nil, errors.Wrap(err, "failed to get osd dump")
}

+ // Use jq to fix potentially invalid JSON from ceph osd dump
+ jqCmd := exec.Command("jq", ".")
+ jqCmd.Stdin = bytes.NewReader(buf)
+ validJSON, err := jqCmd.Output()
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to run jq on osd dump output")
+ }
+
var osdDump OSDDump
- if err := json.Unmarshal(buf, &osdDump); err != nil {
+ if err := json.Unmarshal(validJSON, &osdDump); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal osd dump response")
}

diff --git a/pkg/daemon/ceph/client/osd_test.go b/pkg/daemon/ceph/client/osd_test.go
index 282cbf3f3703..2d4550607f6a 100644
--- a/pkg/daemon/ceph/client/osd_test.go
+++ b/pkg/daemon/ceph/client/osd_test.go
@@ -214,3 +214,79 @@ func TestOSDOkToStop(t *testing.T) {
assert.Equal(t, "--max=0", seenArgs[3])
})
}
+
+func TestGetOSDDump(t *testing.T) {
+ // Valid JSON output from ceph osd dump
+ validOSDDump := `{
+ "osds": [
+ {"osd": 0, "up": 1, "in": 1},
+ {"osd": 1, "up": 1, "in": 1}
+ ],
+ "flags": "nodown,sortbitwise",
+ "crush_node_flags": {},
+ "full_ratio": 0.95,
+ "backfillfull_ratio": 0.9,
+ "nearfull_ratio": 0.85
+ }`
+
+ // Invalid JSON with "inf" value that ceph osd dump can produce
+ // This causes "invalid character 'i' looking for beginning of value" error
+ invalidOSDDumpWithInf := `{
+ "osds": [
+ {"osd": 0, "up": 1, "in": 1}
+ ],
+ "flags": "nodown",
+ "crush_node_flags": {},
+ "full_ratio": 0.95,
+ "backfillfull_ratio": 0.9,
+ "nearfull_ratio": inf
+ }`
+
+ t.Run("valid JSON is parsed correctly", func(t *testing.T) {
+ executor := &exectest.MockExecutor{}
+ executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
+ logger.Infof("Command: %s %v", command, args)
+ if args[0] == "osd" && args[1] == "dump" {
+ return validOSDDump, nil
+ }
+ return "", errors.Errorf("unexpected ceph command %q", args)
+ }
+
+ context := &clusterd.Context{Executor: executor}
+ clusterInfo := AdminTestClusterInfo("mycluster")
+
+ dump, err := GetOSDDump(context, clusterInfo)
+ assert.NoError(t, err)
+ assert.NotNil(t, dump)
+ assert.Equal(t, 2, len(dump.OSDs))
+ assert.Equal(t, "nodown,sortbitwise", dump.Flags)
+ assert.Equal(t, 0.95, dump.FullRatio)
+ assert.Equal(t, 0.9, dump.BackfillFullRatio)
+ assert.Equal(t, 0.85, dump.NearFullRatio)
+ })
+
+ t.Run("invalid JSON with inf is fixed by jq and parsed correctly", func(t *testing.T) {
+ executor := &exectest.MockExecutor{}
+ executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
+ logger.Infof("Command: %s %v", command, args)
+ if args[0] == "osd" && args[1] == "dump" {
+ return invalidOSDDumpWithInf, nil
+ }
+ return "", errors.Errorf("unexpected ceph command %q", args)
+ }
+
+ context := &clusterd.Context{Executor: executor}
+ clusterInfo := AdminTestClusterInfo("mycluster")
+
+ dump, err := GetOSDDump(context, clusterInfo)
+ assert.NoError(t, err)
+ assert.NotNil(t, dump)
+ assert.Equal(t, 1, len(dump.OSDs))
+ assert.Equal(t, "nodown", dump.Flags)
+ assert.Equal(t, 0.95, dump.FullRatio)
+ assert.Equal(t, 0.9, dump.BackfillFullRatio)
+ // jq converts "inf" to 1.7976931348623157e+308 (max float64)
+ // The important thing is that parsing succeeds without error
+ assert.NotEqual(t, 0.0, dump.NearFullRatio)
+ })
+}
Loading