rbd_util.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  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. //
  14. // utility functions to setup rbd volume
  15. // mainly implement diskManager interface
  16. //
  17. package rbd
  18. import (
  19. "encoding/json"
  20. "fmt"
  21. "io/ioutil"
  22. "os"
  23. "os/exec"
  24. "path/filepath"
  25. "strconv"
  26. "strings"
  27. "time"
  28. "k8s.io/api/core/v1"
  29. "k8s.io/apimachinery/pkg/api/resource"
  30. "k8s.io/apimachinery/pkg/util/errors"
  31. "k8s.io/apimachinery/pkg/util/wait"
  32. volumehelpers "k8s.io/cloud-provider/volume/helpers"
  33. "k8s.io/klog"
  34. "k8s.io/kubernetes/pkg/util/mount"
  35. "k8s.io/kubernetes/pkg/util/node"
  36. "k8s.io/kubernetes/pkg/volume"
  37. volutil "k8s.io/kubernetes/pkg/volume/util"
  38. utilpath "k8s.io/utils/path"
  39. )
  40. const (
  41. imageWatcherStr = "watcher="
  42. imageSizeStr = "size "
  43. kubeLockMagic = "kubelet_lock_magic_"
  44. // The following three values are used for 30 seconds timeout
  45. // while waiting for RBD Watcher to expire.
  46. rbdImageWatcherInitDelay = 1 * time.Second
  47. rbdImageWatcherFactor = 1.4
  48. rbdImageWatcherSteps = 10
  49. rbdImageSizeUnitMiB = 1024 * 1024
  50. )
  51. func getDevFromImageAndPool(pool, image string) (string, bool) {
  52. device, found := getRbdDevFromImageAndPool(pool, image)
  53. if found {
  54. return device, true
  55. }
  56. device, found = getNbdDevFromImageAndPool(pool, image)
  57. if found {
  58. return device, true
  59. }
  60. return "", false
  61. }
  62. // Search /sys/bus for rbd device that matches given pool and image.
  63. func getRbdDevFromImageAndPool(pool string, image string) (string, bool) {
  64. // /sys/bus/rbd/devices/X/name and /sys/bus/rbd/devices/X/pool
  65. sys_path := "/sys/bus/rbd/devices"
  66. if dirs, err := ioutil.ReadDir(sys_path); err == nil {
  67. for _, f := range dirs {
  68. // Pool and name format:
  69. // see rbd_pool_show() and rbd_name_show() at
  70. // https://github.com/torvalds/linux/blob/master/drivers/block/rbd.c
  71. name := f.Name()
  72. // First match pool, then match name.
  73. poolFile := filepath.Join(sys_path, name, "pool")
  74. poolBytes, err := ioutil.ReadFile(poolFile)
  75. if err != nil {
  76. klog.V(4).Infof("error reading %s: %v", poolFile, err)
  77. continue
  78. }
  79. if strings.TrimSpace(string(poolBytes)) != pool {
  80. klog.V(4).Infof("device %s is not %q: %q", name, pool, string(poolBytes))
  81. continue
  82. }
  83. imgFile := filepath.Join(sys_path, name, "name")
  84. imgBytes, err := ioutil.ReadFile(imgFile)
  85. if err != nil {
  86. klog.V(4).Infof("error reading %s: %v", imgFile, err)
  87. continue
  88. }
  89. if strings.TrimSpace(string(imgBytes)) != image {
  90. klog.V(4).Infof("device %s is not %q: %q", name, image, string(imgBytes))
  91. continue
  92. }
  93. // Found a match, check if device exists.
  94. devicePath := "/dev/rbd" + name
  95. if _, err := os.Lstat(devicePath); err == nil {
  96. return devicePath, true
  97. }
  98. }
  99. }
  100. return "", false
  101. }
  102. func getMaxNbds() (int, error) {
  103. // the max number of nbd devices may be found in maxNbdsPath
  104. // we will check sysfs for possible nbd devices even if this is not available
  105. maxNbdsPath := "/sys/module/nbd/parameters/nbds_max"
  106. _, err := os.Lstat(maxNbdsPath)
  107. if err != nil {
  108. return 0, fmt.Errorf("rbd-nbd: failed to retrieve max_nbds from %s err: %q", maxNbdsPath, err)
  109. }
  110. klog.V(4).Infof("found nbds max parameters file at %s", maxNbdsPath)
  111. maxNbdBytes, err := ioutil.ReadFile(maxNbdsPath)
  112. if err != nil {
  113. return 0, fmt.Errorf("rbd-nbd: failed to read max_nbds from %s err: %q", maxNbdsPath, err)
  114. }
  115. maxNbds, err := strconv.Atoi(strings.TrimSpace(string(maxNbdBytes)))
  116. if err != nil {
  117. return 0, fmt.Errorf("rbd-nbd: failed to read max_nbds err: %q", err)
  118. }
  119. klog.V(4).Infof("rbd-nbd: max_nbds: %d", maxNbds)
  120. return maxNbds, nil
  121. }
  122. // Locate any existing rbd-nbd process mapping given a <pool, image>.
  123. // Recent versions of rbd-nbd tool can correctly provide this info using list-mapped
  124. // but older versions of list-mapped don't.
  125. // The implementation below peeks at the command line of nbd bound processes
  126. // to figure out any mapped images.
  127. func getNbdDevFromImageAndPool(pool string, image string) (string, bool) {
  128. // nbd module exports the pid of serving process in sysfs
  129. basePath := "/sys/block/nbd"
  130. // Do not change imgPath format - some tools like rbd-nbd are strict about it.
  131. imgPath := fmt.Sprintf("%s/%s", pool, image)
  132. maxNbds, maxNbdsErr := getMaxNbds()
  133. if maxNbdsErr != nil {
  134. klog.V(4).Infof("error reading nbds_max %v", maxNbdsErr)
  135. return "", false
  136. }
  137. for i := 0; i < maxNbds; i++ {
  138. nbdPath := basePath + strconv.Itoa(i)
  139. _, err := os.Lstat(nbdPath)
  140. if err != nil {
  141. klog.V(4).Infof("error reading nbd info directory %s: %v", nbdPath, err)
  142. continue
  143. }
  144. pidBytes, err := ioutil.ReadFile(filepath.Join(nbdPath, "pid"))
  145. if err != nil {
  146. klog.V(5).Infof("did not find valid pid file in dir %s: %v", nbdPath, err)
  147. continue
  148. }
  149. cmdlineFileName := filepath.Join("/proc", strings.TrimSpace(string(pidBytes)), "cmdline")
  150. rawCmdline, err := ioutil.ReadFile(cmdlineFileName)
  151. if err != nil {
  152. klog.V(4).Infof("failed to read cmdline file %s: %v", cmdlineFileName, err)
  153. continue
  154. }
  155. cmdlineArgs := strings.FieldsFunc(string(rawCmdline), func(r rune) bool {
  156. return r == '\u0000'
  157. })
  158. // Check if this process is mapping a rbd device.
  159. // Only accepted pattern of cmdline is from execRbdMap:
  160. // rbd-nbd map pool/image ...
  161. if len(cmdlineArgs) < 3 || cmdlineArgs[0] != "rbd-nbd" || cmdlineArgs[1] != "map" {
  162. klog.V(4).Infof("nbd device %s is not used by rbd", nbdPath)
  163. continue
  164. }
  165. if cmdlineArgs[2] != imgPath {
  166. klog.V(4).Infof("rbd-nbd device %s did not match expected image path: %s with path found: %s",
  167. nbdPath, imgPath, cmdlineArgs[2])
  168. continue
  169. }
  170. devicePath := filepath.Join("/dev", "nbd"+strconv.Itoa(i))
  171. if _, err := os.Lstat(devicePath); err != nil {
  172. klog.Warningf("Stat device %s for imgpath %s failed %v", devicePath, imgPath, err)
  173. continue
  174. }
  175. return devicePath, true
  176. }
  177. return "", false
  178. }
  179. // Stat a path, if it doesn't exist, retry maxRetries times.
  180. func waitForPath(pool, image string, maxRetries int, useNbdDriver bool) (string, bool) {
  181. for i := 0; i < maxRetries; i++ {
  182. if i != 0 {
  183. time.Sleep(time.Second)
  184. }
  185. if useNbdDriver {
  186. if devicePath, found := getNbdDevFromImageAndPool(pool, image); found {
  187. return devicePath, true
  188. }
  189. } else {
  190. if devicePath, found := getRbdDevFromImageAndPool(pool, image); found {
  191. return devicePath, true
  192. }
  193. }
  194. }
  195. return "", false
  196. }
  197. // Execute command to map a rbd device for mounter.
  198. // rbdCmd is driver dependent and either "rbd" or "rbd-nbd".
  199. func execRbdMap(b rbdMounter, rbdCmd string, mon string) ([]byte, error) {
  200. // Commandline: rbdCmd map imgPath ...
  201. // do not change this format - some tools like rbd-nbd are strict about it.
  202. imgPath := fmt.Sprintf("%s/%s", b.Pool, b.Image)
  203. if b.Secret != "" {
  204. return b.exec.Run(rbdCmd,
  205. "map", imgPath, "--id", b.Id, "-m", mon, "--key="+b.Secret)
  206. } else {
  207. return b.exec.Run(rbdCmd,
  208. "map", imgPath, "--id", b.Id, "-m", mon, "-k", b.Keyring)
  209. }
  210. }
  211. // Check if rbd-nbd tools are installed.
  212. func checkRbdNbdTools(e mount.Exec) bool {
  213. _, err := e.Run("modprobe", "nbd")
  214. if err != nil {
  215. klog.V(5).Infof("rbd-nbd: nbd modprobe failed with error %v", err)
  216. return false
  217. }
  218. if _, err := e.Run("rbd-nbd", "--version"); err != nil {
  219. klog.V(5).Infof("rbd-nbd: getting rbd-nbd version failed with error %v", err)
  220. return false
  221. }
  222. klog.V(3).Infof("rbd-nbd tools were found.")
  223. return true
  224. }
  225. // Make a directory like /var/lib/kubelet/plugins/kubernetes.io/rbd/mounts/pool-image-image.
  226. func makePDNameInternal(host volume.VolumeHost, pool string, image string) string {
  227. // Backward compatibility for the deprecated format: /var/lib/kubelet/plugins/kubernetes.io/rbd/rbd/pool-image-image.
  228. deprecatedDir := filepath.Join(host.GetPluginDir(rbdPluginName), "rbd", pool+"-image-"+image)
  229. info, err := os.Stat(deprecatedDir)
  230. if err == nil && info.IsDir() {
  231. // The device mount path has already been created with the deprecated format, return it.
  232. klog.V(5).Infof("Deprecated format path %s found", deprecatedDir)
  233. return deprecatedDir
  234. }
  235. // Return the canonical format path.
  236. return filepath.Join(host.GetPluginDir(rbdPluginName), volutil.MountsInGlobalPDPath, pool+"-image-"+image)
  237. }
  238. // Make a directory like /var/lib/kubelet/plugins/kubernetes.io/rbd/volumeDevices/pool-image-image.
  239. func makeVDPDNameInternal(host volume.VolumeHost, pool string, image string) string {
  240. return filepath.Join(host.GetVolumeDevicePluginDir(rbdPluginName), pool+"-image-"+image)
  241. }
  242. // RBDUtil implements diskManager interface.
  243. type RBDUtil struct{}
  244. var _ diskManager = &RBDUtil{}
  245. func (util *RBDUtil) MakeGlobalPDName(rbd rbd) string {
  246. return makePDNameInternal(rbd.plugin.host, rbd.Pool, rbd.Image)
  247. }
  248. func (util *RBDUtil) MakeGlobalVDPDName(rbd rbd) string {
  249. return makeVDPDNameInternal(rbd.plugin.host, rbd.Pool, rbd.Image)
  250. }
  251. func rbdErrors(runErr, resultErr error) error {
  252. if err, ok := runErr.(*exec.Error); ok {
  253. if err.Err == exec.ErrNotFound {
  254. return fmt.Errorf("rbd: rbd cmd not found")
  255. }
  256. }
  257. return resultErr
  258. }
  259. // 'rbd' utility builds a comma-separated list of monitor addresses from '-m' /
  260. // '--mon_host` parameter (comma, semi-colon, or white-space delimited monitor
  261. // addresses) and send it to kernel rbd/libceph modules, which can accept
  262. // comma-seprated list of monitor addresses (e.g. ip1[:port1][,ip2[:port2]...])
  263. // in their first version in linux (see
  264. // https://github.com/torvalds/linux/blob/602adf400201636e95c3fed9f31fba54a3d7e844/net/ceph/ceph_common.c#L239).
  265. // Also, libceph module chooses monitor randomly, so we can simply pass all
  266. // addresses without randomization (see
  267. // https://github.com/torvalds/linux/blob/602adf400201636e95c3fed9f31fba54a3d7e844/net/ceph/mon_client.c#L132).
  268. func (util *RBDUtil) kernelRBDMonitorsOpt(mons []string) string {
  269. return strings.Join(mons, ",")
  270. }
  271. // rbdUnlock releases a lock on image if found.
  272. func (util *RBDUtil) rbdUnlock(b rbdMounter) error {
  273. var err error
  274. var output, locker string
  275. var cmd []byte
  276. var secret_opt []string
  277. if b.Secret != "" {
  278. secret_opt = []string{"--key=" + b.Secret}
  279. } else {
  280. secret_opt = []string{"-k", b.Keyring}
  281. }
  282. if len(b.adminId) == 0 {
  283. b.adminId = b.Id
  284. }
  285. if len(b.adminSecret) == 0 {
  286. b.adminSecret = b.Secret
  287. }
  288. // Construct lock id using host name and a magic prefix.
  289. hostName, err := node.GetHostname("")
  290. if err != nil {
  291. return err
  292. }
  293. lock_id := kubeLockMagic + hostName
  294. mon := util.kernelRBDMonitorsOpt(b.Mon)
  295. // Get the locker name, something like "client.1234".
  296. args := []string{"lock", "list", b.Image, "--pool", b.Pool, "--id", b.Id, "-m", mon}
  297. args = append(args, secret_opt...)
  298. cmd, err = b.exec.Run("rbd", args...)
  299. output = string(cmd)
  300. klog.V(4).Infof("lock list output %q", output)
  301. if err != nil {
  302. return err
  303. }
  304. ind := strings.LastIndex(output, lock_id) - 1
  305. for i := ind; i >= 0; i-- {
  306. if output[i] == '\n' {
  307. locker = output[(i + 1):ind]
  308. break
  309. }
  310. }
  311. // Remove a lock if found: rbd lock remove.
  312. if len(locker) > 0 {
  313. args := []string{"lock", "remove", b.Image, lock_id, locker, "--pool", b.Pool, "--id", b.Id, "-m", mon}
  314. args = append(args, secret_opt...)
  315. cmd, err = b.exec.Run("rbd", args...)
  316. if err == nil {
  317. klog.V(4).Infof("rbd: successfully remove lock (locker_id: %s) on image: %s/%s with id %s mon %s", lock_id, b.Pool, b.Image, b.Id, mon)
  318. } else {
  319. klog.Warningf("rbd: failed to remove lock (lock_id: %s) on image: %s/%s with id %s mon %s: %v", lock_id, b.Pool, b.Image, b.Id, mon, err)
  320. }
  321. }
  322. return err
  323. }
  324. // AttachDisk attaches the disk on the node.
  325. func (util *RBDUtil) AttachDisk(b rbdMounter) (string, error) {
  326. var output []byte
  327. globalPDPath := util.MakeGlobalPDName(*b.rbd)
  328. if pathExists, pathErr := mount.PathExists(globalPDPath); pathErr != nil {
  329. return "", fmt.Errorf("Error checking if path exists: %v", pathErr)
  330. } else if !pathExists {
  331. if err := os.MkdirAll(globalPDPath, 0750); err != nil {
  332. return "", err
  333. }
  334. }
  335. // Evalute whether this device was mapped with rbd.
  336. devicePath, mapped := waitForPath(b.Pool, b.Image, 1 /*maxRetries*/, false /*useNbdDriver*/)
  337. // If rbd-nbd tools are found, we will fallback to it should the default krbd driver fail.
  338. nbdToolsFound := false
  339. if !mapped {
  340. nbdToolsFound = checkRbdNbdTools(b.exec)
  341. if nbdToolsFound {
  342. devicePath, mapped = waitForPath(b.Pool, b.Image, 1 /*maxRetries*/, true /*useNbdDriver*/)
  343. }
  344. }
  345. if !mapped {
  346. // Currently, we don't acquire advisory lock on image, but for backward
  347. // compatibility, we need to check if the image is being used by nodes running old kubelet.
  348. // osd_client_watch_timeout defaults to 30 seconds, if the watcher stays active longer than 30 seconds,
  349. // rbd image does not get mounted and failure message gets generated.
  350. backoff := wait.Backoff{
  351. Duration: rbdImageWatcherInitDelay,
  352. Factor: rbdImageWatcherFactor,
  353. Steps: rbdImageWatcherSteps,
  354. }
  355. needValidUsed := true
  356. if b.accessModes != nil {
  357. // If accessModes only contains ReadOnlyMany, we don't need check rbd status of being used.
  358. if len(b.accessModes) == 1 && b.accessModes[0] == v1.ReadOnlyMany {
  359. needValidUsed = false
  360. }
  361. }
  362. // If accessModes is nil, the volume is referenced by in-line volume.
  363. // We can assume the AccessModes to be {"RWO" and "ROX"}, which is what the volume plugin supports.
  364. // We do not need to consider ReadOnly here, because it is used for VolumeMounts.
  365. if needValidUsed {
  366. err := wait.ExponentialBackoff(backoff, func() (bool, error) {
  367. used, rbdOutput, err := util.rbdStatus(&b)
  368. if err != nil {
  369. return false, fmt.Errorf("fail to check rbd image status with: (%v), rbd output: (%s)", err, rbdOutput)
  370. }
  371. return !used, nil
  372. })
  373. // Return error if rbd image has not become available for the specified timeout.
  374. if err == wait.ErrWaitTimeout {
  375. return "", fmt.Errorf("rbd image %s/%s is still being used", b.Pool, b.Image)
  376. }
  377. // Return error if any other errors were encountered during waiting for the image to become available.
  378. if err != nil {
  379. return "", err
  380. }
  381. }
  382. mon := util.kernelRBDMonitorsOpt(b.Mon)
  383. klog.V(1).Infof("rbd: map mon %s", mon)
  384. _, err := b.exec.Run("modprobe", "rbd")
  385. if err != nil {
  386. klog.Warningf("rbd: failed to load rbd kernel module:%v", err)
  387. }
  388. output, err = execRbdMap(b, "rbd", mon)
  389. if err != nil {
  390. if !nbdToolsFound {
  391. klog.V(1).Infof("rbd: map error %v, rbd output: %s", err, string(output))
  392. return "", fmt.Errorf("rbd: map failed %v, rbd output: %s", err, string(output))
  393. }
  394. klog.V(3).Infof("rbd: map failed with %v, %s. Retrying with rbd-nbd", err, string(output))
  395. errList := []error{err}
  396. outputList := output
  397. output, err = execRbdMap(b, "rbd-nbd", mon)
  398. if err != nil {
  399. errList = append(errList, err)
  400. outputList = append(outputList, output...)
  401. return "", fmt.Errorf("rbd: map failed %v, rbd output: %s", errors.NewAggregate(errList), string(outputList))
  402. }
  403. devicePath, mapped = waitForPath(b.Pool, b.Image, 10 /*maxRetries*/, true /*useNbdDrive*/)
  404. } else {
  405. devicePath, mapped = waitForPath(b.Pool, b.Image, 10 /*maxRetries*/, false /*useNbdDriver*/)
  406. }
  407. if !mapped {
  408. return "", fmt.Errorf("Could not map image %s/%s, Timeout after 10s", b.Pool, b.Image)
  409. }
  410. }
  411. return devicePath, nil
  412. }
  413. // DetachDisk detaches the disk from the node.
  414. // It detaches device from the node if device is provided, and removes the lock
  415. // if there is persisted RBD info under deviceMountPath.
  416. func (util *RBDUtil) DetachDisk(plugin *rbdPlugin, deviceMountPath string, device string) error {
  417. if len(device) == 0 {
  418. return fmt.Errorf("DetachDisk failed , device is empty")
  419. }
  420. exec := plugin.host.GetExec(plugin.GetPluginName())
  421. var rbdCmd string
  422. // Unlike map, we cannot fallthrough for unmap
  423. // the tool to unmap is based on device type
  424. if strings.HasPrefix(device, "/dev/nbd") {
  425. rbdCmd = "rbd-nbd"
  426. } else {
  427. rbdCmd = "rbd"
  428. }
  429. // rbd unmap
  430. output, err := exec.Run(rbdCmd, "unmap", device)
  431. if err != nil {
  432. return rbdErrors(err, fmt.Errorf("rbd: failed to unmap device %s, error %v, rbd output: %v", device, err, output))
  433. }
  434. klog.V(3).Infof("rbd: successfully unmap device %s", device)
  435. // Currently, we don't persist rbd info on the disk, but for backward
  436. // compatbility, we need to clean it if found.
  437. rbdFile := filepath.Join(deviceMountPath, "rbd.json")
  438. exists, err := utilpath.Exists(utilpath.CheckFollowSymlink, rbdFile)
  439. if err != nil {
  440. return err
  441. }
  442. if exists {
  443. klog.V(3).Infof("rbd: old rbd.json is found under %s, cleaning it", deviceMountPath)
  444. err = util.cleanOldRBDFile(plugin, rbdFile)
  445. if err != nil {
  446. klog.Errorf("rbd: failed to clean %s", rbdFile)
  447. return err
  448. }
  449. klog.V(3).Infof("rbd: successfully remove %s", rbdFile)
  450. }
  451. return nil
  452. }
  453. // DetachBlockDisk detaches the disk from the node.
  454. func (util *RBDUtil) DetachBlockDisk(disk rbdDiskUnmapper, mapPath string) error {
  455. if pathExists, pathErr := mount.PathExists(mapPath); pathErr != nil {
  456. return fmt.Errorf("Error checking if path exists: %v", pathErr)
  457. } else if !pathExists {
  458. klog.Warningf("Warning: Unmap skipped because path does not exist: %v", mapPath)
  459. return nil
  460. }
  461. // If we arrive here, device is no longer used, see if we need to logout of the target
  462. device, err := getBlockVolumeDevice(mapPath)
  463. if err != nil {
  464. return err
  465. }
  466. if len(device) == 0 {
  467. return fmt.Errorf("DetachDisk failed , device is empty")
  468. }
  469. exec := disk.plugin.host.GetExec(disk.plugin.GetPluginName())
  470. var rbdCmd string
  471. // Unlike map, we cannot fallthrough here.
  472. // Any nbd device must be unmapped by rbd-nbd
  473. if strings.HasPrefix(device, "/dev/nbd") {
  474. rbdCmd = "rbd-nbd"
  475. klog.V(4).Infof("rbd: using rbd-nbd for unmap function")
  476. } else {
  477. rbdCmd = "rbd"
  478. klog.V(4).Infof("rbd: using rbd for unmap function")
  479. }
  480. // rbd unmap
  481. output, err := exec.Run(rbdCmd, "unmap", device)
  482. if err != nil {
  483. return rbdErrors(err, fmt.Errorf("rbd: failed to unmap device %s, error %v, rbd output: %s", device, err, string(output)))
  484. }
  485. klog.V(3).Infof("rbd: successfully unmap device %s", device)
  486. return nil
  487. }
  488. // cleanOldRBDFile read rbd info from rbd.json file and removes lock if found.
  489. // At last, it removes rbd.json file.
  490. func (util *RBDUtil) cleanOldRBDFile(plugin *rbdPlugin, rbdFile string) error {
  491. mounter := &rbdMounter{
  492. // util.rbdUnlock needs it to run command.
  493. rbd: newRBD("", "", "", "", false, plugin, util),
  494. }
  495. fp, err := os.Open(rbdFile)
  496. if err != nil {
  497. return fmt.Errorf("rbd: open err %s/%s", rbdFile, err)
  498. }
  499. defer fp.Close()
  500. decoder := json.NewDecoder(fp)
  501. if err = decoder.Decode(mounter); err != nil {
  502. return fmt.Errorf("rbd: decode err: %v.", err)
  503. }
  504. if err != nil {
  505. klog.Errorf("failed to load rbd info from %s: %v", rbdFile, err)
  506. return err
  507. }
  508. // Remove rbd lock if found.
  509. // The disk is not attached to this node anymore, so the lock on image
  510. // for this node can be removed safely.
  511. err = util.rbdUnlock(*mounter)
  512. if err == nil {
  513. os.Remove(rbdFile)
  514. }
  515. return err
  516. }
  517. func (util *RBDUtil) CreateImage(p *rbdVolumeProvisioner) (r *v1.RBDPersistentVolumeSource, size int, err error) {
  518. var output []byte
  519. capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
  520. // Convert to MB that rbd defaults on.
  521. sz, err := volumehelpers.RoundUpToMiBInt(capacity)
  522. if err != nil {
  523. return nil, 0, err
  524. }
  525. volSz := fmt.Sprintf("%d", sz)
  526. mon := util.kernelRBDMonitorsOpt(p.Mon)
  527. if p.rbdMounter.imageFormat == rbdImageFormat2 {
  528. klog.V(4).Infof("rbd: create %s size %s format %s (features: %s) using mon %s, pool %s id %s key %s", p.rbdMounter.Image, volSz, p.rbdMounter.imageFormat, p.rbdMounter.imageFeatures, mon, p.rbdMounter.Pool, p.rbdMounter.adminId, p.rbdMounter.adminSecret)
  529. } else {
  530. klog.V(4).Infof("rbd: create %s size %s format %s using mon %s, pool %s id %s key %s", p.rbdMounter.Image, volSz, p.rbdMounter.imageFormat, mon, p.rbdMounter.Pool, p.rbdMounter.adminId, p.rbdMounter.adminSecret)
  531. }
  532. args := []string{"create", p.rbdMounter.Image, "--size", volSz, "--pool", p.rbdMounter.Pool, "--id", p.rbdMounter.adminId, "-m", mon, "--key=" + p.rbdMounter.adminSecret, "--image-format", p.rbdMounter.imageFormat}
  533. if p.rbdMounter.imageFormat == rbdImageFormat2 {
  534. // If no image features is provided, it results in empty string
  535. // which disable all RBD image format 2 features as expected.
  536. features := strings.Join(p.rbdMounter.imageFeatures, ",")
  537. args = append(args, "--image-feature", features)
  538. }
  539. output, err = p.exec.Run("rbd", args...)
  540. if err != nil {
  541. klog.Warningf("failed to create rbd image, output %v", string(output))
  542. return nil, 0, fmt.Errorf("failed to create rbd image: %v, command output: %s", err, string(output))
  543. }
  544. return &v1.RBDPersistentVolumeSource{
  545. CephMonitors: p.rbdMounter.Mon,
  546. RBDImage: p.rbdMounter.Image,
  547. RBDPool: p.rbdMounter.Pool,
  548. }, sz, nil
  549. }
  550. func (util *RBDUtil) DeleteImage(p *rbdVolumeDeleter) error {
  551. var output []byte
  552. found, rbdOutput, err := util.rbdStatus(p.rbdMounter)
  553. if err != nil {
  554. return fmt.Errorf("error %v, rbd output: %v", err, rbdOutput)
  555. }
  556. if found {
  557. klog.Infof("rbd %s is still being used ", p.rbdMounter.Image)
  558. return fmt.Errorf("rbd image %s/%s is still being used, rbd output: %v", p.rbdMounter.Pool, p.rbdMounter.Image, rbdOutput)
  559. }
  560. // rbd rm.
  561. mon := util.kernelRBDMonitorsOpt(p.rbdMounter.Mon)
  562. klog.V(4).Infof("rbd: rm %s using mon %s, pool %s id %s key %s", p.rbdMounter.Image, mon, p.rbdMounter.Pool, p.rbdMounter.adminId, p.rbdMounter.adminSecret)
  563. output, err = p.exec.Run("rbd",
  564. "rm", p.rbdMounter.Image, "--pool", p.rbdMounter.Pool, "--id", p.rbdMounter.adminId, "-m", mon, "--key="+p.rbdMounter.adminSecret)
  565. if err == nil {
  566. return nil
  567. }
  568. klog.Errorf("failed to delete rbd image: %v, command output: %s", err, string(output))
  569. return fmt.Errorf("error %v, rbd output: %v", err, string(output))
  570. }
  571. // ExpandImage runs rbd resize command to resize the specified image.
  572. func (util *RBDUtil) ExpandImage(rbdExpander *rbdVolumeExpander, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) {
  573. var output []byte
  574. var err error
  575. // Convert to MB that rbd defaults on.
  576. sz := int(volumehelpers.RoundUpToMiB(newSize))
  577. newVolSz := fmt.Sprintf("%d", sz)
  578. newSizeQuant := resource.MustParse(fmt.Sprintf("%dMi", sz))
  579. // Check the current size of rbd image, if equals to or greater that the new request size, do nothing.
  580. curSize, infoErr := util.rbdInfo(rbdExpander.rbdMounter)
  581. if infoErr != nil {
  582. return oldSize, fmt.Errorf("rbd info failed, error: %v", infoErr)
  583. }
  584. if curSize >= sz {
  585. return newSizeQuant, nil
  586. }
  587. // rbd resize.
  588. mon := util.kernelRBDMonitorsOpt(rbdExpander.rbdMounter.Mon)
  589. klog.V(4).Infof("rbd: resize %s using mon %s, pool %s id %s key %s", rbdExpander.rbdMounter.Image, mon, rbdExpander.rbdMounter.Pool, rbdExpander.rbdMounter.adminId, rbdExpander.rbdMounter.adminSecret)
  590. output, err = rbdExpander.exec.Run("rbd",
  591. "resize", rbdExpander.rbdMounter.Image, "--size", newVolSz, "--pool", rbdExpander.rbdMounter.Pool, "--id", rbdExpander.rbdMounter.adminId, "-m", mon, "--key="+rbdExpander.rbdMounter.adminSecret)
  592. if err == nil {
  593. return newSizeQuant, nil
  594. }
  595. klog.Errorf("failed to resize rbd image: %v, command output: %s", err, string(output))
  596. return oldSize, err
  597. }
  598. // rbdInfo runs `rbd info` command to get the current image size in MB.
  599. func (util *RBDUtil) rbdInfo(b *rbdMounter) (int, error) {
  600. var err error
  601. var output []byte
  602. // If we don't have admin id/secret (e.g. attaching), fallback to user id/secret.
  603. id := b.adminId
  604. secret := b.adminSecret
  605. if id == "" {
  606. id = b.Id
  607. secret = b.Secret
  608. }
  609. mon := util.kernelRBDMonitorsOpt(b.Mon)
  610. // cmd "rbd info" get the image info with the following output:
  611. //
  612. // # image exists (exit=0)
  613. // rbd info volume-4a5bcc8b-2b55-46da-ba04-0d3dc5227f08
  614. // size 1024 MB in 256 objects
  615. // order 22 (4096 kB objects)
  616. // block_name_prefix: rbd_data.1253ac238e1f29
  617. // format: 2
  618. // ...
  619. //
  620. // rbd info volume-4a5bcc8b-2b55-46da-ba04-0d3dc5227f08 --format json
  621. // {"name":"volume-4a5bcc8b-2b55-46da-ba04-0d3dc5227f08","size":1073741824,"objects":256,"order":22,"object_size":4194304,"block_name_prefix":"rbd_data.1253ac238e1f29","format":2,"features":["layering","exclusive-lock","object-map","fast-diff","deep-flatten"],"flags":[]}
  622. //
  623. //
  624. // # image does not exist (exit=2)
  625. // rbd: error opening image 1234: (2) No such file or directory
  626. //
  627. klog.V(4).Infof("rbd: info %s using mon %s, pool %s id %s key %s", b.Image, mon, b.Pool, id, secret)
  628. output, err = b.exec.Run("rbd",
  629. "info", b.Image, "--pool", b.Pool, "-m", mon, "--id", id, "--key="+secret, "--format=json")
  630. if err, ok := err.(*exec.Error); ok {
  631. if err.Err == exec.ErrNotFound {
  632. klog.Errorf("rbd cmd not found")
  633. // fail fast if rbd command is not found.
  634. return 0, err
  635. }
  636. }
  637. // If command never succeed, returns its last error.
  638. if err != nil {
  639. return 0, err
  640. }
  641. if len(output) == 0 {
  642. return 0, fmt.Errorf("can not get image size info %s: %s", b.Image, string(output))
  643. }
  644. return getRbdImageSize(output)
  645. }
  646. func getRbdImageSize(output []byte) (int, error) {
  647. info := struct {
  648. Size int64 `json:"size"`
  649. }{}
  650. if err := json.Unmarshal(output, &info); err != nil {
  651. return 0, fmt.Errorf("parse rbd info output failed: %s, %v", string(output), err)
  652. }
  653. return int(info.Size / rbdImageSizeUnitMiB), nil
  654. }
  655. // rbdStatus runs `rbd status` command to check if there is watcher on the image.
  656. func (util *RBDUtil) rbdStatus(b *rbdMounter) (bool, string, error) {
  657. var err error
  658. var output string
  659. var cmd []byte
  660. // If we don't have admin id/secret (e.g. attaching), fallback to user id/secret.
  661. id := b.adminId
  662. secret := b.adminSecret
  663. if id == "" {
  664. id = b.Id
  665. secret = b.Secret
  666. }
  667. mon := util.kernelRBDMonitorsOpt(b.Mon)
  668. // cmd "rbd status" list the rbd client watch with the following output:
  669. //
  670. // # there is a watcher (exit=0)
  671. // Watchers:
  672. // watcher=10.16.153.105:0/710245699 client.14163 cookie=1
  673. //
  674. // # there is no watcher (exit=0)
  675. // Watchers: none
  676. //
  677. // Otherwise, exit is non-zero, for example:
  678. //
  679. // # image does not exist (exit=2)
  680. // rbd: error opening image kubernetes-dynamic-pvc-<UUID>: (2) No such file or directory
  681. //
  682. klog.V(4).Infof("rbd: status %s using mon %s, pool %s id %s key %s", b.Image, mon, b.Pool, id, secret)
  683. cmd, err = b.exec.Run("rbd",
  684. "status", b.Image, "--pool", b.Pool, "-m", mon, "--id", id, "--key="+secret)
  685. output = string(cmd)
  686. if err, ok := err.(*exec.Error); ok {
  687. if err.Err == exec.ErrNotFound {
  688. klog.Errorf("rbd cmd not found")
  689. // fail fast if command not found
  690. return false, output, err
  691. }
  692. }
  693. // If command never succeed, returns its last error.
  694. if err != nil {
  695. return false, output, err
  696. }
  697. if strings.Contains(output, imageWatcherStr) {
  698. klog.V(4).Infof("rbd: watchers on %s: %s", b.Image, output)
  699. return true, output, nil
  700. } else {
  701. klog.Warningf("rbd: no watchers on %s", b.Image)
  702. return false, output, nil
  703. }
  704. }