summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/modules/vdo.py
blob: f1ea40e2e47e85f98a65152b974f8d321a491e3f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2018, Red Hat, Inc.
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = r'''
---
author:
    - Bryan Gurney (@bgurney-rh)

module: vdo

short_description: Module to control VDO


description:
    - This module controls the VDO dedupe and compression device.
    - VDO, or Virtual Data Optimizer, is a device-mapper target that
      provides inline block-level deduplication, compression, and
      thin provisioning capabilities to primary storage.

extends_documentation_fragment:
    - community.general.attributes

attributes:
    check_mode:
        support: none
    diff_mode:
        support: none

options:
    name:
        description:
            - The name of the VDO volume.
        type: str
        required: true
    state:
        description:
            - Whether this VDO volume should be "present" or "absent".
              If a "present" VDO volume does not exist, it will be
              created.  If a "present" VDO volume already exists, it
              will be modified, by updating the configuration, which
              will take effect when the VDO volume is restarted.
              Not all parameters of an existing VDO volume can be
              modified; the "statusparamkeys" list contains the
              parameters that can be modified after creation. If an
              "absent" VDO volume does not exist, it will not be
              removed.
        type: str
        choices: [ absent, present ]
        default: present
    activated:
        description:
            - The "activate" status for a VDO volume.  If this is set
              to C(false), the VDO volume cannot be started, and it will
              not start on system startup.  However, on initial
              creation, a VDO volume with "activated" set to "off"
              will be running, until stopped.  This is the default
              behavior of the "vdo create" command; it provides the
              user an opportunity to write a base amount of metadata
              (filesystem, LVM headers, etc.) to the VDO volume prior
              to stopping the volume, and leaving it deactivated
              until ready to use.
        type: bool
    running:
        description:
            - Whether this VDO volume is running.
            - A VDO volume must be activated in order to be started.
        type: bool
    device:
        description:
            - The full path of the device to use for VDO storage.
            - This is required if "state" is "present".
        type: str
    logicalsize:
        description:
            - The logical size of the VDO volume (in megabytes, or
              LVM suffix format).  If not specified for a new volume,
              this defaults to the same size as the underlying storage
              device, which is specified in the 'device' parameter.
              Existing volumes will maintain their size if the
              logicalsize parameter is not specified, or is smaller
              than or identical to the current size.  If the specified
              size is larger than the current size, a growlogical
              operation will be performed.
        type: str
    deduplication:
        description:
            - Configures whether deduplication is enabled.  The
              default for a created volume is 'enabled'.  Existing
              volumes will maintain their previously configured
              setting unless a different value is specified in the
              playbook.
        type: str
        choices: [ disabled, enabled ]
    compression:
        description:
            - Configures whether compression is enabled.  The default
              for a created volume is 'enabled'.  Existing volumes
              will maintain their previously configured setting unless
              a different value is specified in the playbook.
        type: str
        choices: [ disabled, enabled ]
    blockmapcachesize:
        description:
            - The amount of memory allocated for caching block map
              pages, in megabytes (or may be issued with an LVM-style
              suffix of K, M, G, or T).  The default (and minimum)
              value is 128M.  The value specifies the size of the
              cache; there is a 15% memory usage overhead. Each 1.25G
              of block map covers 1T of logical blocks, therefore a
              small amount of block map cache memory can cache a
              significantly large amount of block map data.  Existing
              volumes will maintain their previously configured
              setting unless a different value is specified in the
              playbook.
        type: str
    readcache:
        description:
            - Enables or disables the read cache.  The default is
              'disabled'.  Choosing 'enabled' enables a read cache
              which may improve performance for workloads of high
              deduplication, read workloads with a high level of
              compression, or on hard disk storage.  Existing
              volumes will maintain their previously configured
              setting unless a different value is specified in the
              playbook.
            - The read cache feature is available in VDO 6.1 and older.
        type: str
        choices: [ disabled, enabled ]
    readcachesize:
        description:
            - Specifies the extra VDO device read cache size in
              megabytes.  This is in addition to a system-defined
              minimum.  Using a value with a suffix of K, M, G, or T
              is optional.  The default value is 0.  1.125 MB of
              memory per bio thread will be used per 1 MB of read
              cache specified (for example, a VDO volume configured
              with 4 bio threads will have a read cache memory usage
              overhead of 4.5 MB per 1 MB of read cache specified).
              Existing volumes will maintain their previously
              configured setting unless a different value is specified
              in the playbook.
            - The read cache feature is available in VDO 6.1 and older.
        type: str
    emulate512:
        description:
            - Enables 512-byte emulation mode, allowing drivers or
              filesystems to access the VDO volume at 512-byte
              granularity, instead of the default 4096-byte granularity.
              Default is 'disabled'; only recommended when a driver
              or filesystem requires 512-byte sector level access to
              a device.  This option is only available when creating
              a new volume, and cannot be changed for an existing
              volume.
        type: bool
        default: false
    growphysical:
        description:
            - Specifies whether to attempt to execute a growphysical
              operation, if there is enough unused space on the
              device.  A growphysical operation will be executed if
              there is at least 64 GB of free space, relative to the
              previous physical size of the affected VDO volume.
        type: bool
        default: false
    slabsize:
        description:
            - The size of the increment by which the physical size of
              a VDO volume is grown, in megabytes (or may be issued
              with an LVM-style suffix of K, M, G, or T).  Must be a
              power of two between 128M and 32G.  The default is 2G,
              which supports volumes having a physical size up to 16T.
              The maximum, 32G, supports a physical size of up to 256T.
              This option is only available when creating a new
              volume, and cannot be changed for an existing volume.
        type: str
    writepolicy:
        description:
            - Specifies the write policy of the VDO volume.  The
              'sync' mode acknowledges writes only after data is on
              stable storage.  The 'async' mode acknowledges writes
              when data has been cached for writing to stable
              storage.  The default (and highly recommended) 'auto'
              mode checks the storage device to determine whether it
              supports flushes.  Devices that support flushes will
              result in a VDO volume in 'async' mode, while devices
              that do not support flushes will run in sync mode.
              Existing volumes will maintain their previously
              configured setting unless a different value is
              specified in the playbook.
        type: str
        choices: [ async, auto, sync ]
    indexmem:
        description:
            - Specifies the amount of index memory in gigabytes.  The
              default is 0.25.  The special decimal values 0.25, 0.5,
              and 0.75 can be used, as can any positive integer.
              This option is only available when creating a new
              volume, and cannot be changed for an existing volume.
        type: str
    indexmode:
        description:
            - Specifies the index mode of the Albireo index.  The
              default is 'dense', which has a deduplication window of
              1 GB of index memory per 1 TB of incoming data,
              requiring 10 GB of index data on persistent storage.
              The 'sparse' mode has a deduplication window of 1 GB of
              index memory per 10 TB of incoming data, but requires
              100 GB of index data on persistent storage.  This option
              is only available when creating a new volume, and cannot
              be changed for an existing volume.
        type: str
        choices: [ dense, sparse ]
    ackthreads:
        description:
            - Specifies the number of threads to use for
              acknowledging completion of requested VDO I/O operations.
              Valid values are integer values from 1 to 100 (lower
              numbers are preferable due to overhead).  The default is
              1.  Existing volumes will maintain their previously
              configured setting unless a different value is specified
              in the playbook.
        type: str
    biothreads:
        description:
            - Specifies the number of threads to use for submitting I/O
              operations to the storage device.  Valid values are
              integer values from 1 to 100 (lower numbers are
              preferable due to overhead).  The default is 4.
              Existing volumes will maintain their previously
              configured setting unless a different value is specified
              in the playbook.
        type: str
    cputhreads:
        description:
            - Specifies the number of threads to use for CPU-intensive
              work such as hashing or compression.  Valid values are
              integer values from 1 to 100 (lower numbers are
              preferable due to overhead).  The default is 2.
              Existing volumes will maintain their previously
              configured setting unless a different value is specified
              in the playbook.
        type: str
    logicalthreads:
        description:
            - Specifies the number of threads across which to
              subdivide parts of the VDO processing based on logical
              block addresses.  Valid values are integer values from
              1 to 100 (lower numbers are preferable due to overhead).
              The default is 1.  Existing volumes will maintain their
              previously configured setting unless a different value
              is specified in the playbook.
        type: str
    physicalthreads:
        description:
            - Specifies the number of threads across which to
              subdivide parts of the VDO processing based on physical
              block addresses.  Valid values are integer values from
              1 to 16 (lower numbers are preferable due to overhead).
              The physical space used by the VDO volume must be
              larger than (slabsize * physicalthreads).  The default
              is 1.  Existing volumes will maintain their previously
              configured setting unless a different value is specified
              in the playbook.
        type: str
    force:
        description:
            - When creating a volume, ignores any existing file system
              or VDO signature already present in the storage device.
              When stopping or removing a VDO volume, first unmounts
              the file system stored on the device if mounted.
            - "B(Warning:) Since this parameter removes all safety
              checks it is important to make sure that all parameters
              provided are accurate and intentional."
        type: bool
        default: false
        version_added: 2.4.0
notes:
  - In general, the default thread configuration should be used.
requirements:
  - PyYAML
  - kmod-kvdo
  - vdo
'''

