You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
12 KiB

  1. /*
  2. * Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package cmd
  17. import (
  18. "path"
  19. "strconv"
  20. "strings"
  21. "sync"
  22. "testing"
  23. "time"
  24. )
  25. // API suite container common to both FS and XL.
  26. type TestRPCControlSuite struct {
  27. serverType string
  28. testServer TestServer
  29. testAuthConf *authConfig
  30. }
  31. // Setting up the test suite.
  32. // Starting the Test server with temporary FS backend.
  33. func (s *TestRPCControlSuite) SetUpSuite(c *testing.T) {
  34. s.testServer = StartTestControlRPCServer(c, s.serverType)
  35. s.testAuthConf = &authConfig{
  36. address: s.testServer.Server.Listener.Addr().String(),
  37. accessKey: s.testServer.AccessKey,
  38. secretKey: s.testServer.SecretKey,
  39. path: path.Join(reservedBucket, controlPath),
  40. loginMethod: "Control.LoginHandler",
  41. }
  42. }
  43. // No longer used with gocheck, but used in explicit teardown code in
  44. // each test function. // Called implicitly by "gopkg.in/check.v1"
  45. // after all tests are run.
  46. func (s *TestRPCControlSuite) TearDownSuite(c *testing.T) {
  47. s.testServer.Stop()
  48. }
  49. func TestRPCControlLock(t *testing.T) {
  50. //setup code
  51. s := &TestRPCControlSuite{serverType: "XL"}
  52. s.SetUpSuite(t)
  53. //run test
  54. s.testRPCControlLock(t)
  55. //teardown code
  56. s.TearDownSuite(t)
  57. }
  58. // Tests to validate the correctness of lock instrumentation control RPC end point.
  59. func (s *TestRPCControlSuite) testRPCControlLock(c *testing.T) {
  60. expectedResult := []lockStateCase{
  61. // Test case - 1.
  62. // Case where 10 read locks are held.
  63. // Entry for any of the 10 reads locks has to be found.
  64. // Since they held in a loop, Lock origin for first 10 read locks (opsID 0-9) should be the same.
  65. {
  66. volume: "my-bucket",
  67. path: "my-object",
  68. opsID: "0",
  69. readLock: true,
  70. lockOrigin: "[lock held] in github.com/minio/minio/cmd.TestLockStats[/Users/hackintoshrao/mycode/go/src/github.com/minio/minio/cmd/namespace-lock_test.go:298]",
  71. // expected metrics.
  72. expectedErr: nil,
  73. expectedLockStatus: "Running",
  74. expectedGlobalLockCount: 10,
  75. expectedRunningLockCount: 10,
  76. expectedBlockedLockCount: 0,
  77. expectedVolPathLockCount: 10,
  78. expectedVolPathRunningCount: 10,
  79. expectedVolPathBlockCount: 0,
  80. },
  81. // Test case 2.
  82. // Testing the existence of entry for the last read lock (read lock with opsID "9").
  83. {
  84. volume: "my-bucket",
  85. path: "my-object",
  86. opsID: "9",
  87. readLock: true,
  88. lockOrigin: "[lock held] in github.com/minio/minio/cmd.TestLockStats[/Users/hackintoshrao/mycode/go/src/github.com/minio/minio/cmd/namespace-lock_test.go:298]",
  89. // expected metrics.
  90. expectedErr: nil,
  91. expectedLockStatus: "Running",
  92. expectedGlobalLockCount: 10,
  93. expectedRunningLockCount: 10,
  94. expectedBlockedLockCount: 0,
  95. expectedVolPathLockCount: 10,
  96. expectedVolPathRunningCount: 10,
  97. expectedVolPathBlockCount: 0,
  98. },
  99. // Test case 3.
  100. // Hold a write lock, and it should block since 10 read locks
  101. // on <"my-bucket", "my-object"> are still held.
  102. {
  103. volume: "my-bucket",
  104. path: "my-object",
  105. opsID: "10",
  106. readLock: false,
  107. lockOrigin: "[lock held] in github.com/minio/minio/cmd.TestLockStats[/Users/hackintoshrao/mycode/go/src/github.com/minio/minio/cmd/namespace-lock_test.go:298]",
  108. // expected metrics.
  109. expectedErr: nil,
  110. expectedLockStatus: "Blocked",
  111. expectedGlobalLockCount: 11,
  112. expectedRunningLockCount: 10,
  113. expectedBlockedLockCount: 1,
  114. expectedVolPathLockCount: 11,
  115. expectedVolPathRunningCount: 10,
  116. expectedVolPathBlockCount: 1,
  117. },
  118. // Test case 4.
  119. // Expected result when all the read locks are released and the blocked write lock acquires the lock.
  120. {
  121. volume: "my-bucket",
  122. path: "my-object",
  123. opsID: "10",
  124. readLock: false,
  125. lockOrigin: "[lock held] in github.com/minio/minio/cmd.TestLockStats[/Users/hackintoshrao/mycode/go/src/github.com/minio/minio/cmd/namespace-lock_test.go:298]",
  126. // expected metrics.
  127. expectedErr: nil,
  128. expectedLockStatus: "Running",
  129. expectedGlobalLockCount: 1,
  130. expectedRunningLockCount: 1,
  131. expectedBlockedLockCount: 0,
  132. expectedVolPathLockCount: 1,
  133. expectedVolPathRunningCount: 1,
  134. expectedVolPathBlockCount: 0,
  135. },
  136. // Test case - 5.
  137. // At the end after locks are released, its verified whether the counters are set to 0.
  138. {
  139. volume: "my-bucket",
  140. path: "my-object",
  141. // expected metrics.
  142. expectedErr: nil,
  143. expectedLockStatus: "Blocked",
  144. expectedGlobalLockCount: 0,
  145. expectedRunningLockCount: 0,
  146. expectedBlockedLockCount: 0,
  147. },
  148. }
  149. // used to make sure that the tests don't end till locks held in other go routines are released.
  150. var wg sync.WaitGroup
  151. // Hold 5 read locks. We should find the info about these in the RPC response.
  152. // hold 10 read locks.
  153. // Then call the RPC control end point for obtaining lock instrumentation info.
  154. for i := 0; i < 10; i++ {
  155. nsMutex.RLock("my-bucket", "my-object", strconv.Itoa(i))
  156. }
  157. client := newAuthClient(s.testAuthConf)
  158. defer client.Close()
  159. args := &GenericArgs{}
  160. reply := make(map[string]*SystemLockState)
  161. // Call the lock instrumentation RPC end point.
  162. err := client.Call("Control.LockInfo", args, &reply)
  163. if err != nil {
  164. c.Errorf("Add: expected no error but got string %q", err.Error())
  165. }
  166. // expected lock info.
  167. expectedLockStats := expectedResult[0]
  168. // verify the actual lock info with the expected one.
  169. // verify the existence entry for first read lock (read lock with opsID "0").
  170. verifyRPCLockInfoResponse(expectedLockStats, reply, c, 1)
  171. expectedLockStats = expectedResult[1]
  172. // verify the actual lock info with the expected one.
  173. // verify the existence entry for last read lock (read lock with opsID "9").
  174. verifyRPCLockInfoResponse(expectedLockStats, reply, c, 2)
  175. // now hold a write lock in a different go routine and it should block since 10 read locks are
  176. // still held.
  177. wg.Add(1)
  178. go func() {
  179. defer wg.Done()
  180. // blocks till all read locks are released.
  181. nsMutex.Lock("my-bucket", "my-object", strconv.Itoa(10))
  182. // Once the above attempt to lock is unblocked/acquired, we verify the stats and release the lock.
  183. expectedWLockStats := expectedResult[3]
  184. // Since the write lock acquired here, the number of blocked locks should reduce by 1 and
  185. // count of running locks should increase by 1.
  186. // Call the RPC control handle to fetch the lock instrumentation info.
  187. reply = make(map[string]*SystemLockState)
  188. // Call the lock instrumentation RPC end point.
  189. err = client.Call("Control.LockInfo", args, &reply)
  190. if err != nil {
  191. c.Errorf("Add: expected no error but got string %q", err.Error())
  192. }
  193. verifyRPCLockInfoResponse(expectedWLockStats, reply, c, 4)
  194. // release the write lock.
  195. nsMutex.Unlock("my-bucket", "my-object", strconv.Itoa(10))
  196. }()
  197. // waiting for a second so that the attempt to acquire the write lock in
  198. // the above go routines gets blocked.
  199. time.Sleep(1 * time.Second)
  200. // The write lock should have got blocked by now,
  201. // check whether the entry for one blocked lock exists.
  202. expectedLockStats = expectedResult[2]
  203. // Call the RPC control handle to fetch the lock instrumentation info.
  204. reply = make(map[string]*SystemLockState)
  205. // Call the lock instrumentation RPC end point.
  206. err = client.Call("Control.LockInfo", args, &reply)
  207. if err != nil {
  208. c.Errorf("Add: expected no error but got string %q", err.Error())
  209. }
  210. verifyRPCLockInfoResponse(expectedLockStats, reply, c, 3)
  211. // Release all the read locks held.
  212. // the blocked write lock in the above go routines should get unblocked.
  213. for i := 0; i < 10; i++ {
  214. nsMutex.RUnlock("my-bucket", "my-object", strconv.Itoa(i))
  215. }
  216. wg.Wait()
  217. // Since all the locks are released. There should not be any entry in the lock info.
  218. // and all the counters should be set to 0.
  219. reply = make(map[string]*SystemLockState)
  220. // Call the lock instrumentation RPC end point.
  221. err = client.Call("Control.LockInfo", args, &reply)
  222. if err != nil {
  223. c.Errorf("Add: expected no error but got string %q", err.Error())
  224. }
  225. for _, rpcLockInfo := range reply {
  226. if rpcLockInfo.TotalAcquiredLocks != 0 && rpcLockInfo.TotalLocks != 0 && rpcLockInfo.TotalBlockedLocks != 0 {
  227. c.Fatalf("The counters are not reset properly after all locks are released")
  228. }
  229. if len(rpcLockInfo.LocksInfoPerObject) != 0 {
  230. c.Fatalf("Since all locks are released there shouldn't have been any lock info entry, but found %d", len(rpcLockInfo.LocksInfoPerObject))
  231. }
  232. }
  233. }
  234. func TestControlHealDiskMetadataH(t *testing.T) {
  235. // Setup code
  236. s := &TestRPCControlSuite{serverType: "XL"}
  237. s.SetUpSuite(t)
  238. // Run test
  239. s.testControlHealFormatH(t)
  240. // Teardown code
  241. s.TearDownSuite(t)
  242. }
  243. // TestControlHandlerHealFormat - Registers and call the `HealFormatHandler`, asserts to validate the success.
  244. func (s *TestRPCControlSuite) testControlHealFormatH(c *testing.T) {
  245. // The suite has already started the test RPC server, just send RPC calls.
  246. client := newAuthClient(s.testAuthConf)
  247. defer client.Close()
  248. args := &GenericArgs{}
  249. reply := &GenericReply{}
  250. err := client.Call("Control.HealFormatHandler", args, reply)
  251. if err != nil {
  252. c.Errorf("Test failed with <ERROR> %s", err)
  253. }
  254. }
  255. func TestControlHealObjectH(t *testing.T) {
  256. // Setup code
  257. s := &TestRPCControlSuite{serverType: "XL"}
  258. s.SetUpSuite(t)
  259. // Run test
  260. s.testControlHealObjectsH(t)
  261. // Teardown code
  262. s.TearDownSuite(t)
  263. }
  264. func (s *TestRPCControlSuite) testControlHealObjectsH(t *testing.T) {
  265. client := newAuthClient(s.testAuthConf)
  266. defer client.Close()
  267. objAPI := newObjectLayerFn()
  268. err := objAPI.MakeBucket("testbucket")
  269. if err != nil {
  270. t.Fatalf("Create bucket failed with <ERROR> %s", err)
  271. }
  272. datum := strings.NewReader("a")
  273. _, err = objAPI.PutObject("testbucket", "testobject1", 1, datum, nil, "")
  274. if err != nil {
  275. t.Fatalf("Put object failed with <ERROR> %s", err)
  276. }
  277. datum = strings.NewReader("a")
  278. _, err = objAPI.PutObject("testbucket", "testobject2", 1, datum, nil, "")
  279. if err != nil {
  280. t.Fatalf("Put object failed with <ERROR> %s", err)
  281. }
  282. args := &HealObjectArgs{
  283. Bucket: "testbucket",
  284. Objects: []ObjectInfo{
  285. {
  286. Name: "testobject1",
  287. }, {
  288. Name: "testobject2",
  289. },
  290. },
  291. }
  292. reply := &HealObjectReply{}
  293. err = client.Call("Control.HealObjectsHandler", args, reply)
  294. if err != nil {
  295. t.Errorf("Test failed with <ERROR> %s", err)
  296. }
  297. }
  298. func TestControlListObjectsHealH(t *testing.T) {
  299. // Setup code
  300. s := &TestRPCControlSuite{serverType: "XL"}
  301. s.SetUpSuite(t)
  302. // Run test
  303. s.testControlListObjectsHealH(t)
  304. // Teardown code
  305. s.TearDownSuite(t)
  306. }
  307. func (s *TestRPCControlSuite) testControlListObjectsHealH(t *testing.T) {
  308. client := newAuthClient(s.testAuthConf)
  309. defer client.Close()
  310. objAPI := newObjectLayerFn()
  311. // Create a bucket
  312. err := objAPI.MakeBucket("testbucket")
  313. if err != nil {
  314. t.Fatalf("Create bucket failed - %s", err)
  315. }
  316. r := strings.NewReader("0")
  317. _, err = objAPI.PutObject("testbucket", "testObj-0", 1, r, nil, "")
  318. if err != nil {
  319. t.Fatalf("Object creation failed - %s", err)
  320. }
  321. args := &HealListArgs{
  322. GenericArgs{}, "testbucket", "testObj-",
  323. "", "", 100,
  324. }
  325. reply := &GenericReply{}
  326. err = client.Call("Control.ListObjectsHealHandler", args, reply)
  327. if err != nil {
  328. t.Errorf("Test failed - %s", err)
  329. }
  330. }