pool_test.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. /*
  2. Copyright 2016 The Rook Authors. All rights reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package client
  14. import (
  15. "fmt"
  16. "os/exec"
  17. "reflect"
  18. "strconv"
  19. "testing"
  20. "golang.org/x/exp/slices"
  21. "github.com/pkg/errors"
  22. cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
  23. "github.com/rook/rook/pkg/clusterd"
  24. exectest "github.com/rook/rook/pkg/util/exec/test"
  25. "github.com/stretchr/testify/assert"
  26. )
  27. const emptyApplicationName = `{"":{}}`
  28. func TestCreateECPoolWithOverwrites(t *testing.T) {
  29. testCreateECPool(t, true, "")
  30. }
  31. func TestCreateECPoolWithoutOverwrites(t *testing.T) {
  32. testCreateECPool(t, false, "")
  33. }
  34. func TestCreateECPoolWithCompression(t *testing.T) {
  35. testCreateECPool(t, false, "aggressive")
  36. testCreateECPool(t, true, "none")
  37. }
  38. func testCreateECPool(t *testing.T, overwrite bool, compressionMode string) {
  39. compressionModeCreated := false
  40. p := cephv1.NamedPoolSpec{
  41. Name: "mypool",
  42. PoolSpec: cephv1.PoolSpec{
  43. FailureDomain: "host",
  44. ErasureCoded: cephv1.ErasureCodedSpec{},
  45. },
  46. }
  47. if compressionMode != "" {
  48. p.CompressionMode = compressionMode
  49. }
  50. executor := &exectest.MockExecutor{}
  51. context := &clusterd.Context{Executor: executor}
  52. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  53. logger.Infof("Command: %s %v", command, args)
  54. if args[1] == "pool" {
  55. if args[2] == "create" {
  56. assert.Equal(t, "mypool", args[3])
  57. assert.Equal(t, "erasure", args[5])
  58. assert.Equal(t, "mypoolprofile", args[6])
  59. return "", nil
  60. }
  61. if args[2] == "set" {
  62. assert.Equal(t, "mypool", args[3])
  63. if args[4] == "allow_ec_overwrites" {
  64. assert.Equal(t, true, overwrite)
  65. assert.Equal(t, "true", args[5])
  66. return "", nil
  67. }
  68. if args[4] == "compression_mode" {
  69. assert.Equal(t, compressionMode, args[5])
  70. compressionModeCreated = true
  71. return "", nil
  72. }
  73. }
  74. if args[2] == "application" {
  75. if args[3] == "get" {
  76. return emptyApplicationName, nil
  77. }
  78. assert.Equal(t, "enable", args[3])
  79. assert.Equal(t, "mypool", args[4])
  80. assert.Equal(t, "myapp", args[5])
  81. return "", nil
  82. }
  83. }
  84. return "", errors.Errorf("unexpected ceph command %q", args)
  85. }
  86. err := createECPoolForApp(context, AdminTestClusterInfo("mycluster"), "mypoolprofile", p, DefaultPGCount, "myapp", overwrite)
  87. assert.Nil(t, err)
  88. if compressionMode != "" {
  89. assert.True(t, compressionModeCreated)
  90. } else {
  91. assert.False(t, compressionModeCreated)
  92. }
  93. }
  94. func TestSetPoolApplication(t *testing.T) {
  95. poolName := "testpool"
  96. appName := "testapp"
  97. setAppName := false
  98. blankAppName := false
  99. clusterInfo := AdminTestClusterInfo("mycluster")
  100. executor := &exectest.MockExecutor{}
  101. context := &clusterd.Context{Executor: executor}
  102. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  103. logger.Infof("Command: %s %v", command, args)
  104. if args[1] == "pool" && args[2] == "application" {
  105. if args[3] == "get" {
  106. assert.Equal(t, poolName, args[4])
  107. if blankAppName {
  108. return emptyApplicationName, nil
  109. } else {
  110. return fmt.Sprintf(`{"%s":{}}`, appName), nil
  111. }
  112. }
  113. if args[3] == "enable" {
  114. setAppName = true
  115. assert.Equal(t, poolName, args[4])
  116. assert.Equal(t, appName, args[5])
  117. return "", nil
  118. }
  119. }
  120. return "", errors.Errorf("unexpected ceph command %q", args)
  121. }
  122. t.Run("set pool application", func(t *testing.T) {
  123. setAppName = false
  124. blankAppName = true
  125. err := givePoolAppTag(context, clusterInfo, poolName, appName)
  126. assert.NoError(t, err)
  127. assert.True(t, setAppName)
  128. })
  129. t.Run("pool application already set", func(t *testing.T) {
  130. setAppName = false
  131. blankAppName = false
  132. err := givePoolAppTag(context, clusterInfo, poolName, appName)
  133. assert.NoError(t, err)
  134. assert.False(t, setAppName)
  135. })
  136. }
  137. func TestCreateReplicaPoolWithFailureDomain(t *testing.T) {
  138. testCreateReplicaPool(t, "osd", "mycrushroot", "", "")
  139. }
  140. func TestCreateReplicaPoolWithDeviceClass(t *testing.T) {
  141. testCreateReplicaPool(t, "osd", "mycrushroot", "hdd", "")
  142. }
  143. func TestCreateReplicaPoolWithCompression(t *testing.T) {
  144. testCreateReplicaPool(t, "osd", "mycrushroot", "hdd", "passive")
  145. testCreateReplicaPool(t, "osd", "mycrushroot", "hdd", "force")
  146. }
  147. func testCreateReplicaPool(t *testing.T, failureDomain, crushRoot, deviceClass, compressionMode string) {
  148. crushRuleCreated := false
  149. compressionModeCreated := false
  150. executor := &exectest.MockExecutor{}
  151. context := &clusterd.Context{Executor: executor}
  152. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  153. logger.Infof("Command: %s %v", command, args)
  154. if args[1] == "pool" {
  155. if args[2] == "create" {
  156. assert.Equal(t, "mypool", args[3])
  157. assert.Equal(t, "replicated", args[5])
  158. assert.Equal(t, "--size", args[7])
  159. assert.Equal(t, "12345", args[8])
  160. return "", nil
  161. }
  162. if args[2] == "set" {
  163. assert.Equal(t, "mypool", args[3])
  164. if args[4] == "size" {
  165. assert.Equal(t, "12345", args[5])
  166. }
  167. if args[4] == "compression_mode" {
  168. assert.Equal(t, compressionMode, args[5])
  169. compressionModeCreated = true
  170. }
  171. return "", nil
  172. }
  173. if args[2] == "application" {
  174. if args[3] == "get" {
  175. return emptyApplicationName, nil
  176. }
  177. assert.Equal(t, "enable", args[3])
  178. assert.Equal(t, "mypool", args[4])
  179. assert.Equal(t, "myapp", args[5])
  180. return "", nil
  181. }
  182. }
  183. if args[1] == "crush" {
  184. crushRuleCreated = true
  185. assert.Equal(t, "rule", args[2])
  186. assert.Equal(t, "create-replicated", args[3])
  187. assert.Equal(t, "mypool", args[4])
  188. if crushRoot == "" {
  189. assert.Equal(t, "cluster-crush-root", args[5])
  190. } else {
  191. assert.Equal(t, crushRoot, args[5])
  192. }
  193. if failureDomain == "" {
  194. assert.Equal(t, "host", args[6])
  195. } else {
  196. assert.Equal(t, failureDomain, args[6])
  197. }
  198. if deviceClass == "" {
  199. assert.False(t, slices.Contains(args, "hdd"))
  200. } else {
  201. assert.Equal(t, deviceClass, args[7])
  202. }
  203. return "", nil
  204. }
  205. return "", errors.Errorf("unexpected ceph command %q", args)
  206. }
  207. p := cephv1.NamedPoolSpec{
  208. Name: "mypool",
  209. PoolSpec: cephv1.PoolSpec{
  210. FailureDomain: failureDomain, CrushRoot: crushRoot, DeviceClass: deviceClass,
  211. Replicated: cephv1.ReplicatedSpec{Size: 12345},
  212. },
  213. }
  214. if compressionMode != "" {
  215. p.CompressionMode = compressionMode
  216. }
  217. clusterSpec := &cephv1.ClusterSpec{Storage: cephv1.StorageScopeSpec{Config: map[string]string{CrushRootConfigKey: "cluster-crush-root"}}}
  218. err := createReplicatedPoolForApp(context, AdminTestClusterInfo("mycluster"), clusterSpec, p, DefaultPGCount, "myapp")
  219. assert.Nil(t, err)
  220. assert.True(t, crushRuleCreated)
  221. if compressionMode != "" {
  222. assert.True(t, compressionModeCreated)
  223. } else {
  224. assert.False(t, compressionModeCreated)
  225. }
  226. }
  227. func TestUpdateFailureDomain(t *testing.T) {
  228. var newCrushRule string
  229. currentFailureDomain := "rack"
  230. executor := &exectest.MockExecutor{}
  231. context := &clusterd.Context{Executor: executor}
  232. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  233. logger.Infof("Command: %s %v", command, args)
  234. if args[1] == "pool" {
  235. if args[2] == "get" {
  236. assert.Equal(t, "mypool", args[3])
  237. return `{"crush_rule": "test_rule"}`, nil
  238. }
  239. if args[2] == "set" {
  240. assert.Equal(t, "mypool", args[3])
  241. assert.Equal(t, "crush_rule", args[4])
  242. newCrushRule = args[5]
  243. return "", nil
  244. }
  245. }
  246. if args[1] == "crush" {
  247. if args[2] == "rule" && args[3] == "dump" {
  248. return fmt.Sprintf(`{"steps": [{"foo":"bar"},{"type":"%s"}]}`, currentFailureDomain), nil
  249. }
  250. newCrushRule = "foo"
  251. return "", nil
  252. }
  253. return "", errors.Errorf("unexpected ceph command %q", args)
  254. }
  255. t.Run("no desired failure domain", func(t *testing.T) {
  256. p := cephv1.NamedPoolSpec{
  257. Name: "mypool",
  258. PoolSpec: cephv1.PoolSpec{
  259. Replicated: cephv1.ReplicatedSpec{Size: 3},
  260. },
  261. }
  262. clusterSpec := &cephv1.ClusterSpec{Storage: cephv1.StorageScopeSpec{}}
  263. err := updatePoolCrushRule(context, AdminTestClusterInfo("mycluster"), clusterSpec, p)
  264. assert.NoError(t, err)
  265. assert.Equal(t, "", newCrushRule)
  266. })
  267. t.Run("same failure domain", func(t *testing.T) {
  268. p := cephv1.NamedPoolSpec{
  269. Name: "mypool",
  270. PoolSpec: cephv1.PoolSpec{
  271. FailureDomain: currentFailureDomain,
  272. Replicated: cephv1.ReplicatedSpec{Size: 3},
  273. },
  274. }
  275. clusterSpec := &cephv1.ClusterSpec{Storage: cephv1.StorageScopeSpec{}}
  276. err := updatePoolCrushRule(context, AdminTestClusterInfo("mycluster"), clusterSpec, p)
  277. assert.NoError(t, err)
  278. assert.Equal(t, "", newCrushRule)
  279. })
  280. t.Run("changing failure domain", func(t *testing.T) {
  281. p := cephv1.NamedPoolSpec{
  282. Name: "mypool",
  283. PoolSpec: cephv1.PoolSpec{
  284. FailureDomain: "zone",
  285. Replicated: cephv1.ReplicatedSpec{Size: 3},
  286. },
  287. }
  288. clusterSpec := &cephv1.ClusterSpec{Storage: cephv1.StorageScopeSpec{}}
  289. err := updatePoolCrushRule(context, AdminTestClusterInfo("mycluster"), clusterSpec, p)
  290. assert.NoError(t, err)
  291. assert.Equal(t, "mypool_zone", newCrushRule)
  292. })
  293. }
  294. func TestExtractPoolDetails(t *testing.T) {
  295. t.Run("complex crush rule skipped", func(t *testing.T) {
  296. rule := ruleSpec{Steps: []stepSpec{
  297. {Type: ""},
  298. {Type: ""},
  299. {Type: "zone"},
  300. }}
  301. failureDomain, _ := extractPoolDetails(rule)
  302. assert.Equal(t, "", failureDomain)
  303. })
  304. t.Run("valid crush rule", func(t *testing.T) {
  305. rule := ruleSpec{Steps: []stepSpec{
  306. {Type: ""},
  307. {Type: "zone", ItemName: "ssd"},
  308. }}
  309. failureDomain, deviceClass := extractPoolDetails(rule)
  310. assert.Equal(t, "zone", failureDomain)
  311. assert.Equal(t, "ssd", deviceClass)
  312. })
  313. }
  314. func TestGetPoolStatistics(t *testing.T) {
  315. p := PoolStatistics{}
  316. p.Images.Count = 1
  317. p.Images.ProvisionedBytes = 1024
  318. p.Images.SnapCount = 1
  319. p.Trash.Count = 1
  320. p.Trash.ProvisionedBytes = 2048
  321. p.Trash.SnapCount = 0
  322. executor := &exectest.MockExecutor{}
  323. context := &clusterd.Context{Executor: executor}
  324. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  325. a := "{\"images\":{\"count\":1,\"provisioned_bytes\":1024,\"snap_count\":1},\"trash\":{\"count\":1,\"provisioned_bytes\":2048,\"snap_count\":0}}"
  326. logger.Infof("Command: %s %v", command, args)
  327. if args[0] == "pool" {
  328. if args[1] == "stats" {
  329. if args[2] == "replicapool" {
  330. return a, nil
  331. }
  332. return "", errors.Errorf("rbd:error opening pool '%s': (2) No such file or directory", args[3])
  333. }
  334. }
  335. return "", errors.Errorf("unexpected rbd command %q", args)
  336. }
  337. clusterInfo := AdminTestClusterInfo("mycluster")
  338. stats, err := GetPoolStatistics(context, clusterInfo, "replicapool")
  339. assert.Nil(t, err)
  340. assert.True(t, reflect.DeepEqual(stats, &p))
  341. stats, err = GetPoolStatistics(context, clusterInfo, "rbd")
  342. assert.NotNil(t, err)
  343. assert.Nil(t, stats)
  344. }
  345. func TestSetPoolReplicatedSizeProperty(t *testing.T) {
  346. poolName := "mypool"
  347. executor := &exectest.MockExecutor{}
  348. context := &clusterd.Context{Executor: executor}
  349. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  350. logger.Infof("Command: %s %v", command, args)
  351. if args[2] == "set" {
  352. assert.Equal(t, poolName, args[3])
  353. assert.Equal(t, "size", args[4])
  354. assert.Equal(t, "3", args[5])
  355. return "", nil
  356. }
  357. return "", errors.Errorf("unexpected ceph command %q", args)
  358. }
  359. err := SetPoolReplicatedSizeProperty(context, AdminTestClusterInfo("mycluster"), poolName, "3")
  360. assert.NoError(t, err)
  361. // TEST POOL SIZE 1 AND RequireSafeReplicaSize True
  362. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  363. logger.Infof("Command: %s %v", command, args)
  364. if args[2] == "set" {
  365. assert.Equal(t, "mypool", args[3])
  366. assert.Equal(t, "size", args[4])
  367. assert.Equal(t, "1", args[5])
  368. assert.Equal(t, "--yes-i-really-mean-it", args[6])
  369. return "", nil
  370. }
  371. return "", errors.Errorf("unexpected ceph command %q", args)
  372. }
  373. err = SetPoolReplicatedSizeProperty(context, AdminTestClusterInfo("mycluster"), poolName, "1")
  374. assert.NoError(t, err)
  375. }
  376. func TestCreateStretchCrushRule(t *testing.T) {
  377. testCreateStretchCrushRule(t, true)
  378. testCreateStretchCrushRule(t, false)
  379. }
  380. func testCreateStretchCrushRule(t *testing.T, alreadyExists bool) {
  381. executor := &exectest.MockExecutor{}
  382. context := &clusterd.Context{Executor: executor}
  383. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  384. logger.Infof("Command: %s %v", command, args)
  385. if args[0] == "osd" {
  386. if args[1] == "getcrushmap" {
  387. return "", nil
  388. }
  389. if args[1] == "setcrushmap" {
  390. if alreadyExists {
  391. return "", errors.New("setcrushmap not expected for already existing crush rule")
  392. }
  393. return "", nil
  394. }
  395. }
  396. if command == "crushtool" {
  397. switch {
  398. case args[0] == "--decompile" || args[0] == "--compile":
  399. if alreadyExists {
  400. return "", errors.New("--compile or --decompile not expected for already existing crush rule")
  401. }
  402. return "", nil
  403. }
  404. }
  405. if args[0] == "osd" && args[1] == "crush" && args[2] == "dump" {
  406. return testCrushMap, nil
  407. }
  408. return "", errors.Errorf("unexpected ceph command %q", args)
  409. }
  410. clusterInfo := AdminTestClusterInfo("mycluster")
  411. clusterSpec := &cephv1.ClusterSpec{}
  412. poolSpec := cephv1.PoolSpec{FailureDomain: "rack"}
  413. ruleName := "testrule"
  414. if alreadyExists {
  415. ruleName = "replicated_ruleset"
  416. }
  417. err := createStretchCrushRule(context, clusterInfo, clusterSpec, ruleName, poolSpec)
  418. assert.NoError(t, err)
  419. }
  420. func TestCreatePoolWithReplicasPerFailureDomain(t *testing.T) {
  421. // This test goes via the path of explicit compile/decompile CRUSH map; ignored if 'crushtool' is not installed
  422. // on local build machine
  423. if hasCrushtool() {
  424. testCreatePoolWithReplicasPerFailureDomain(t, "host", "mycrushroot", "hdd")
  425. testCreatePoolWithReplicasPerFailureDomain(t, "rack", "mycrushroot", "ssd")
  426. }
  427. }
  428. func testCreatePoolWithReplicasPerFailureDomain(t *testing.T, failureDomain, crushRoot, deviceClass string) {
  429. poolRuleCreated := false
  430. poolRuleSet := false
  431. poolAppEnable := false
  432. poolSpec := cephv1.NamedPoolSpec{
  433. Name: "mypool-with-two-step-clush-rule",
  434. PoolSpec: cephv1.PoolSpec{
  435. FailureDomain: failureDomain,
  436. CrushRoot: crushRoot,
  437. DeviceClass: deviceClass,
  438. Replicated: cephv1.ReplicatedSpec{
  439. Size: 12345678,
  440. ReplicasPerFailureDomain: 2,
  441. },
  442. },
  443. }
  444. executor := &exectest.MockExecutor{}
  445. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  446. logger.Infof("Command: %s %v", command, args)
  447. assert.Equal(t, command, "ceph")
  448. assert.Equal(t, args[0], "osd")
  449. if len(args) >= 3 && args[1] == "crush" && args[2] == "dump" {
  450. return testCrushMap, nil
  451. }
  452. if len(args) >= 3 && args[1] == "pool" && args[2] == "create" {
  453. // Currently, CRUSH-rule name equals pool's name
  454. assert.GreaterOrEqual(t, len(args), 7)
  455. assert.Equal(t, args[3], poolSpec.Name)
  456. assert.Equal(t, args[5], "replicated")
  457. crushRuleName := args[6]
  458. assert.Equal(t, crushRuleName, poolSpec.Name)
  459. poolRuleCreated = true
  460. return "", nil
  461. }
  462. if len(args) >= 3 && args[1] == "pool" && args[2] == "set" {
  463. crushRuleName := args[3]
  464. assert.Equal(t, crushRuleName, poolSpec.Name)
  465. assert.Equal(t, args[4], "size")
  466. poolSize, err := strconv.Atoi(args[5])
  467. assert.NoError(t, err)
  468. assert.Equal(t, uint(poolSize), poolSpec.Replicated.Size)
  469. poolRuleSet = true
  470. return "", nil
  471. }
  472. if len(args) >= 4 && args[1] == "pool" && args[2] == "application" {
  473. if args[3] == "get" {
  474. return emptyApplicationName, nil
  475. }
  476. crushRuleName := args[4]
  477. assert.Equal(t, crushRuleName, poolSpec.Name)
  478. poolAppEnable = true
  479. return "", nil
  480. }
  481. if len(args) >= 4 && args[1] == "crush" && args[2] == "rule" && args[3] == "create-replicated" {
  482. crushRuleName := args[4]
  483. assert.Equal(t, crushRuleName, poolSpec.Name)
  484. deviceClassName := args[7]
  485. assert.Equal(t, deviceClassName, deviceClass)
  486. poolRuleCreated = true
  487. return "", nil
  488. }
  489. return "", errors.Errorf("unexpected ceph command %q", args)
  490. }
  491. context := &clusterd.Context{Executor: executor}
  492. clusterSpec := &cephv1.ClusterSpec{Storage: cephv1.StorageScopeSpec{Config: map[string]string{CrushRootConfigKey: "cluster-crush-root"}}}
  493. err := createReplicatedPoolForApp(context, AdminTestClusterInfo("mycluster"), clusterSpec, poolSpec, DefaultPGCount, "myapp")
  494. assert.Nil(t, err)
  495. assert.True(t, poolRuleCreated)
  496. assert.True(t, poolRuleSet)
  497. assert.True(t, poolAppEnable)
  498. }
  499. func TestCreateHybridCrushRule(t *testing.T) {
  500. testCreateHybridCrushRule(t, true)
  501. testCreateHybridCrushRule(t, false)
  502. }
  503. func testCreateHybridCrushRule(t *testing.T, alreadyExists bool) {
  504. executor := &exectest.MockExecutor{}
  505. context := &clusterd.Context{Executor: executor}
  506. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  507. logger.Infof("Command: %s %v", command, args)
  508. if args[0] == "osd" {
  509. if args[1] == "getcrushmap" {
  510. return "", nil
  511. }
  512. if args[1] == "setcrushmap" {
  513. if alreadyExists {
  514. return "", errors.New("setcrushmap not expected for already existing crush rule")
  515. }
  516. return "", nil
  517. }
  518. }
  519. if command == "crushtool" {
  520. switch {
  521. case args[0] == "--decompile" || args[0] == "--compile":
  522. if alreadyExists {
  523. return "", errors.New("--compile or --decompile not expected for already existing crush rule")
  524. }
  525. return "", nil
  526. }
  527. }
  528. if args[0] == "osd" && args[1] == "crush" && args[2] == "dump" {
  529. return testCrushMap, nil
  530. }
  531. return "", errors.Errorf("unexpected ceph command %q", args)
  532. }
  533. clusterInfo := AdminTestClusterInfo("mycluster")
  534. clusterSpec := &cephv1.ClusterSpec{}
  535. poolSpec := cephv1.PoolSpec{
  536. FailureDomain: "rack",
  537. Replicated: cephv1.ReplicatedSpec{
  538. HybridStorage: &cephv1.HybridStorageSpec{
  539. PrimaryDeviceClass: "ssd",
  540. SecondaryDeviceClass: "hdd",
  541. },
  542. },
  543. }
  544. ruleName := "testrule"
  545. if alreadyExists {
  546. ruleName = "hybrid_ruleset"
  547. }
  548. err := createHybridCrushRule(context, clusterInfo, clusterSpec, ruleName, poolSpec)
  549. assert.NoError(t, err)
  550. }
  551. func hasCrushtool() bool {
  552. _, err := exec.LookPath("crushtool")
  553. return err == nil
  554. }