EXAMPLES = r'''
- name: Create 2 TB VDO volume vdo1 on device /dev/md0
  community.general.vdo:
    name: vdo1
    state: present
    device: /dev/md0
    logicalsize: 2T

- name: Remove VDO volume vdo1
  community.general.vdo:
    name: vdo1
    state: absent
'''

RETURN = r'''#  '''

from ansible.module_utils.basic import AnsibleModule, missing_required_lib
import re
import traceback

YAML_IMP_ERR = None
try:
    import yaml
    HAS_YAML = True
except ImportError:
    YAML_IMP_ERR = traceback.format_exc()
    HAS_YAML = False


# Generate a list of VDO volumes, whether they are running or stopped.
#
# @param module  The AnsibleModule object.
# @param vdocmd  The path of the 'vdo' command.
#
# @return vdolist  A list of currently created VDO volumes.
def inventory_vdos(module, vdocmd):
    rc, vdostatusout, err = module.run_command([vdocmd, "status"])

    # if rc != 0:
    #   module.fail_json(msg="Inventorying VDOs failed: %s"
    #                        % vdostatusout, rc=rc, err=err)

    vdolist = []

    if rc == 2 and re.findall(r"vdoconf\.yml does not exist", err, re.MULTILINE):
        # If there is no /etc/vdoconf.yml file, assume there are no
        # VDO volumes. Return an empty list of VDO volumes.
        return vdolist

    if rc != 0:
        module.fail_json(msg="Inventorying VDOs failed: %s" % vdostatusout, rc=rc, err=err)

    vdostatusyaml = yaml.safe_load(vdostatusout)
    if vdostatusyaml is None:
        return vdolist

    vdoyamls = vdostatusyaml['VDOs']

    if vdoyamls is not None:
        vdolist = list(vdoyamls.keys())

    return vdolist


