upgrade_test.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. /*
  2. Copyright 2019 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. "encoding/json"
  16. "testing"
  17. "time"
  18. "github.com/pkg/errors"
  19. cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
  20. "github.com/rook/rook/pkg/clusterd"
  21. "github.com/rook/rook/pkg/daemon/ceph/client/fake"
  22. exectest "github.com/rook/rook/pkg/util/exec/test"
  23. "github.com/stretchr/testify/assert"
  24. )
  25. func TestGetCephMonVersionString(t *testing.T) {
  26. executor := &exectest.MockExecutor{}
  27. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  28. assert.Equal(t, "version", args[0])
  29. return "", nil
  30. }
  31. context := &clusterd.Context{Executor: executor}
  32. _, err := getCephMonVersionString(context, AdminTestClusterInfo("mycluster"))
  33. assert.NoError(t, err)
  34. }
  35. func TestGetCephMonVersionsString(t *testing.T) {
  36. executor := &exectest.MockExecutor{}
  37. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  38. assert.Equal(t, "versions", args[0])
  39. return "", nil
  40. }
  41. context := &clusterd.Context{Executor: executor}
  42. _, err := getAllCephDaemonVersionsString(context, AdminTestClusterInfo("mycluster"))
  43. assert.Nil(t, err)
  44. }
  45. func TestEnableReleaseOSDFunctionality(t *testing.T) {
  46. executor := &exectest.MockExecutor{}
  47. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  48. assert.Equal(t, "osd", args[0])
  49. assert.Equal(t, "require-osd-release", args[1])
  50. return "", nil
  51. }
  52. context := &clusterd.Context{Executor: executor}
  53. err := EnableReleaseOSDFunctionality(context, AdminTestClusterInfo("mycluster"), "quincy")
  54. assert.NoError(t, err)
  55. }
  56. func TestOkToStopDaemon(t *testing.T) {
  57. // First test
  58. executor := &exectest.MockExecutor{}
  59. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  60. switch {
  61. case args[0] == "mon" && args[1] == "ok-to-stop" && args[2] == "a":
  62. return "", nil
  63. }
  64. return "", errors.Errorf("unexpected ceph command %q", args)
  65. }
  66. context := &clusterd.Context{Executor: executor}
  67. deployment := "rook-ceph-mon-a"
  68. err := okToStopDaemon(context, AdminTestClusterInfo("mycluster"), deployment, "mon", "a")
  69. assert.NoError(t, err)
  70. // Second test
  71. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  72. assert.Equal(t, "mgr", args[0])
  73. assert.Equal(t, "ok-to-stop", args[1])
  74. assert.Equal(t, "a", args[2])
  75. return "", nil
  76. }
  77. context = &clusterd.Context{Executor: executor}
  78. deployment = "rook-ceph-mgr-a"
  79. err = okToStopDaemon(context, AdminTestClusterInfo("mycluster"), deployment, "mgr", "a")
  80. assert.NoError(t, err)
  81. // Third test
  82. executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) {
  83. assert.Equal(t, "dummy", args[0])
  84. assert.Equal(t, "ok-to-stop", args[1])
  85. assert.Equal(t, "a", args[2])
  86. return "", nil
  87. }
  88. context = &clusterd.Context{Executor: executor}
  89. deployment = "rook-ceph-dummy-a"
  90. err = okToStopDaemon(context, AdminTestClusterInfo("mycluster"), deployment, "dummy", "a")
  91. assert.NoError(t, err)
  92. }
  93. func TestOkToContinue(t *testing.T) {
  94. executor := &exectest.MockExecutor{}
  95. context := &clusterd.Context{Executor: executor}
  96. err := OkToContinue(context, AdminTestClusterInfo("mycluster"), "rook-ceph-mon-a", "mon", "a") // mon is not checked on ok-to-continue so nil is expected
  97. assert.NoError(t, err)
  98. }
  99. func TestFindFSName(t *testing.T) {
  100. fsName := findFSName("rook-ceph-mds-myfs-a")
  101. assert.Equal(t, "myfs-a", fsName)
  102. fsName = findFSName("rook-ceph-mds-my-super-fs-a")
  103. assert.Equal(t, "my-super-fs-a", fsName)
  104. }
  105. func TestDaemonMapEntry(t *testing.T) {
  106. dummyVersionsRaw := []byte(`
  107. {
  108. "mon": {
  109. "ceph version 18.2.5 (cbff874f9007f1869bfd3821b7e33b2a6ffd4988) reef (stable)": 1,
  110. "ceph version 17.2.7 (3a54b2b6d167d4a2a19e003a705696d4fe619afc) quincy (stable)": 2
  111. }
  112. }`)
  113. var dummyVersions cephv1.CephDaemonsVersions
  114. err := json.Unmarshal([]byte(dummyVersionsRaw), &dummyVersions)
  115. assert.NoError(t, err)
  116. m, err := daemonMapEntry(&dummyVersions, "mon")
  117. assert.NoError(t, err)
  118. assert.Equal(t, dummyVersions.Mon, m)
  119. _, err = daemonMapEntry(&dummyVersions, "dummy")
  120. assert.Error(t, err)
  121. }
  122. func TestBuildHostListFromTree(t *testing.T) {
  123. dummyOsdTreeRaw := []byte(`
  124. {
  125. "nodes": [
  126. {
  127. "id": -3,
  128. "name": "r1",
  129. "type": "rack",
  130. "type_id": 3,
  131. "children": [
  132. -4
  133. ]
  134. },
  135. {
  136. "id": -4,
  137. "name": "ceph-nano-oooooo",
  138. "type": "host",
  139. "type_id": 1,
  140. "pool_weights": {},
  141. "children": [
  142. 0
  143. ]
  144. },
  145. {
  146. "id": 0,
  147. "name": "osd.0",
  148. "type": "osd",
  149. "type_id": 0,
  150. "crush_weight": 0.009796,
  151. "depth": 2,
  152. "pool_weights": {},
  153. "exists": 1,
  154. "status": "up",
  155. "reweight": 1,
  156. "primary_affinity": 1
  157. },
  158. {
  159. "id": -1,
  160. "name": "default",
  161. "type": "root",
  162. "type_id": 10,
  163. "children": [
  164. -2
  165. ]
  166. },
  167. {
  168. "id": -2,
  169. "name": "ceph-nano-nau-faa32aebf00b",
  170. "type": "host",
  171. "type_id": 1,
  172. "pool_weights": {},
  173. "children": []
  174. }
  175. ],
  176. "stray": [
  177. {
  178. "id": 1,
  179. "name": "osd.1",
  180. "type": "osd",
  181. "type_id": 0,
  182. "crush_weight": 0,
  183. "depth": 0,
  184. "exists": 1,
  185. "status": "down",
  186. "reweight": 0,
  187. "primary_affinity": 1
  188. }
  189. ]
  190. }`)
  191. var dummyTree OsdTree
  192. err := json.Unmarshal([]byte(dummyOsdTreeRaw), &dummyTree)
  193. assert.NoError(t, err)
  194. osdHosts, err := buildHostListFromTree(dummyTree)
  195. assert.NoError(t, err)
  196. assert.Equal(t, 2, len(osdHosts.Nodes))
  197. dummyEmptyOsdTreeRaw := []byte(`{}`)
  198. var dummyEmptyTree OsdTree
  199. err = json.Unmarshal([]byte(dummyEmptyOsdTreeRaw), &dummyEmptyTree)
  200. assert.NoError(t, err)
  201. _, err = buildHostListFromTree(dummyEmptyTree)
  202. assert.Error(t, err)
  203. dummyEmptyNodeOsdTreeRaw := []byte(`{"nodes": []}`)
  204. var dummyEmptyNodeTree OsdTree
  205. err = json.Unmarshal([]byte(dummyEmptyNodeOsdTreeRaw), &dummyEmptyNodeTree)
  206. assert.NoError(t, err)
  207. _, err = buildHostListFromTree(dummyEmptyNodeTree)
  208. assert.NoError(t, err)
  209. }
  210. func TestGetRetryConfig(t *testing.T) {
  211. testcases := []struct {
  212. label string
  213. clusterInfo *ClusterInfo
  214. daemonType string
  215. expectedRetries int
  216. expectedDelay time.Duration
  217. }{
  218. {
  219. label: "case 1: mon daemon",
  220. clusterInfo: &ClusterInfo{},
  221. daemonType: "mon",
  222. expectedRetries: 10,
  223. expectedDelay: 60 * time.Second,
  224. },
  225. {
  226. label: "case 2: osd daemon with 5 minutes delay",
  227. clusterInfo: &ClusterInfo{OsdUpgradeTimeout: 5 * time.Minute},
  228. daemonType: "osd",
  229. expectedRetries: 30,
  230. expectedDelay: 10 * time.Second,
  231. },
  232. {
  233. label: "case 3: osd daemon with 10 minutes delay",
  234. clusterInfo: &ClusterInfo{OsdUpgradeTimeout: 10 * time.Minute},
  235. daemonType: "osd",
  236. expectedRetries: 60,
  237. expectedDelay: 10 * time.Second,
  238. },
  239. {
  240. label: "case 4: mds daemon",
  241. clusterInfo: &ClusterInfo{},
  242. daemonType: "mds",
  243. expectedRetries: 10,
  244. expectedDelay: 15 * time.Second,
  245. },
  246. }
  247. for _, tc := range testcases {
  248. actualRetries, actualDelay := getRetryConfig(tc.clusterInfo, tc.daemonType)
  249. assert.Equal(t, tc.expectedRetries, actualRetries, "[%s] failed to get correct retry count", tc.label)
  250. assert.Equalf(t, tc.expectedDelay, actualDelay, "[%s] failed to get correct delays between retries", tc.label)
  251. }
  252. }
  253. func TestOSDUpdateShouldCheckOkToStop(t *testing.T) {
  254. clusterInfo := AdminTestClusterInfo("mycluster")
  255. lsOutput := ""
  256. treeOutput := ""
  257. context := &clusterd.Context{
  258. Executor: &exectest.MockExecutor{
  259. MockExecuteCommandWithOutput: func(command string, args ...string) (string, error) {
  260. t.Logf("command: %s %v", command, args)
  261. if command != "ceph" || args[0] != "osd" {
  262. panic("not a 'ceph osd' call")
  263. }
  264. if args[1] == "tree" {
  265. if treeOutput == "" {
  266. return "", errors.Errorf("induced error")
  267. }
  268. return treeOutput, nil
  269. }
  270. if args[1] == "ls" {
  271. if lsOutput == "" {
  272. return "", errors.Errorf("induced error")
  273. }
  274. return lsOutput, nil
  275. }
  276. panic("do not understand command")
  277. },
  278. },
  279. }
  280. t.Run("3 nodes with 1 OSD each", func(t *testing.T) {
  281. lsOutput = fake.OsdLsOutput(3)
  282. treeOutput = fake.OsdTreeOutput(3, 1)
  283. assert.True(t, OSDUpdateShouldCheckOkToStop(context, clusterInfo))
  284. })
  285. t.Run("1 node with 3 OSDs", func(t *testing.T) {
  286. lsOutput = fake.OsdLsOutput(3)
  287. treeOutput = fake.OsdTreeOutput(1, 3)
  288. assert.False(t, OSDUpdateShouldCheckOkToStop(context, clusterInfo))
  289. })
  290. t.Run("2 nodes with 1 OSD each", func(t *testing.T) {
  291. lsOutput = fake.OsdLsOutput(2)
  292. treeOutput = fake.OsdTreeOutput(2, 1)
  293. assert.False(t, OSDUpdateShouldCheckOkToStop(context, clusterInfo))
  294. })
  295. t.Run("3 nodes with 3 OSDs each", func(t *testing.T) {
  296. lsOutput = fake.OsdLsOutput(9)
  297. treeOutput = fake.OsdTreeOutput(3, 3)
  298. assert.True(t, OSDUpdateShouldCheckOkToStop(context, clusterInfo))
  299. })
  300. // degraded case but good to test just in case
  301. t.Run("0 nodes", func(t *testing.T) {
  302. lsOutput = fake.OsdLsOutput(0)
  303. treeOutput = fake.OsdTreeOutput(0, 0)
  304. assert.False(t, OSDUpdateShouldCheckOkToStop(context, clusterInfo))
  305. })
  306. // degraded case, OSDs are failing to start so they haven't registered in the CRUSH map yet
  307. t.Run("0 nodes with down OSDs", func(t *testing.T) {
  308. lsOutput = fake.OsdLsOutput(3)
  309. treeOutput = fake.OsdTreeOutput(0, 1)
  310. assert.False(t, OSDUpdateShouldCheckOkToStop(context, clusterInfo))
  311. })
  312. }