We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
lvg fails if pvs option omitted when state=absent
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

`lvg` module
##### ANSIBLE VERSION

```
ansible 2.1.2.0
  config file = /Users/jsok/workspace/ansible.cfg
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

N/A
##### OS / ENVIRONMENT

CentOS 6.7
##### SUMMARY

The `pvs` option is not necessary when `state=absent`, however failing to supply an empty string will cause the module to fail.
##### STEPS TO REPRODUCE

```

---
- name: Remove a volume group
  hosts: localhost
  tasks:
    - name: Remove vg01
      lvg:
        vg: vg01
        state: absent
```
##### EXPECTED RESULTS

The volume group is removed successfully.
##### ACTUAL RESULTS

```
fatal: [localhost]: FAILED! => {"changed": false, "failed": true, "module_stderr": "", "module_stdout": "Traceback (most recent call last):\r\n  File \"/tmp/ansible_tj_9JM/ansible_module_lvg.py\", line 255, in <module>\r\n    main()\r\n  File \"/tmp/ansible_tj_9JM/ansible_module_lvg.py\", line 140, in main\r\n    for idx, dev in enumerate(dev_list):\r\nUnboundLocalError: local variable 'dev_list' referenced before assignment\r\n", "msg": "MODULE FAILURE"}
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `system/lvg.py`
Content:
```
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 
4 # (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
5 # based on lvol module by Jeroen Hoekx <jeroen.hoekx@dsquare.be>
6 #
7 # This file is part of Ansible
8 #
9 # Ansible is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # Ansible is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
21 
22 DOCUMENTATION = '''
23 ---
24 author: "Alexander Bulimov (@abulimov)"
25 module: lvg
26 short_description: Configure LVM volume groups
27 description:
28   - This module creates, removes or resizes volume groups.
29 version_added: "1.1"
30 options:
31   vg:
32     description:
33     - The name of the volume group.
34     required: true
35   pvs:
36     description:
37     - List of comma-separated devices to use as physical devices in this volume group. Required when creating or resizing volume group.
38     - The module will take care of running pvcreate if needed. 
39     required: false
40   pesize:
41     description:
42     - The size of the physical extent in megabytes. Must be a power of 2.
43     default: 4
44     required: false
45   vg_options:
46     description:
47     - Additional options to pass to C(vgcreate) when creating the volume group.
48     default: null
49     required: false
50     version_added: "1.6"
51   state:
52     choices: [ "present", "absent" ]
53     default: present
54     description:
55     - Control if the volume group exists.
56     required: false
57   force:
58     choices: [ "yes", "no" ]
59     default: "no"
60     description:
61     - If yes, allows to remove volume group with logical volumes.
62     required: false
63 notes:
64   - module does not modify PE size for already present volume group
65 '''
66 
67 EXAMPLES = '''
68 # Create a volume group on top of /dev/sda1 with physical extent size = 32MB.
69 - lvg:  vg=vg.services pvs=/dev/sda1 pesize=32
70 
71 # Create or resize a volume group on top of /dev/sdb1 and /dev/sdc5.
72 # If, for example, we already have VG vg.services on top of /dev/sdb1,
73 # this VG will be extended by /dev/sdc5.  Or if vg.services was created on
74 # top of /dev/sda5, we first extend it with /dev/sdb1 and /dev/sdc5,
75 # and then reduce by /dev/sda5.
76 - lvg: vg=vg.services pvs=/dev/sdb1,/dev/sdc5
77 
78 # Remove a volume group with name vg.services.
79 - lvg: vg=vg.services state=absent
80 '''
81 
82 def parse_vgs(data):
83     vgs = []
84     for line in data.splitlines():
85         parts = line.strip().split(';')
86         vgs.append({
87             'name': parts[0],
88             'pv_count': int(parts[1]),
89             'lv_count': int(parts[2]),
90         })
91     return vgs
92 
93 def find_mapper_device_name(module, dm_device):
94         dmsetup_cmd = module.get_bin_path('dmsetup', True)
95         mapper_prefix = '/dev/mapper/'
96         rc, dm_name, err = module.run_command("%s info -C --noheadings -o name %s" % (dmsetup_cmd, dm_device))
97         if rc != 0:
98             module.fail_json(msg="Failed executing dmsetup command.", rc=rc, err=err)
99         mapper_device = mapper_prefix + dm_name.rstrip()
100         return mapper_device
101 
102 def parse_pvs(module, data):
103     pvs = []
104     dm_prefix = '/dev/dm-'
105     for line in data.splitlines():
106         parts = line.strip().split(';')
107         if parts[0].startswith(dm_prefix):
108             parts[0] = find_mapper_device_name(module, parts[0])
109         pvs.append({
110             'name': parts[0],
111             'vg_name': parts[1],
112         })
113     return pvs
114 
115 def main():
116     module = AnsibleModule(
117         argument_spec = dict(
118             vg=dict(required=True),
119             pvs=dict(type='list'),
120             pesize=dict(type='int', default=4),
121             vg_options=dict(default=''),
122             state=dict(choices=["absent", "present"], default='present'),
123             force=dict(type='bool', default='no'),
124         ),
125         supports_check_mode=True,
126     )
127 
128     vg = module.params['vg']
129     state = module.params['state']
130     force = module.boolean(module.params['force'])
131     pesize = module.params['pesize']
132     vgoptions = module.params['vg_options'].split()
133 
134     if module.params['pvs']:
135         dev_list = module.params['pvs']
136     elif state == 'present':
137         module.fail_json(msg="No physical volumes given.")
138 
139     # LVM always uses real paths not symlinks so replace symlinks with actual path
140     for idx, dev in enumerate(dev_list):
141         dev_list[idx] = os.path.realpath(dev)
142 
143     if state=='present':
144         ### check given devices
145         for test_dev in dev_list:
146             if not os.path.exists(test_dev):
147                 module.fail_json(msg="Device %s not found."%test_dev)
148 
149         ### get pv list
150         pvs_cmd = module.get_bin_path('pvs', True)
151         rc,current_pvs,err = module.run_command("%s --noheadings -o pv_name,vg_name --separator ';'" % pvs_cmd)
152         if rc != 0:
153             module.fail_json(msg="Failed executing pvs command.",rc=rc, err=err)
154 
155         ### check pv for devices
156         pvs = parse_pvs(module, current_pvs)
157         used_pvs = [ pv for pv in pvs if pv['name'] in dev_list and pv['vg_name'] and pv['vg_name'] != vg ]
158         if used_pvs:
159             module.fail_json(msg="Device %s is already in %s volume group."%(used_pvs[0]['name'],used_pvs[0]['vg_name']))
160 
161     vgs_cmd = module.get_bin_path('vgs', True)
162     rc,current_vgs,err = module.run_command("%s --noheadings -o vg_name,pv_count,lv_count --separator ';'" % vgs_cmd)
163 
164     if rc != 0:
165         module.fail_json(msg="Failed executing vgs command.",rc=rc, err=err)
166 
167     changed = False
168 
169     vgs = parse_vgs(current_vgs)
170 
171     for test_vg in vgs:
172         if test_vg['name'] == vg:
173             this_vg = test_vg
174             break
175     else:
176         this_vg = None
177 
178     if this_vg is None:
179         if state == 'present':
180             ### create VG
181             if module.check_mode:
182                 changed = True
183             else:
184                 ### create PV
185                 pvcreate_cmd = module.get_bin_path('pvcreate', True)
186                 for current_dev in dev_list:
187                     rc,_,err = module.run_command("%s -f %s" % (pvcreate_cmd,current_dev))
188                     if rc == 0:
189                         changed = True
190                     else:
191                         module.fail_json(msg="Creating physical volume '%s' failed" % current_dev, rc=rc, err=err)
192                 vgcreate_cmd = module.get_bin_path('vgcreate')
193                 rc,_,err = module.run_command([vgcreate_cmd] + vgoptions + ['-s', str(pesize), vg] + dev_list)
194                 if rc == 0:
195                     changed = True
196                 else:
197                     module.fail_json(msg="Creating volume group '%s' failed"%vg, rc=rc, err=err)
198     else:
199         if state == 'absent':
200             if module.check_mode:
201                 module.exit_json(changed=True)
202             else:
203                 if this_vg['lv_count'] == 0 or force:
204                     ### remove VG
205                     vgremove_cmd = module.get_bin_path('vgremove', True)
206                     rc,_,err = module.run_command("%s --force %s" % (vgremove_cmd, vg))
207                     if rc == 0:
208                         module.exit_json(changed=True)
209                     else:
210                         module.fail_json(msg="Failed to remove volume group %s"%(vg),rc=rc, err=err)
211                 else:
212                     module.fail_json(msg="Refuse to remove non-empty volume group %s without force=yes"%(vg))
213 
214         ### resize VG
215         current_devs = [ os.path.realpath(pv['name']) for pv in pvs if pv['vg_name'] == vg ]
216         devs_to_remove = list(set(current_devs) - set(dev_list))
217         devs_to_add = list(set(dev_list) - set(current_devs))
218 
219         if devs_to_add or devs_to_remove:
220             if module.check_mode:
221                 changed = True
222             else:
223                 if devs_to_add:
224                     devs_to_add_string = ' '.join(devs_to_add)
225                     ### create PV
226                     pvcreate_cmd = module.get_bin_path('pvcreate', True)
227                     for current_dev in devs_to_add:
228                         rc,_,err = module.run_command("%s -f %s" % (pvcreate_cmd, current_dev))
229                         if rc == 0:
230                             changed = True
231                         else:
232                             module.fail_json(msg="Creating physical volume '%s' failed"%current_dev, rc=rc, err=err)
233                     ### add PV to our VG
234                     vgextend_cmd = module.get_bin_path('vgextend', True)
235                     rc,_,err = module.run_command("%s %s %s" % (vgextend_cmd, vg, devs_to_add_string))
236                     if rc == 0:
237                         changed = True
238                     else:
239                         module.fail_json(msg="Unable to extend %s by %s."%(vg, devs_to_add_string),rc=rc,err=err)
240 
241                 ### remove some PV from our VG
242                 if devs_to_remove:
243                     devs_to_remove_string = ' '.join(devs_to_remove)
244                     vgreduce_cmd = module.get_bin_path('vgreduce', True)
245                     rc,_,err = module.run_command("%s --force %s %s" % (vgreduce_cmd, vg, devs_to_remove_string))
246                     if rc == 0:
247                         changed = True
248                     else:
249                         module.fail_json(msg="Unable to reduce %s by %s."%(vg, devs_to_remove_string),rc=rc,err=err)
250 
251     module.exit_json(changed=changed)
252 
253 # import module snippets
254 from ansible.module_utils.basic import *
255 main()
256
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/system/lvg.py b/system/lvg.py
--- a/system/lvg.py
+++ b/system/lvg.py
@@ -131,6 +131,7 @@
     pesize = module.params['pesize']
     vgoptions = module.params['vg_options'].split()
 
+    dev_list = []
     if module.params['pvs']:
         dev_list = module.params['pvs']
     elif state == 'present':

{"golden_diff": "diff --git a/system/lvg.py b/system/lvg.py\n--- a/system/lvg.py\n+++ b/system/lvg.py\n@@ -131,6 +131,7 @@\n     pesize = module.params['pesize']\n     vgoptions = module.params['vg_options'].split()\n \n+    dev_list = []\n     if module.params['pvs']:\n         dev_list = module.params['pvs']\n     elif state == 'present':\n", "issue": "lvg fails if pvs option omitted when state=absent\n##### ISSUE TYPE\n- Bug Report\n##### COMPONENT NAME\n\n`lvg` module\n##### ANSIBLE VERSION\n\n```\nansible 2.1.2.0\n  config file = /Users/jsok/workspace/ansible.cfg\n  configured module search path = Default w/o overrides\n```\n##### CONFIGURATION\n\nN/A\n##### OS / ENVIRONMENT\n\nCentOS 6.7\n##### SUMMARY\n\nThe `pvs` option is not necessary when `state=absent`, however failing to supply an empty string will cause the module to fail.\n##### STEPS TO REPRODUCE\n\n```\n\n---\n- name: Remove a volume group\n  hosts: localhost\n  tasks:\n    - name: Remove vg01\n      lvg:\n        vg: vg01\n        state: absent\n```\n##### EXPECTED RESULTS\n\nThe volume group is removed successfully.\n##### ACTUAL RESULTS\n\n```\nfatal: [localhost]: FAILED! => {\"changed\": false, \"failed\": true, \"module_stderr\": \"\", \"module_stdout\": \"Traceback (most recent call last):\\r\\n  File \\\"/tmp/ansible_tj_9JM/ansible_module_lvg.py\\\", line 255, in <module>\\r\\n    main()\\r\\n  File \\\"/tmp/ansible_tj_9JM/ansible_module_lvg.py\\\", line 140, in main\\r\\n    for idx, dev in enumerate(dev_list):\\r\\nUnboundLocalError: local variable 'dev_list' referenced before assignment\\r\\n\", \"msg\": \"MODULE FAILURE\"}\n```\n\n", "before_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>\n# based on lvol module by Jeroen Hoekx <jeroen.hoekx@dsquare.be>\n#\n# This file is part of Ansible\n#\n# Ansible is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Ansible is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.\n\nDOCUMENTATION = '''\n---\nauthor: \"Alexander Bulimov (@abulimov)\"\nmodule: lvg\nshort_description: Configure LVM volume groups\ndescription:\n  - This module creates, removes or resizes volume groups.\nversion_added: \"1.1\"\noptions:\n  vg:\n    description:\n    - The name of the volume group.\n    required: true\n  pvs:\n    description:\n    - List of comma-separated devices to use as physical devices in this volume group. Required when creating or resizing volume group.\n    - The module will take care of running pvcreate if needed. \n    required: false\n  pesize:\n    description:\n    - The size of the physical extent in megabytes. Must be a power of 2.\n    default: 4\n    required: false\n  vg_options:\n    description:\n    - Additional options to pass to C(vgcreate) when creating the volume group.\n    default: null\n    required: false\n    version_added: \"1.6\"\n  state:\n    choices: [ \"present\", \"absent\" ]\n    default: present\n    description:\n    - Control if the volume group exists.\n    required: false\n  force:\n    choices: [ \"yes\", \"no\" ]\n    default: \"no\"\n    description:\n    - If yes, allows to remove volume group with logical volumes.\n    required: false\nnotes:\n  - module does not modify PE size for already present volume group\n'''\n\nEXAMPLES = '''\n# Create a volume group on top of /dev/sda1 with physical extent size = 32MB.\n- lvg:  vg=vg.services pvs=/dev/sda1 pesize=32\n\n# Create or resize a volume group on top of /dev/sdb1 and /dev/sdc5.\n# If, for example, we already have VG vg.services on top of /dev/sdb1,\n# this VG will be extended by /dev/sdc5.  Or if vg.services was created on\n# top of /dev/sda5, we first extend it with /dev/sdb1 and /dev/sdc5,\n# and then reduce by /dev/sda5.\n- lvg: vg=vg.services pvs=/dev/sdb1,/dev/sdc5\n\n# Remove a volume group with name vg.services.\n- lvg: vg=vg.services state=absent\n'''\n\ndef parse_vgs(data):\n    vgs = []\n    for line in data.splitlines():\n        parts = line.strip().split(';')\n        vgs.append({\n            'name': parts[0],\n            'pv_count': int(parts[1]),\n            'lv_count': int(parts[2]),\n        })\n    return vgs\n\ndef find_mapper_device_name(module, dm_device):\n        dmsetup_cmd = module.get_bin_path('dmsetup', True)\n        mapper_prefix = '/dev/mapper/'\n        rc, dm_name, err = module.run_command(\"%s info -C --noheadings -o name %s\" % (dmsetup_cmd, dm_device))\n        if rc != 0:\n            module.fail_json(msg=\"Failed executing dmsetup command.\", rc=rc, err=err)\n        mapper_device = mapper_prefix + dm_name.rstrip()\n        return mapper_device\n\ndef parse_pvs(module, data):\n    pvs = []\n    dm_prefix = '/dev/dm-'\n    for line in data.splitlines():\n        parts = line.strip().split(';')\n        if parts[0].startswith(dm_prefix):\n            parts[0] = find_mapper_device_name(module, parts[0])\n        pvs.append({\n            'name': parts[0],\n            'vg_name': parts[1],\n        })\n    return pvs\n\ndef main():\n    module = AnsibleModule(\n        argument_spec = dict(\n            vg=dict(required=True),\n            pvs=dict(type='list'),\n            pesize=dict(type='int', default=4),\n            vg_options=dict(default=''),\n            state=dict(choices=[\"absent\", \"present\"], default='present'),\n            force=dict(type='bool', default='no'),\n        ),\n        supports_check_mode=True,\n    )\n\n    vg = module.params['vg']\n    state = module.params['state']\n    force = module.boolean(module.params['force'])\n    pesize = module.params['pesize']\n    vgoptions = module.params['vg_options'].split()\n\n    if module.params['pvs']:\n        dev_list = module.params['pvs']\n    elif state == 'present':\n        module.fail_json(msg=\"No physical volumes given.\")\n\n    # LVM always uses real paths not symlinks so replace symlinks with actual path\n    for idx, dev in enumerate(dev_list):\n        dev_list[idx] = os.path.realpath(dev)\n\n    if state=='present':\n        ### check given devices\n        for test_dev in dev_list:\n            if not os.path.exists(test_dev):\n                module.fail_json(msg=\"Device %s not found.\"%test_dev)\n\n        ### get pv list\n        pvs_cmd = module.get_bin_path('pvs', True)\n        rc,current_pvs,err = module.run_command(\"%s --noheadings -o pv_name,vg_name --separator ';'\" % pvs_cmd)\n        if rc != 0:\n            module.fail_json(msg=\"Failed executing pvs command.\",rc=rc, err=err)\n\n        ### check pv for devices\n        pvs = parse_pvs(module, current_pvs)\n        used_pvs = [ pv for pv in pvs if pv['name'] in dev_list and pv['vg_name'] and pv['vg_name'] != vg ]\n        if used_pvs:\n            module.fail_json(msg=\"Device %s is already in %s volume group.\"%(used_pvs[0]['name'],used_pvs[0]['vg_name']))\n\n    vgs_cmd = module.get_bin_path('vgs', True)\n    rc,current_vgs,err = module.run_command(\"%s --noheadings -o vg_name,pv_count,lv_count --separator ';'\" % vgs_cmd)\n\n    if rc != 0:\n        module.fail_json(msg=\"Failed executing vgs command.\",rc=rc, err=err)\n\n    changed = False\n\n    vgs = parse_vgs(current_vgs)\n\n    for test_vg in vgs:\n        if test_vg['name'] == vg:\n            this_vg = test_vg\n            break\n    else:\n        this_vg = None\n\n    if this_vg is None:\n        if state == 'present':\n            ### create VG\n            if module.check_mode:\n                changed = True\n            else:\n                ### create PV\n                pvcreate_cmd = module.get_bin_path('pvcreate', True)\n                for current_dev in dev_list:\n                    rc,_,err = module.run_command(\"%s -f %s\" % (pvcreate_cmd,current_dev))\n                    if rc == 0:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"Creating physical volume '%s' failed\" % current_dev, rc=rc, err=err)\n                vgcreate_cmd = module.get_bin_path('vgcreate')\n                rc,_,err = module.run_command([vgcreate_cmd] + vgoptions + ['-s', str(pesize), vg] + dev_list)\n                if rc == 0:\n                    changed = True\n                else:\n                    module.fail_json(msg=\"Creating volume group '%s' failed\"%vg, rc=rc, err=err)\n    else:\n        if state == 'absent':\n            if module.check_mode:\n                module.exit_json(changed=True)\n            else:\n                if this_vg['lv_count'] == 0 or force:\n                    ### remove VG\n                    vgremove_cmd = module.get_bin_path('vgremove', True)\n                    rc,_,err = module.run_command(\"%s --force %s\" % (vgremove_cmd, vg))\n                    if rc == 0:\n                        module.exit_json(changed=True)\n                    else:\n                        module.fail_json(msg=\"Failed to remove volume group %s\"%(vg),rc=rc, err=err)\n                else:\n                    module.fail_json(msg=\"Refuse to remove non-empty volume group %s without force=yes\"%(vg))\n\n        ### resize VG\n        current_devs = [ os.path.realpath(pv['name']) for pv in pvs if pv['vg_name'] == vg ]\n        devs_to_remove = list(set(current_devs) - set(dev_list))\n        devs_to_add = list(set(dev_list) - set(current_devs))\n\n        if devs_to_add or devs_to_remove:\n            if module.check_mode:\n                changed = True\n            else:\n                if devs_to_add:\n                    devs_to_add_string = ' '.join(devs_to_add)\n                    ### create PV\n                    pvcreate_cmd = module.get_bin_path('pvcreate', True)\n                    for current_dev in devs_to_add:\n                        rc,_,err = module.run_command(\"%s -f %s\" % (pvcreate_cmd, current_dev))\n                        if rc == 0:\n                            changed = True\n                        else:\n                            module.fail_json(msg=\"Creating physical volume '%s' failed\"%current_dev, rc=rc, err=err)\n                    ### add PV to our VG\n                    vgextend_cmd = module.get_bin_path('vgextend', True)\n                    rc,_,err = module.run_command(\"%s %s %s\" % (vgextend_cmd, vg, devs_to_add_string))\n                    if rc == 0:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"Unable to extend %s by %s.\"%(vg, devs_to_add_string),rc=rc,err=err)\n\n                ### remove some PV from our VG\n                if devs_to_remove:\n                    devs_to_remove_string = ' '.join(devs_to_remove)\n                    vgreduce_cmd = module.get_bin_path('vgreduce', True)\n                    rc,_,err = module.run_command(\"%s --force %s %s\" % (vgreduce_cmd, vg, devs_to_remove_string))\n                    if rc == 0:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"Unable to reduce %s by %s.\"%(vg, devs_to_remove_string),rc=rc,err=err)\n\n    module.exit_json(changed=changed)\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nmain()\n", "path": "system/lvg.py"}], "after_files": [{"content": "#!/usr/bin/python\n# -*- coding: utf-8 -*-\n\n# (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>\n# based on lvol module by Jeroen Hoekx <jeroen.hoekx@dsquare.be>\n#\n# This file is part of Ansible\n#\n# Ansible is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# Ansible is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.\n\nDOCUMENTATION = '''\n---\nauthor: \"Alexander Bulimov (@abulimov)\"\nmodule: lvg\nshort_description: Configure LVM volume groups\ndescription:\n  - This module creates, removes or resizes volume groups.\nversion_added: \"1.1\"\noptions:\n  vg:\n    description:\n    - The name of the volume group.\n    required: true\n  pvs:\n    description:\n    - List of comma-separated devices to use as physical devices in this volume group. Required when creating or resizing volume group.\n    - The module will take care of running pvcreate if needed. \n    required: false\n  pesize:\n    description:\n    - The size of the physical extent in megabytes. Must be a power of 2.\n    default: 4\n    required: false\n  vg_options:\n    description:\n    - Additional options to pass to C(vgcreate) when creating the volume group.\n    default: null\n    required: false\n    version_added: \"1.6\"\n  state:\n    choices: [ \"present\", \"absent\" ]\n    default: present\n    description:\n    - Control if the volume group exists.\n    required: false\n  force:\n    choices: [ \"yes\", \"no\" ]\n    default: \"no\"\n    description:\n    - If yes, allows to remove volume group with logical volumes.\n    required: false\nnotes:\n  - module does not modify PE size for already present volume group\n'''\n\nEXAMPLES = '''\n# Create a volume group on top of /dev/sda1 with physical extent size = 32MB.\n- lvg:  vg=vg.services pvs=/dev/sda1 pesize=32\n\n# Create or resize a volume group on top of /dev/sdb1 and /dev/sdc5.\n# If, for example, we already have VG vg.services on top of /dev/sdb1,\n# this VG will be extended by /dev/sdc5.  Or if vg.services was created on\n# top of /dev/sda5, we first extend it with /dev/sdb1 and /dev/sdc5,\n# and then reduce by /dev/sda5.\n- lvg: vg=vg.services pvs=/dev/sdb1,/dev/sdc5\n\n# Remove a volume group with name vg.services.\n- lvg: vg=vg.services state=absent\n'''\n\ndef parse_vgs(data):\n    vgs = []\n    for line in data.splitlines():\n        parts = line.strip().split(';')\n        vgs.append({\n            'name': parts[0],\n            'pv_count': int(parts[1]),\n            'lv_count': int(parts[2]),\n        })\n    return vgs\n\ndef find_mapper_device_name(module, dm_device):\n        dmsetup_cmd = module.get_bin_path('dmsetup', True)\n        mapper_prefix = '/dev/mapper/'\n        rc, dm_name, err = module.run_command(\"%s info -C --noheadings -o name %s\" % (dmsetup_cmd, dm_device))\n        if rc != 0:\n            module.fail_json(msg=\"Failed executing dmsetup command.\", rc=rc, err=err)\n        mapper_device = mapper_prefix + dm_name.rstrip()\n        return mapper_device\n\ndef parse_pvs(module, data):\n    pvs = []\n    dm_prefix = '/dev/dm-'\n    for line in data.splitlines():\n        parts = line.strip().split(';')\n        if parts[0].startswith(dm_prefix):\n            parts[0] = find_mapper_device_name(module, parts[0])\n        pvs.append({\n            'name': parts[0],\n            'vg_name': parts[1],\n        })\n    return pvs\n\ndef main():\n    module = AnsibleModule(\n        argument_spec = dict(\n            vg=dict(required=True),\n            pvs=dict(type='list'),\n            pesize=dict(type='int', default=4),\n            vg_options=dict(default=''),\n            state=dict(choices=[\"absent\", \"present\"], default='present'),\n            force=dict(type='bool', default='no'),\n        ),\n        supports_check_mode=True,\n    )\n\n    vg = module.params['vg']\n    state = module.params['state']\n    force = module.boolean(module.params['force'])\n    pesize = module.params['pesize']\n    vgoptions = module.params['vg_options'].split()\n\n    dev_list = []\n    if module.params['pvs']:\n        dev_list = module.params['pvs']\n    elif state == 'present':\n        module.fail_json(msg=\"No physical volumes given.\")\n\n    # LVM always uses real paths not symlinks so replace symlinks with actual path\n    for idx, dev in enumerate(dev_list):\n        dev_list[idx] = os.path.realpath(dev)\n\n    if state=='present':\n        ### check given devices\n        for test_dev in dev_list:\n            if not os.path.exists(test_dev):\n                module.fail_json(msg=\"Device %s not found.\"%test_dev)\n\n        ### get pv list\n        pvs_cmd = module.get_bin_path('pvs', True)\n        rc,current_pvs,err = module.run_command(\"%s --noheadings -o pv_name,vg_name --separator ';'\" % pvs_cmd)\n        if rc != 0:\n            module.fail_json(msg=\"Failed executing pvs command.\",rc=rc, err=err)\n\n        ### check pv for devices\n        pvs = parse_pvs(module, current_pvs)\n        used_pvs = [ pv for pv in pvs if pv['name'] in dev_list and pv['vg_name'] and pv['vg_name'] != vg ]\n        if used_pvs:\n            module.fail_json(msg=\"Device %s is already in %s volume group.\"%(used_pvs[0]['name'],used_pvs[0]['vg_name']))\n\n    vgs_cmd = module.get_bin_path('vgs', True)\n    rc,current_vgs,err = module.run_command(\"%s --noheadings -o vg_name,pv_count,lv_count --separator ';'\" % vgs_cmd)\n\n    if rc != 0:\n        module.fail_json(msg=\"Failed executing vgs command.\",rc=rc, err=err)\n\n    changed = False\n\n    vgs = parse_vgs(current_vgs)\n\n    for test_vg in vgs:\n        if test_vg['name'] == vg:\n            this_vg = test_vg\n            break\n    else:\n        this_vg = None\n\n    if this_vg is None:\n        if state == 'present':\n            ### create VG\n            if module.check_mode:\n                changed = True\n            else:\n                ### create PV\n                pvcreate_cmd = module.get_bin_path('pvcreate', True)\n                for current_dev in dev_list:\n                    rc,_,err = module.run_command(\"%s -f %s\" % (pvcreate_cmd,current_dev))\n                    if rc == 0:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"Creating physical volume '%s' failed\" % current_dev, rc=rc, err=err)\n                vgcreate_cmd = module.get_bin_path('vgcreate')\n                rc,_,err = module.run_command([vgcreate_cmd] + vgoptions + ['-s', str(pesize), vg] + dev_list)\n                if rc == 0:\n                    changed = True\n                else:\n                    module.fail_json(msg=\"Creating volume group '%s' failed\"%vg, rc=rc, err=err)\n    else:\n        if state == 'absent':\n            if module.check_mode:\n                module.exit_json(changed=True)\n            else:\n                if this_vg['lv_count'] == 0 or force:\n                    ### remove VG\n                    vgremove_cmd = module.get_bin_path('vgremove', True)\n                    rc,_,err = module.run_command(\"%s --force %s\" % (vgremove_cmd, vg))\n                    if rc == 0:\n                        module.exit_json(changed=True)\n                    else:\n                        module.fail_json(msg=\"Failed to remove volume group %s\"%(vg),rc=rc, err=err)\n                else:\n                    module.fail_json(msg=\"Refuse to remove non-empty volume group %s without force=yes\"%(vg))\n\n        ### resize VG\n        current_devs = [ os.path.realpath(pv['name']) for pv in pvs if pv['vg_name'] == vg ]\n        devs_to_remove = list(set(current_devs) - set(dev_list))\n        devs_to_add = list(set(dev_list) - set(current_devs))\n\n        if devs_to_add or devs_to_remove:\n            if module.check_mode:\n                changed = True\n            else:\n                if devs_to_add:\n                    devs_to_add_string = ' '.join(devs_to_add)\n                    ### create PV\n                    pvcreate_cmd = module.get_bin_path('pvcreate', True)\n                    for current_dev in devs_to_add:\n                        rc,_,err = module.run_command(\"%s -f %s\" % (pvcreate_cmd, current_dev))\n                        if rc == 0:\n                            changed = True\n                        else:\n                            module.fail_json(msg=\"Creating physical volume '%s' failed\"%current_dev, rc=rc, err=err)\n                    ### add PV to our VG\n                    vgextend_cmd = module.get_bin_path('vgextend', True)\n                    rc,_,err = module.run_command(\"%s %s %s\" % (vgextend_cmd, vg, devs_to_add_string))\n                    if rc == 0:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"Unable to extend %s by %s.\"%(vg, devs_to_add_string),rc=rc,err=err)\n\n                ### remove some PV from our VG\n                if devs_to_remove:\n                    devs_to_remove_string = ' '.join(devs_to_remove)\n                    vgreduce_cmd = module.get_bin_path('vgreduce', True)\n                    rc,_,err = module.run_command(\"%s --force %s %s\" % (vgreduce_cmd, vg, devs_to_remove_string))\n                    if rc == 0:\n                        changed = True\n                    else:\n                        module.fail_json(msg=\"Unable to reduce %s by %s.\"%(vg, devs_to_remove_string),rc=rc,err=err)\n\n    module.exit_json(changed=changed)\n\n# import module snippets\nfrom ansible.module_utils.basic import *\nmain()\n", "path": "system/lvg.py"}]}