def list_running_vdos(module, vdocmd):
    rc, vdolistout, err = module.run_command([vdocmd, "list"])
    runningvdolist = filter(None, vdolistout.split('\n'))
    return runningvdolist


# Generate a string containing options to pass to the 'VDO' command.
# Note that a 'create' operation will pass more options than a
# 'modify' operation.
#
# @param params  A dictionary of parameters, and their values
#                (values of 'None' and/or nonexistent values are ignored).
#
# @return vdocmdoptions  A string to be used in a 'vdo <action>' command.
def start_vdo(module, vdoname, vdocmd):
    rc, out, err = module.run_command([vdocmd, "start", "--name=%s" % vdoname])
    if rc == 0:
        module.log("started VDO volume %s" % vdoname)
    return rc


def stop_vdo(module, vdoname, vdocmd):
    rc, out, err = module.run_command([vdocmd, "stop", "--name=%s" % vdoname])
    if rc == 0:
        module.log("stopped VDO volume %s" % vdoname)
    return rc


def activate_vdo(module, vdoname, vdocmd):
    rc, out, err = module.run_command([vdocmd, "activate", "--name=%s" % vdoname])
    if rc == 0:
        module.log("activated VDO volume %s" % vdoname)
    return rc


def deactivate_vdo(module, vdoname, vdocmd):
    rc, out, err = module.run_command([vdocmd, "deactivate", "--name=%s" % vdoname])
    if rc == 0:
        module.log("deactivated VDO volume %s" % vdoname)
    return rc


