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