rados.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*
  2. Copyright 2022 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. "crypto/rand"
  16. "encoding/json"
  17. "fmt"
  18. "math"
  19. "time"
  20. "github.com/pkg/errors"
  21. "github.com/rook/rook/pkg/clusterd"
  22. "github.com/rook/rook/pkg/util/exec"
  23. )
  24. /*
  25. Example Lock info:
  26. {
  27. "name": "rook-ceph-nfs",
  28. "type": "exclusive",
  29. "tag": "rook-ceph-nfs",
  30. "lockers": [
  31. {
  32. "name": "client.28945",
  33. "cookie": "test-cookie",
  34. "description": "",
  35. "expiration": "2022-09-08T23:56:57.924802+0000",
  36. "addr": "10.244.0.44:0/960227889"
  37. }
  38. ]
  39. }
  40. */
  41. type radosLockInfo struct {
  42. Name string `json:"name"`
  43. Tag string `json:"tag"`
  44. Lockers []radosLockerInfo `json:"lockers"`
  45. }
  46. type radosLockerInfo struct {
  47. Name string `json:"name"`
  48. Cookie string `json:"cookie"`
  49. }
  50. // RadosLockObject locks a rados object in a given pool and namespace and returns a lock "cookie"
  51. // that can be used to identify this unique lock.
  52. func RadosLockObject(
  53. context *clusterd.Context, clusterInfo *ClusterInfo,
  54. pool, namespace, objectName, lockName string,
  55. lockTimeout time.Duration,
  56. ) (string, error) {
  57. // generate a random "cookie" that identifies this lock "session"
  58. cookieLen := 12
  59. b := make([]byte, cookieLen)
  60. if _, err := rand.Read(b); err != nil {
  61. return "", errors.Wrapf(err, "failed to generate cookie for lock %q on rados object rados://%s/%s/%s",
  62. lockName, pool, namespace, objectName)
  63. }
  64. cookie := fmt.Sprintf("%x", b)[:cookieLen]
  65. cmd := NewRadosCommand(context, clusterInfo, []string{
  66. "--pool", pool,
  67. "--namespace", namespace,
  68. "lock", "get", objectName, lockName,
  69. "--lock-tag", lockName, // assume we aren't making many locks; use lock name as the tag
  70. "--lock-cookie", cookie,
  71. "--lock-duration", fmt.Sprintf("%d", int(math.Ceil(lockTimeout.Seconds()))),
  72. })
  73. if _, err := cmd.RunWithTimeout(exec.CephCommandsTimeout); err != nil {
  74. return "", errors.Wrapf(err, "failed to acquire lock %q on rados object rados://%s/%s/%s",
  75. lockName, pool, namespace, objectName)
  76. }
  77. return cookie, nil
  78. }
  79. // RadosUnlockObject unlocks a rados object in a given pool and namespace by searching for locks on
  80. // the object that match the "cookie" obtained from a RadosLockObject() call.
  81. func RadosUnlockObject(
  82. context *clusterd.Context, clusterInfo *ClusterInfo,
  83. pool, namespace, objectName, lockName string,
  84. lockCookie string,
  85. ) error {
  86. lockInfo, err := radosObjectLockInfo(context, clusterInfo, pool, namespace, objectName, lockName, lockCookie)
  87. if err != nil {
  88. return errors.Wrap(err, "failed to unlock object")
  89. }
  90. if lockInfo.Name != lockName || lockInfo.Tag != lockName {
  91. // some other lock has locked the object, but that means the lock with the given cookie no
  92. // longer has it locked; treat this as success
  93. logger.Infof("rados object rados://%s/%s/%s is not locked by lock %q but is locked by %q",
  94. pool, namespace, objectName, lockName, lockInfo.Name)
  95. return nil
  96. }
  97. if len(lockInfo.Lockers) == 0 {
  98. logger.Infof("rados object rados://%s/%s/%s is already fully unlocked (lock: %q, cookie: %q)",
  99. pool, namespace, objectName, lockName, lockCookie)
  100. return nil
  101. }
  102. locker := findLockerWithCookie(lockInfo.Lockers, lockCookie)
  103. if locker == nil {
  104. // this cookie is not locking the object but another is; still treat this as success but log it
  105. logger.Infof("rados object rados://%s/%s/%s is not locked by lock %q with cookie %q but is locked: %+v",
  106. pool, namespace, objectName, lockName, lockCookie, lockInfo.Lockers)
  107. return nil
  108. }
  109. cmd := NewRadosCommand(context, clusterInfo, []string{
  110. "--pool", pool,
  111. "--namespace", namespace,
  112. "lock", "break", objectName, lockName, locker.Name, // breaking the lock also requires locker name
  113. "--lock-tag", lockName, // assume we aren't making many locks; use lock name as the tag
  114. "--lock-cookie", lockCookie,
  115. })
  116. if _, err := cmd.RunWithTimeout(exec.CephCommandsTimeout); err != nil {
  117. return errors.Wrapf(err, "failed to unlock lock %q on rados object rados://%s/%s/%s",
  118. lockName, pool, namespace, objectName)
  119. }
  120. return nil
  121. }
  122. func radosObjectLockInfo(
  123. context *clusterd.Context, clusterInfo *ClusterInfo,
  124. pool, namespace, objectName, lockName string,
  125. lockCookie string,
  126. ) (radosLockInfo, error) {
  127. cmd := NewRadosCommand(context, clusterInfo, []string{
  128. "--pool", pool,
  129. "--namespace", namespace,
  130. "lock", "info", objectName, lockName,
  131. "--format", "json",
  132. })
  133. cmd.JsonOutput = true
  134. rawInfo, err := cmd.RunWithTimeout(exec.CephCommandsTimeout)
  135. if err != nil {
  136. return radosLockInfo{}, errors.Wrapf(err, "failed to get lock %q info for rados object rados://%s/%s/%s",
  137. lockName, pool, namespace, objectName)
  138. }
  139. lockInfo := radosLockInfo{}
  140. if err := json.Unmarshal(rawInfo, &lockInfo); err != nil {
  141. return radosLockInfo{}, errors.Wrapf(err, "failed to parse lock %q info for rados object rados://%s/%s/%s",
  142. lockName, pool, namespace, objectName)
  143. }
  144. return lockInfo, nil
  145. }
  146. func findLockerWithCookie(lockers []radosLockerInfo, cookie string) *radosLockerInfo {
  147. for _, locker := range lockers {
  148. if locker.Cookie == cookie {
  149. return &locker
  150. }
  151. }
  152. return nil
  153. }
  154. // RadosRemoveObject idempotently removes a rados object from the given pool and namespace.
  155. func RadosRemoveObject(
  156. context *clusterd.Context, clusterInfo *ClusterInfo,
  157. pool, namespace, objectName string,
  158. ) error {
  159. cmd := NewRadosCommand(context, clusterInfo, []string{
  160. "--pool", pool,
  161. "--namespace", namespace,
  162. "stat", objectName,
  163. })
  164. _, err := cmd.RunWithTimeout(exec.CephCommandsTimeout)
  165. if err != nil {
  166. if exec.IsTimeout(err) {
  167. return errors.Wrapf(err, "failed to determine if rados object rados://%s/%s/%s exists before removing it",
  168. pool, namespace, objectName)
  169. }
  170. // assume any other error means the object already doesn't exist
  171. logger.Debugf("rados object rados:/%s/%s/%s being removed is assumed to not exist after stat-ing: %v",
  172. pool, namespace, objectName, err)
  173. return nil
  174. }
  175. cmd = NewRadosCommand(context, clusterInfo, []string{
  176. "--pool", pool,
  177. "--namespace", namespace,
  178. "rm", objectName,
  179. })
  180. _, err = cmd.RunWithTimeout(exec.CephCommandsTimeout)
  181. if err != nil {
  182. return errors.Wrapf(err, "failed to remove rados object rados://%s/%s/%s", pool, namespace, objectName)
  183. }
  184. return nil
  185. }