def add_vdooptions(params):
    options = []

    if params.get('logicalsize') is not None:
        options.append("--vdoLogicalSize=" + params['logicalsize'])

    if params.get('blockmapcachesize') is not None:
        options.append("--blockMapCacheSize=" + params['blockmapcachesize'])

    if params.get('readcache') == 'enabled':
        options.append("--readCache=enabled")

    if params.get('readcachesize') is not None:
        options.append("--readCacheSize=" + params['readcachesize'])

    if params.get('slabsize') is not None:
        options.append("--vdoSlabSize=" + params['slabsize'])

    if params.get('emulate512'):
        options.append("--emulate512=enabled")

    if params.get('indexmem') is not None:
        options.append("--indexMem=" + params['indexmem'])

    if params.get('indexmode') == 'sparse':
        options.append("--sparseIndex=enabled")

    if params.get('force'):
        options.append("--force")

    # Entering an invalid thread config results in a cryptic
    # 'Could not set up device mapper for %s' error from the 'vdo'
    # command execution.  The dmsetup module on the system will
    # output a more helpful message, but one would have to log
    # onto that system to read the error.  For now, heed the thread
    # limit warnings in the DOCUMENTATION section above.
    if params.get('ackthreads') is not None:
        options.append("--vdoAckThreads=" + params['ackthreads'])

    if params.get('biothreads') is not None:
        options.append("--vdoBioThreads=" + params['biothreads'])

    if params.get('cputhreads') is not None:
        options.append("--vdoCpuThreads=" + params['cputhreads'])

    if params.get('logicalthreads') is not None:
        options.append("--vdoLogicalThreads=" + params['logicalthreads'])

    if params.get('physicalthreads') is not None:
        options.append("--vdoPhysicalThreads=" + params['physicalthreads'])

    return options


