1""" 2Support for Linux LVM2 3""" 4 5import logging 6import os.path 7 8import salt.utils.path 9 10# Set up logger 11log = logging.getLogger(__name__) 12 13# Define the module's virtual name 14__virtualname__ = "lvm" 15 16 17def __virtual__(): 18 """ 19 Only load the module if lvm is installed 20 """ 21 if salt.utils.path.which("lvm"): 22 return __virtualname__ 23 return ( 24 False, 25 "The linux_lvm execution module cannot be loaded: the lvm binary is not in the" 26 " path.", 27 ) 28 29 30def version(): 31 """ 32 Return LVM version from lvm version 33 34 CLI Example: 35 36 .. code-block:: bash 37 38 salt '*' lvm.version 39 """ 40 cmd = "lvm version" 41 out = __salt__["cmd.run"](cmd).splitlines() 42 ret = out[0].split(": ") 43 return ret[1].strip() 44 45 46def fullversion(): 47 """ 48 Return all version info from lvm version 49 50 CLI Example: 51 52 .. code-block:: bash 53 54 salt '*' lvm.fullversion 55 """ 56 ret = {} 57 cmd = "lvm version" 58 out = __salt__["cmd.run"](cmd).splitlines() 59 for line in out: 60 comps = line.split(":") 61 ret[comps[0].strip()] = comps[1].strip() 62 return ret 63 64 65def pvdisplay(pvname="", real=False, quiet=False): 66 """ 67 Return information about the physical volume(s) 68 69 pvname 70 physical device name 71 72 real 73 dereference any symlinks and report the real device 74 75 .. versionadded:: 2015.8.7 76 77 quiet 78 if the physical volume is not present, do not show any error 79 80 CLI Examples: 81 82 .. code-block:: bash 83 84 salt '*' lvm.pvdisplay 85 salt '*' lvm.pvdisplay /dev/md0 86 """ 87 ret = {} 88 cmd = ["pvdisplay", "-c"] 89 if pvname: 90 cmd.append(pvname) 91 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False, ignore_retcode=quiet) 92 93 if cmd_ret["retcode"] != 0: 94 return {} 95 96 out = cmd_ret["stdout"].splitlines() 97 for line in out: 98 if "is a new physical volume" not in line: 99 comps = line.strip().split(":") 100 if real: 101 device = os.path.realpath(comps[0]) 102 else: 103 device = comps[0] 104 ret[device] = { 105 "Physical Volume Device": comps[0], 106 "Volume Group Name": comps[1], 107 "Physical Volume Size (kB)": comps[2], 108 "Internal Physical Volume Number": comps[3], 109 "Physical Volume Status": comps[4], 110 "Physical Volume (not) Allocatable": comps[5], 111 "Current Logical Volumes Here": comps[6], 112 "Physical Extent Size (kB)": comps[7], 113 "Total Physical Extents": comps[8], 114 "Free Physical Extents": comps[9], 115 "Allocated Physical Extents": comps[10], 116 } 117 if real: 118 ret[device]["Real Physical Volume Device"] = device 119 return ret 120 121 122def vgdisplay(vgname="", quiet=False): 123 """ 124 Return information about the volume group(s) 125 126 vgname 127 volume group name 128 129 quiet 130 if the volume group is not present, do not show any error 131 132 CLI Examples: 133 134 .. code-block:: bash 135 136 salt '*' lvm.vgdisplay 137 salt '*' lvm.vgdisplay nova-volumes 138 """ 139 ret = {} 140 cmd = ["vgdisplay", "-c"] 141 if vgname: 142 cmd.append(vgname) 143 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False, ignore_retcode=quiet) 144 145 if cmd_ret["retcode"] != 0: 146 return {} 147 148 out = cmd_ret["stdout"].splitlines() 149 for line in out: 150 comps = line.strip().split(":") 151 ret[comps[0]] = { 152 "Volume Group Name": comps[0], 153 "Volume Group Access": comps[1], 154 "Volume Group Status": comps[2], 155 "Internal Volume Group Number": comps[3], 156 "Maximum Logical Volumes": comps[4], 157 "Current Logical Volumes": comps[5], 158 "Open Logical Volumes": comps[6], 159 "Maximum Logical Volume Size": comps[7], 160 "Maximum Physical Volumes": comps[8], 161 "Current Physical Volumes": comps[9], 162 "Actual Physical Volumes": comps[10], 163 "Volume Group Size (kB)": comps[11], 164 "Physical Extent Size (kB)": comps[12], 165 "Total Physical Extents": comps[13], 166 "Allocated Physical Extents": comps[14], 167 "Free Physical Extents": comps[15], 168 "UUID": comps[16], 169 } 170 return ret 171 172 173def lvdisplay(lvname="", quiet=False): 174 """ 175 Return information about the logical volume(s) 176 177 lvname 178 logical device name 179 180 quiet 181 if the logical volume is not present, do not show any error 182 183 CLI Examples: 184 185 .. code-block:: bash 186 187 salt '*' lvm.lvdisplay 188 salt '*' lvm.lvdisplay /dev/vg_myserver/root 189 """ 190 ret = {} 191 cmd = ["lvdisplay", "-c"] 192 if lvname: 193 cmd.append(lvname) 194 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False, ignore_retcode=quiet) 195 196 if cmd_ret["retcode"] != 0: 197 return {} 198 199 out = cmd_ret["stdout"].splitlines() 200 for line in out: 201 comps = line.strip().split(":") 202 ret[comps[0]] = { 203 "Logical Volume Name": comps[0], 204 "Volume Group Name": comps[1], 205 "Logical Volume Access": comps[2], 206 "Logical Volume Status": comps[3], 207 "Internal Logical Volume Number": comps[4], 208 "Open Logical Volumes": comps[5], 209 "Logical Volume Size": comps[6], 210 "Current Logical Extents Associated": comps[7], 211 "Allocated Logical Extents": comps[8], 212 "Allocation Policy": comps[9], 213 "Read Ahead Sectors": comps[10], 214 "Major Device Number": comps[11], 215 "Minor Device Number": comps[12], 216 } 217 return ret 218 219 220def pvcreate(devices, override=True, force=True, **kwargs): 221 """ 222 Set a physical device to be used as an LVM physical volume 223 224 override 225 Skip devices, if they are already LVM physical volumes 226 227 CLI Examples: 228 229 .. code-block:: bash 230 231 salt mymachine lvm.pvcreate /dev/sdb1,/dev/sdb2 232 salt mymachine lvm.pvcreate /dev/sdb1 dataalignmentoffset=7s 233 """ 234 if not devices: 235 return "Error: at least one device is required" 236 if isinstance(devices, str): 237 devices = devices.split(",") 238 239 cmd = ["pvcreate"] 240 241 if force: 242 cmd.append("--yes") 243 else: 244 cmd.append("-qq") 245 246 for device in devices: 247 if not os.path.exists(device): 248 return "{} does not exist".format(device) 249 if not pvdisplay(device, quiet=True): 250 cmd.append(device) 251 elif not override: 252 return 'Device "{}" is already an LVM physical volume.'.format(device) 253 254 if not cmd[2:]: 255 # All specified devices are already LVM volumes 256 return True 257 258 valid = ( 259 "metadatasize", 260 "dataalignment", 261 "dataalignmentoffset", 262 "pvmetadatacopies", 263 "metadatacopies", 264 "metadataignore", 265 "restorefile", 266 "norestorefile", 267 "labelsector", 268 "setphysicalvolumesize", 269 ) 270 no_parameter = "norestorefile" 271 for var in kwargs: 272 if kwargs[var] and var in valid: 273 cmd.extend(["--{}".format(var), kwargs[var]]) 274 elif kwargs[var] and var in no_parameter: 275 cmd.append("--{}".format(var)) 276 277 out = __salt__["cmd.run_all"](cmd, python_shell=False) 278 if out.get("retcode"): 279 return out.get("stderr") 280 281 # Verify pvcreate was successful 282 for device in devices: 283 if not pvdisplay(device): 284 return 'Device "{}" was not affected.'.format(device) 285 286 return True 287 288 289def pvremove(devices, override=True, force=True): 290 """ 291 Remove a physical device being used as an LVM physical volume 292 293 override 294 Skip devices, if they are already not used as LVM physical volumes 295 296 CLI Examples: 297 298 .. code-block:: bash 299 300 salt mymachine lvm.pvremove /dev/sdb1,/dev/sdb2 301 """ 302 if isinstance(devices, str): 303 devices = devices.split(",") 304 305 cmd = ["pvremove"] 306 307 if force: 308 cmd.append("--yes") 309 else: 310 cmd.append("-qq") 311 312 for device in devices: 313 if pvdisplay(device): 314 cmd.append(device) 315 elif not override: 316 return "{} is not a physical volume".format(device) 317 318 if not cmd[2:]: 319 # Nothing to do 320 return True 321 322 out = __salt__["cmd.run_all"](cmd, python_shell=False) 323 if out.get("retcode"): 324 return out.get("stderr") 325 326 # Verify pvremove was successful 327 for device in devices: 328 if pvdisplay(device, quiet=True): 329 return 'Device "{}" was not affected.'.format(device) 330 331 return True 332 333 334def vgcreate(vgname, devices, force=False, **kwargs): 335 """ 336 Create an LVM volume group 337 338 CLI Examples: 339 340 .. code-block:: bash 341 342 salt mymachine lvm.vgcreate my_vg /dev/sdb1,/dev/sdb2 343 salt mymachine lvm.vgcreate my_vg /dev/sdb1 clustered=y 344 """ 345 if not vgname or not devices: 346 return "Error: vgname and device(s) are both required" 347 if isinstance(devices, str): 348 devices = devices.split(",") 349 350 cmd = ["vgcreate", vgname] 351 for device in devices: 352 cmd.append(device) 353 354 if force: 355 cmd.append("--yes") 356 else: 357 cmd.append("-qq") 358 359 valid = ( 360 "addtag", 361 "alloc", 362 "autobackup", 363 "clustered", 364 "maxlogicalvolumes", 365 "maxphysicalvolumes", 366 "metadatatype", 367 "vgmetadatacopies", 368 "metadatacopies", 369 "physicalextentsize", 370 "zero", 371 ) 372 for var in kwargs: 373 if kwargs[var] and var in valid: 374 cmd.append("--{}".format(var)) 375 cmd.append(kwargs[var]) 376 377 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False) 378 if cmd_ret.get("retcode"): 379 out = cmd_ret.get("stderr").strip() 380 else: 381 out = 'Volume group "{}" successfully created'.format(vgname) 382 383 vgdata = vgdisplay(vgname) 384 vgdata["Output from vgcreate"] = out 385 return vgdata 386 387 388def vgextend(vgname, devices, force=False): 389 """ 390 Add physical volumes to an LVM volume group 391 392 CLI Examples: 393 394 .. code-block:: bash 395 396 salt mymachine lvm.vgextend my_vg /dev/sdb1,/dev/sdb2 397 salt mymachine lvm.vgextend my_vg /dev/sdb1 398 """ 399 if not vgname or not devices: 400 return "Error: vgname and device(s) are both required" 401 if isinstance(devices, str): 402 devices = devices.split(",") 403 404 cmd = ["vgextend", vgname] 405 406 if force: 407 cmd.append("--yes") 408 else: 409 cmd.append("-qq") 410 411 for device in devices: 412 cmd.append(device) 413 414 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False) 415 if cmd_ret.get("retcode"): 416 out = cmd_ret.get("stderr").strip() 417 else: 418 out = 'Volume group "{}" successfully extended'.format(vgname) 419 420 vgdata = {"Output from vgextend": out} 421 return vgdata 422 423 424def lvcreate( 425 lvname, 426 vgname, 427 size=None, 428 extents=None, 429 snapshot=None, 430 pv=None, 431 thinvolume=False, 432 thinpool=False, 433 force=False, 434 **kwargs 435): 436 """ 437 Create a new logical volume, with option for which physical volume to be used 438 439 CLI Examples: 440 441 .. code-block:: bash 442 443 salt '*' lvm.lvcreate new_volume_name vg_name size=10G 444 salt '*' lvm.lvcreate new_volume_name vg_name extents=100 pv=/dev/sdb 445 salt '*' lvm.lvcreate new_snapshot vg_name snapshot=volume_name size=3G 446 447 .. versionadded:: 0.12.0 448 449 Support for thin pools and thin volumes 450 451 CLI Examples: 452 453 .. code-block:: bash 454 455 salt '*' lvm.lvcreate new_thinpool_name vg_name size=20G thinpool=True 456 salt '*' lvm.lvcreate new_thinvolume_name vg_name/thinpool_name size=10G thinvolume=True 457 458 """ 459 if size and extents: 460 return "Error: Please specify only one of size or extents" 461 if thinvolume and thinpool: 462 return "Error: Please set only one of thinvolume or thinpool to True" 463 464 valid = ( 465 "activate", 466 "chunksize", 467 "contiguous", 468 "discards", 469 "stripes", 470 "stripesize", 471 "minor", 472 "persistent", 473 "mirrors", 474 "nosync", 475 "noudevsync", 476 "monitor", 477 "ignoremonitoring", 478 "permission", 479 "poolmetadatasize", 480 "readahead", 481 "regionsize", 482 "type", 483 "virtualsize", 484 "zero", 485 ) 486 no_parameter = ( 487 "nosync", 488 "noudevsync", 489 "ignoremonitoring", 490 "thin", 491 ) 492 493 extra_arguments = [] 494 if kwargs: 495 for k, v in kwargs.items(): 496 if k in no_parameter: 497 extra_arguments.append("--{}".format(k)) 498 elif k in valid: 499 extra_arguments.extend(["--{}".format(k), "{}".format(v)]) 500 501 cmd = [salt.utils.path.which("lvcreate")] 502 503 if thinvolume: 504 cmd.extend(["--thin", "-n", lvname]) 505 elif thinpool: 506 cmd.extend(["--thinpool", lvname]) 507 else: 508 cmd.extend(["-n", lvname]) 509 510 if snapshot: 511 cmd.extend(["-s", "{}/{}".format(vgname, snapshot)]) 512 else: 513 cmd.append(vgname) 514 515 if size and thinvolume: 516 cmd.extend(["-V", "{}".format(size)]) 517 elif extents and thinvolume: 518 return "Error: Thin volume size cannot be specified as extents" 519 elif size: 520 cmd.extend(["-L", "{}".format(size)]) 521 elif extents: 522 cmd.extend(["-l", "{}".format(extents)]) 523 else: 524 return "Error: Either size or extents must be specified" 525 526 if pv: 527 cmd.append(pv) 528 if extra_arguments: 529 cmd.extend(extra_arguments) 530 531 if force: 532 cmd.append("--yes") 533 else: 534 cmd.append("-qq") 535 536 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False) 537 if cmd_ret.get("retcode"): 538 out = cmd_ret.get("stderr").strip() 539 else: 540 out = 'Logical volume "{}" created.'.format(lvname) 541 542 lvdev = "/dev/{}/{}".format(vgname, lvname) 543 lvdata = lvdisplay(lvdev) 544 lvdata["Output from lvcreate"] = out 545 return lvdata 546 547 548def vgremove(vgname, force=True): 549 """ 550 Remove an LVM volume group 551 552 CLI Examples: 553 554 .. code-block:: bash 555 556 salt mymachine lvm.vgremove vgname 557 salt mymachine lvm.vgremove vgname force=True 558 """ 559 cmd = ["vgremove", vgname] 560 561 if force: 562 cmd.append("--yes") 563 else: 564 cmd.append("-qq") 565 566 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False) 567 if cmd_ret.get("retcode"): 568 out = cmd_ret.get("stderr").strip() 569 else: 570 out = 'Volume group "{}" successfully removed'.format(vgname) 571 return out 572 573 574def lvremove(lvname, vgname, force=True): 575 """ 576 Remove a given existing logical volume from a named existing volume group 577 578 CLI Example: 579 580 .. code-block:: bash 581 582 salt '*' lvm.lvremove lvname vgname force=True 583 """ 584 cmd = ["lvremove", "{}/{}".format(vgname, lvname)] 585 586 if force: 587 cmd.append("--yes") 588 else: 589 cmd.append("-qq") 590 591 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False) 592 if cmd_ret.get("retcode"): 593 out = cmd_ret.get("stderr").strip() 594 else: 595 out = 'Logical volume "{}" successfully removed'.format(lvname) 596 597 return out 598 599 600def lvresize(size=None, lvpath=None, extents=None, force=False, resizefs=False): 601 """ 602 Resize a logical volume to specific size. 603 604 CLI Examples: 605 606 .. code-block:: bash 607 608 609 salt '*' lvm.lvresize +12M /dev/mapper/vg1-test 610 salt '*' lvm.lvresize lvpath=/dev/mapper/vg1-test extents=+100%FREE 611 612 """ 613 if size and extents: 614 log.error("Error: Please specify only one of size or extents") 615 return {} 616 617 cmd = ["lvresize"] 618 619 if force: 620 cmd.append("--force") 621 else: 622 cmd.append("-qq") 623 624 if resizefs: 625 cmd.append("--resizefs") 626 627 if size: 628 cmd.extend(["-L", "{}".format(size)]) 629 elif extents: 630 cmd.extend(["-l", "{}".format(extents)]) 631 else: 632 log.error("Error: Either size or extents must be specified") 633 return {} 634 635 cmd.append(lvpath) 636 637 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False) 638 if cmd_ret.get("retcode"): 639 out = cmd_ret.get("stderr").strip() 640 else: 641 out = 'Logical volume "{}" successfully resized.'.format(lvpath) 642 643 return {"Output from lvresize": out} 644 645 646def lvextend(size=None, lvpath=None, extents=None, force=False, resizefs=False): 647 """ 648 Increase a logical volume to specific size. 649 650 CLI Examples: 651 652 .. code-block:: bash 653 654 655 salt '*' lvm.lvextend +12M /dev/mapper/vg1-test 656 salt '*' lvm.lvextend lvpath=/dev/mapper/vg1-test extents=+100%FREE 657 658 """ 659 if size and extents: 660 log.error("Error: Please specify only one of size or extents") 661 return {} 662 663 cmd = ["lvextend"] 664 665 if force: 666 cmd.append("--yes") 667 else: 668 cmd.append("-qq") 669 670 if resizefs: 671 cmd.append("--resizefs") 672 673 if size: 674 cmd.extend(["-L", "{}".format(size)]) 675 elif extents: 676 cmd.extend(["-l", "{}".format(extents)]) 677 else: 678 log.error("Error: Either size or extents must be specified") 679 return {} 680 681 cmd.append(lvpath) 682 683 cmd_ret = __salt__["cmd.run_all"](cmd, python_shell=False) 684 if cmd_ret.get("retcode"): 685 out = cmd_ret.get("stderr").strip() 686 else: 687 out = 'Logical volume "{}" successfully extended.'.format(lvpath) 688 689 return {"Output from lvextend": out} 690 691 692def pvresize(devices, override=True, force=True): 693 """ 694 Resize a LVM physical volume to the physical device size 695 696 override 697 Skip devices, if they are already not used as LVM physical volumes 698 699 CLI Examples: 700 701 .. code-block:: bash 702 703 salt mymachine lvm.pvresize /dev/sdb1,/dev/sdb2 704 """ 705 if isinstance(devices, str): 706 devices = devices.split(",") 707 708 cmd = ["pvresize"] 709 710 if force: 711 cmd.append("--yes") 712 else: 713 cmd.append("-qq") 714 715 for device in devices: 716 if pvdisplay(device): 717 cmd.append(device) 718 elif not override: 719 return "{} is not a physical volume".format(device) 720 721 if not cmd[2:]: 722 # Nothing to do 723 return True 724 725 out = __salt__["cmd.run_all"](cmd, python_shell=False) 726 if out.get("retcode"): 727 return out.get("stderr") 728 729 return True 730