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