def run_module():

    # Define the available arguments/parameters that a user can pass to
    # the module.
    # Defaults for VDO parameters are None, in order to facilitate
    # the detection of parameters passed from the playbook.
    # Creation param defaults are determined by the creation section.

    module_args = dict(
        name=dict(type='str', required=True),
        state=dict(type='str', default='present', choices=['absent', 'present']),
        activated=dict(type='bool'),
        running=dict(type='bool'),
        growphysical=dict(type='bool', default=False),
        device=dict(type='str'),
        logicalsize=dict(type='str'),
        deduplication=dict(type='str', choices=['disabled', 'enabled']),
        compression=dict(type='str', choices=['disabled', 'enabled']),
        blockmapcachesize=dict(type='str'),
        readcache=dict(type='str', choices=['disabled', 'enabled']),
        readcachesize=dict(type='str'),
        emulate512=dict(type='bool', default=False),
        slabsize=dict(type='str'),
        writepolicy=dict(type='str', choices=['async', 'auto', 'sync']),
        indexmem=dict(type='str'),
        indexmode=dict(type='str', choices=['dense', 'sparse']),
        ackthreads=dict(type='str'),
        biothreads=dict(type='str'),
        cputhreads=dict(type='str'),
        logicalthreads=dict(type='str'),
        physicalthreads=dict(type='str'),
        force=dict(type='bool', default=False),
    )

    # Seed the result dictionary in the object.  There will be an
    # 'invocation' dictionary added with 'module_args' (arguments
    # given).
    result = dict(
        changed=False,
    )

    # the AnsibleModule object will be our abstraction working with Ansible
    # this includes instantiation, a couple of common attr would be the
    # args/params passed to the execution, as well as if the module
    # supports check mode
    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=False,
    )

    if not HAS_YAML:
        module.fail_json(msg=missing_required_lib('PyYAML'), exception=YAML_IMP_ERR)

    vdocmd = module.get_bin_path("vdo", required=True)
    if not vdocmd:
        module.fail_json(msg='VDO is not installed.', **result)

    # Print a pre-run list of VDO volumes in the result object.
    vdolist = inventory_vdos(module, vdocmd)

    runningvdolist = list_running_vdos(module, vdocmd)

    # Collect the name of the desired VDO volume, and its state.  These will
    # determine what to do.
    desiredvdo = module.params['name']
    state = module.params['state']

    # Create a desired VDO volume that doesn't exist yet.
    if (desiredvdo not in vdolist) and (state == 'present'):
        device = module.params['device']
        if device is None:
            module.fail_json(msg="Creating a VDO volume requires specifying "
                                 "a 'device' in the playbook.")

        # Create a dictionary of the options from the AnsibleModule
        # parameters, compile the vdo command options, and run "vdo create"
        # with those options.
        # Since this is a creation of a new VDO volume, it will contain all
        # all of the parameters given by the playbook; the rest will
        # assume default values.
        vdocmdoptions = add_vdooptions(module.params)
        rc, out, err = module.run_command(
            [vdocmd, "create", "--name=%s" % desiredvdo, "--device=%s" % device] + vdocmdoptions)
        if rc == 0:
            result['changed'] = True
        else:
            module.fail_json(msg="Creating VDO %s failed." % desiredvdo, rc=rc, err=err)

        if module.params['compression'] == 'disabled':
            rc, out, err = module.run_command([vdocmd, "disableCompression", "--name=%s" % desiredvdo])

        if module.params['deduplication'] == 'disabled':
            rc, out, err = module.run_command([vdocmd, "disableDeduplication", "--name=%s" % desiredvdo])

        if module.params['activated'] is False:
            deactivate_vdo(module, desiredvdo, vdocmd)

        if module.params['running'] is False:
            stop_vdo(module, desiredvdo, vdocmd)

        # Print a post-run list of VDO volumes in the result object.
        vdolist = inventory_vdos(module, vdocmd)
        module.log("created VDO volume %s" % desiredvdo)
        module.exit_json(**result)

    # Modify the current parameters of a VDO that exists.
    if desiredvdo in vdolist and state == 'present':
        rc, vdostatusoutput, err = module.run_command([vdocmd, "status"])
        vdostatusyaml = yaml.safe_load(vdostatusoutput)

        # An empty dictionary to contain dictionaries of VDO statistics
        processedvdos = {}

        vdoyamls = vdostatusyaml['VDOs']
        if vdoyamls is not None:
            processedvdos = vdoyamls

        # The 'vdo status' keys that are currently modifiable.
        statusparamkeys = ['Acknowledgement threads',
                           'Bio submission threads',
                           'Block map cache size',
                           'CPU-work threads',
                           'Logical threads',
                           'Physical threads',
                           'Read cache',
                           'Read cache size',
                           'Configured write policy',
                           'Compression',
                           'Deduplication']

        # A key translation table from 'vdo status' output to Ansible
        # module parameters.  This covers all of the 'vdo status'
        # parameter keys that could be modified with the 'vdo'
        # command.
        vdokeytrans = {
            'Logical size': 'logicalsize',
            'Compression': 'compression',
            'Deduplication': 'deduplication',
            'Block map cache size': 'blockmapcachesize',
            'Read cache': 'readcache',
            'Read cache size': 'readcachesize',
            'Configured write policy': 'writepolicy',
            'Acknowledgement threads': 'ackthreads',
            'Bio submission threads': 'biothreads',
            'CPU-work threads': 'cputhreads',
            'Logical threads': 'logicalthreads',
            'Physical threads': 'physicalthreads'
        }

        # Build a dictionary of the current VDO status parameters, with
        # the keys used by VDO.  (These keys will be converted later.)
        currentvdoparams = {}

        # Build a "lookup table" dictionary containing a translation table
        # of the parameters that can be modified
        modtrans = {}

        for statfield in statusparamkeys:
            if statfield in processedvdos[desiredvdo]:
                currentvdoparams[statfield] = processedvdos[desiredvdo][statfield]

            modtrans[statfield] = vdokeytrans[statfield]

        # Build a dictionary of current parameters formatted with the
        # same keys as the AnsibleModule parameters.
        currentparams = {}
        for paramkey in modtrans.keys():
            currentparams[modtrans[paramkey]] = modtrans[paramkey]

        diffparams = {}

        # Check for differences between the playbook parameters and the
        # current parameters. This will need a comparison function;
        # since AnsibleModule params are all strings, compare them as
        # strings (but if it's None; skip).
        for key in currentparams.keys():
            if module.params[key] is not None:
                if str(currentparams[key]) != module.params[key]:
                    diffparams[key] = module.params[key]

        if diffparams:
            vdocmdoptions = add_vdooptions(diffparams)
            if vdocmdoptions:
                rc, out, err = module.run_command([vdocmd, "modify", "--name=%s" % desiredvdo] + vdocmdoptions)
                if rc == 0:
                    result['changed'] = True
                else:
                    module.fail_json(msg="Modifying VDO %s failed."
                                     % desiredvdo, rc=rc, err=err)

            if 'deduplication' in diffparams.keys():
                dedupemod = diffparams['deduplication']
                dedupeparam = "disableDeduplication" if dedupemod == 'disabled' else "enableDeduplication"
                rc, out, err = module.run_command([vdocmd, dedupeparam, "--name=%s" % desiredvdo])

                if rc == 0:
                    result['changed'] = True
                else:
                    module.fail_json(msg="Changing deduplication on VDO volume %s failed." % desiredvdo, rc=rc, err=err)

            if 'compression' in diffparams.keys():
                compressmod = diffparams['compression']
                compressparam = "disableCompression" if compressmod == 'disabled' else "enableCompression"
                rc, out, err = module.run_command([vdocmd, compressparam, "--name=%s" % desiredvdo])
                if rc == 0:
                    result['changed'] = True
                else:
                    module.fail_json(msg="Changing compression on VDO volume %s failed." % desiredvdo, rc=rc, err=err)

            if 'writepolicy' in diffparams.keys():
                writepolmod = diffparams['writepolicy']
                rc, out, err = module.run_command([
                    vdocmd,
                    "changeWritePolicy",
                    "--name=%s" % desiredvdo,
                    "--writePolicy=%s" % writepolmod,
                ])

                if rc == 0:
                    result['changed'] = True
                else:
                    module.fail_json(msg="Changing write policy on VDO volume %s failed." % desiredvdo, rc=rc, err=err)

        # Process the size parameters, to determine of a growPhysical or
        # growLogical operation needs to occur.
        sizeparamkeys = ['Logical size', ]

        currentsizeparams = {}
        sizetrans = {}
        for statfield in sizeparamkeys:
            currentsizeparams[statfield] = processedvdos[desiredvdo][statfield]
            sizetrans[statfield] = vdokeytrans[statfield]

        sizeparams = {}
        for paramkey in currentsizeparams.keys():
            sizeparams[sizetrans[paramkey]] = currentsizeparams[paramkey]

        diffsizeparams = {}
        for key in sizeparams.keys():
            if module.params[key] is not None and str(sizeparams[key]) != module.params[key]:
                diffsizeparams[key] = module.params[key]

        if module.params['growphysical']:
            physdevice = module.params['device']
            rc, devsectors, err = module.run_command([module.get_bin_path("blockdev"), "--getsz", physdevice])
            devblocks = (int(devsectors) / 8)
            dmvdoname = ('/dev/mapper/' + desiredvdo)
            currentvdostats = processedvdos[desiredvdo]['VDO statistics'][dmvdoname]
            currentphysblocks = currentvdostats['physical blocks']

            # Set a growPhysical threshold to grow only when there is
            # guaranteed to be more than 2 slabs worth of unallocated
            # space on the device to use.  For now, set to device
            # size + 64 GB, since 32 GB is the largest possible
            # slab size.
            growthresh = devblocks + 16777216

            if currentphysblocks > growthresh:
                result['changed'] = True
                rc, out, err = module.run_command([vdocmd, "growPhysical", "--name=%s" % desiredvdo])

        if 'logicalsize' in diffsizeparams.keys():
            result['changed'] = True
            rc, out, err = module.run_command([vdocmd, "growLogical", "--name=%s" % desiredvdo, "--vdoLogicalSize=%s" % diffsizeparams['logicalsize']])

        vdoactivatestatus = processedvdos[desiredvdo]['Activate']

        if module.params['activated'] is False and vdoactivatestatus == 'enabled':
            deactivate_vdo(module, desiredvdo, vdocmd)
            if not result['changed']:
                result['changed'] = True

        if module.params['activated'] and vdoactivatestatus == 'disabled':
            activate_vdo(module, desiredvdo, vdocmd)
            if not result['changed']:
                result['changed'] = True

        if module.params['running'] is False and desiredvdo in runningvdolist:
            stop_vdo(module, desiredvdo, vdocmd)
            if not result['changed']:
                result['changed'] = True

        # Note that a disabled VDO volume cannot be started by the
        # 'vdo start' command, by design.  To accurately track changed
        # status, don't try to start a disabled VDO volume.
        # If the playbook contains 'activated: true', assume that
        # the activate_vdo() operation succeeded, as 'vdoactivatestatus'
        # will have the activated status prior to the activate_vdo()
        # call.
        if (vdoactivatestatus == 'enabled' or module.params['activated']) and module.params['running'] and desiredvdo not in runningvdolist:
            start_vdo(module, desiredvdo, vdocmd)
            if not result['changed']:
                result['changed'] = True

        # Print a post-run list of VDO volumes in the result object.
        vdolist = inventory_vdos(module, vdocmd)
        if diffparams:
            module.log("modified parameters of VDO volume %s" % desiredvdo)

        module.exit_json(**result)

    # Remove a desired VDO that currently exists.
    if desiredvdo in vdolist and state == 'absent':
        rc, out, err = module.run_command([vdocmd, "remove", "--name=%s" % desiredvdo])
        if rc == 0:
            result['changed'] = True
        else:
            module.fail_json(msg="Removing VDO %s failed." % desiredvdo, rc=rc, err=err)

        # Print a post-run list of VDO volumes in the result object.
        vdolist = inventory_vdos(module, vdocmd)
        module.log("removed VDO volume %s" % desiredvdo)
        module.exit_json(**result)

    # fall through
    # The state for the desired VDO volume was absent, and it does
    # not exist. Print a post-run list of VDO volumes in the result
    # object.
    vdolist = inventory_vdos(module, vdocmd)
    module.log("received request to remove non-existent VDO volume %s" % desiredvdo)

    module.exit_json(**result)


def main():
    run_module()


if __name__ == '__main__':
    main()