rbd_util.go 28 KB

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