2013-02-27 11:24:56 +01:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
2013-03-11 09:20:01 +01:00
# (c) 2013, Jeroen Hoekx <jeroen.hoekx@dsquare.be>, Alexander Bulimov <lazywolf0@gmail.com>
2013-02-27 11:24:56 +01:00
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
- - -
2015-05-14 15:37:00 +02:00
author :
2015-10-05 19:16:29 +02:00
- " Jeroen Hoekx (@jhoekx) "
- " Alexander Bulimov (@abulimov) "
2013-02-27 11:24:56 +01:00
module : lvol
short_description : Configure LVM logical volumes
description :
- This module creates , removes or resizes logical volumes .
version_added : " 1.1 "
options :
vg :
description :
- The volume group this logical volume is part of .
required : true
lv :
description :
- The name of the logical volume .
required : true
size :
description :
2013-06-24 22:37:31 +02:00
- The size of the logical volume , according to lvcreate ( 8 ) - - size , by
default in megabytes or optionally with one of [ bBsSkKmMgGtTpPeE ] units ; or
according to lvcreate ( 8 ) - - extents as a percentage of [ VG | PVS | FREE ] ;
2015-12-25 14:27:48 +01:00
Float values must begin with a digit .
Resizing using percentage values was not supported prior to 2.1 .
2013-02-27 11:24:56 +01:00
state :
choices : [ " present " , " absent " ]
default : present
description :
2016-07-28 17:31:51 +02:00
- Control if the logical volume exists . If C ( present ) and the
volume does not already exist then the C ( size ) option is required .
required : false
active :
version_added : " 2.2 "
choices : [ " yes " , " no " ]
default : " yes "
description :
- Whether the volume is activate and visible to the host .
2013-02-27 11:24:56 +01:00
required : false
2014-01-26 18:07:48 +01:00
force :
version_added : " 1.5 "
choices : [ " yes " , " no " ]
default : " no "
description :
- Shrink or remove operations of volumes requires this switch . Ensures that
that filesystems get never corrupted / destroyed by mistake .
required : false
2014-10-21 23:56:13 +02:00
opts :
2015-06-02 20:11:51 +02:00
version_added : " 2.0 "
2014-10-21 23:56:13 +02:00
description :
- Free - form options to be passed to the lvcreate command
2015-12-16 03:45:11 +01:00
snapshot :
version_added : " 2.1 "
description :
- The name of the snapshot volume
required : false
2016-04-19 19:31:23 +02:00
pvs :
2016-04-28 23:42:41 +02:00
version_added : " 2.2 "
2016-04-19 19:31:23 +02:00
description :
- Comma separated list of physical volumes e . g . / dev / sda , / dev / sdb
required : false
2016-05-13 11:28:41 +02:00
shrink :
version_added : " 2.2 "
description :
- shrink if current size is higher than size requested
required : false
default : yes
2013-02-27 11:24:56 +01:00
notes :
- Filesystems on top of the volume are not resized .
'''
2013-06-14 11:53:43 +02:00
EXAMPLES = '''
# Create a logical volume of 512m.
- lvol : vg = firefly lv = test size = 512
2016-04-11 20:18:14 +02:00
# Create a logical volume of 512m with disks /dev/sda and /dev/sdb
- lvol : vg = firefly lv = test size = 512 pvs = / dev / sda , / dev / sdb
2016-04-11 21:55:40 +02:00
# Create cache pool logical volume
- lvol : vg = firefly lv = lvcache size = 512 m opts = ' --type cache-pool '
2013-06-24 22:37:31 +02:00
# Create a logical volume of 512g.
- lvol : vg = firefly lv = test size = 512 g
# Create a logical volume the size of all remaining space in the volume group
- lvol : vg = firefly lv = test size = 100 % FREE
2014-10-21 23:56:13 +02:00
# Create a logical volume with special options
- lvol : vg = firefly lv = test size = 512 g opts = " -r 16 "
2013-06-14 11:53:43 +02:00
# Extend the logical volume to 1024m.
- lvol : vg = firefly lv = test size = 1024
2015-12-25 14:27:48 +01:00
# Extend the logical volume to consume all remaining space in the volume group
- lvol : vg = firefly lv = test size = + 100 % FREE
# Extend the logical volume to take all remaining space of the PVs
- lvol : vg = firefly lv = test size = 100 % PVS
# Resize the logical volume to % of VG
- lvol : vg - firefly lv = test size = 80 % VG force = yes
2013-06-14 11:53:43 +02:00
# Reduce the logical volume to 512m
2014-01-26 18:07:48 +01:00
- lvol : vg = firefly lv = test size = 512 force = yes
2013-06-14 11:53:43 +02:00
2016-05-13 11:28:41 +02:00
# Set the logical volume to 512m and do not try to shrink if size is lower than current one
- lvol : vg = firefly lv = test size = 512 shrink = no
2013-06-14 11:53:43 +02:00
# Remove the logical volume.
2014-01-26 18:07:48 +01:00
- lvol : vg = firefly lv = test state = absent force = yes
2015-12-16 03:45:11 +01:00
# Create a snapshot volume of the test logical volume.
- lvol : vg = firefly lv = test snapshot = snap1 size = 100 m
2016-07-28 17:31:51 +02:00
# Deactivate a logical volume
- lvol : vg = firefly lv = test active = false
# Create a deactivated logical volume
- lvol : vg = firefly lv = test size = 512 g active = false
2013-06-14 11:53:43 +02:00
'''
2013-09-24 15:06:18 +02:00
import re
2014-02-24 23:56:08 +01:00
2015-12-25 14:27:48 +01:00
decimal_point = re . compile ( r " ( \ d+) " )
2013-09-24 15:06:18 +02:00
2015-05-09 15:06:58 +02:00
def mkversion ( major , minor , patch ) :
return ( 1000 * 1000 * int ( major ) ) + ( 1000 * int ( minor ) ) + int ( patch )
2014-02-24 23:56:08 +01:00
2013-02-27 11:24:56 +01:00
def parse_lvs ( data ) :
lvs = [ ]
for line in data . splitlines ( ) :
parts = line . strip ( ) . split ( ' ; ' )
lvs . append ( {
2016-04-11 21:55:40 +02:00
' name ' : parts [ 0 ] . replace ( ' [ ' , ' ' ) . replace ( ' ] ' , ' ' ) ,
2016-07-28 17:31:51 +02:00
' size ' : int ( decimal_point . match ( parts [ 1 ] ) . group ( 1 ) ) ,
' active ' : ( parts [ 2 ] [ 4 ] == ' a ' )
2013-02-27 11:24:56 +01:00
} )
return lvs
2015-12-25 14:27:48 +01:00
def parse_vgs ( data ) :
vgs = [ ]
for line in data . splitlines ( ) :
parts = line . strip ( ) . split ( ' ; ' )
vgs . append ( {
' name ' : parts [ 0 ] ,
' size ' : int ( decimal_point . match ( parts [ 1 ] ) . group ( 1 ) ) ,
' free ' : int ( decimal_point . match ( parts [ 2 ] ) . group ( 1 ) ) ,
' ext_size ' : int ( decimal_point . match ( parts [ 3 ] ) . group ( 1 ) )
} )
return vgs
2014-02-24 23:56:08 +01:00
2015-05-09 15:06:58 +02:00
def get_lvm_version ( module ) :
ver_cmd = module . get_bin_path ( " lvm " , required = True )
rc , out , err = module . run_command ( " %s version " % ( ver_cmd ) )
if rc != 0 :
return None
m = re . search ( " LVM version: \ s+( \ d+) \ .( \ d+) \ .( \ d+).*( \ d {4} - \ d {2} - \ d {2} ) " , out )
if not m :
return None
return mkversion ( m . group ( 1 ) , m . group ( 2 ) , m . group ( 3 ) )
2013-02-27 11:24:56 +01:00
def main ( ) :
module = AnsibleModule (
2014-02-24 23:56:08 +01:00
argument_spec = dict (
2013-02-27 11:24:56 +01:00
vg = dict ( required = True ) ,
lv = dict ( required = True ) ,
2015-10-05 19:16:29 +02:00
size = dict ( type = ' str ' ) ,
2014-10-21 23:56:13 +02:00
opts = dict ( type = ' str ' ) ,
2013-02-27 11:24:56 +01:00
state = dict ( choices = [ " absent " , " present " ] , default = ' present ' ) ,
2014-01-26 18:07:48 +01:00
force = dict ( type = ' bool ' , default = ' no ' ) ,
2016-05-13 11:28:41 +02:00
shrink = dict ( type = ' bool ' , default = ' yes ' ) ,
2016-07-28 17:31:51 +02:00
active = dict ( type = ' bool ' , default = ' yes ' ) ,
2015-12-16 03:45:11 +01:00
snapshot = dict ( type = ' str ' , default = None ) ,
2016-04-11 20:18:14 +02:00
pvs = dict ( type = ' str ' )
2013-02-27 11:24:56 +01:00
) ,
supports_check_mode = True ,
)
2015-05-09 15:06:58 +02:00
# Determine if the "--yes" option should be used
version_found = get_lvm_version ( module )
if version_found == None :
module . fail_json ( msg = " Failed to get LVM version number " )
version_yesopt = mkversion ( 2 , 2 , 99 ) # First LVM with the "--yes" option
2015-05-28 20:46:53 +02:00
if version_found > = version_yesopt :
yesopt = " --yes "
else :
yesopt = " "
2015-05-09 15:06:58 +02:00
2013-02-27 11:24:56 +01:00
vg = module . params [ ' vg ' ]
lv = module . params [ ' lv ' ]
size = module . params [ ' size ' ]
2014-10-21 23:56:13 +02:00
opts = module . params [ ' opts ' ]
2013-02-27 11:24:56 +01:00
state = module . params [ ' state ' ]
2014-01-26 18:07:48 +01:00
force = module . boolean ( module . params [ ' force ' ] )
2016-05-13 11:28:41 +02:00
shrink = module . boolean ( module . params [ ' shrink ' ] )
2016-07-28 17:31:51 +02:00
active = module . boolean ( module . params [ ' active ' ] )
2013-07-19 10:59:11 +02:00
size_opt = ' L '
size_unit = ' m '
2015-12-16 03:45:11 +01:00
snapshot = module . params [ ' snapshot ' ]
2016-04-11 20:18:14 +02:00
pvs = module . params [ ' pvs ' ]
if pvs is None :
pvs = " "
else :
pvs = pvs . replace ( " , " , " " )
2013-02-27 11:24:56 +01:00
2014-10-21 23:56:13 +02:00
if opts is None :
opts = " "
2016-05-14 10:40:49 +02:00
# Add --test option when running in check-mode
if module . check_mode :
test_opt = ' --test '
else :
test_opt = ' '
2013-02-27 11:24:56 +01:00
if size :
2013-06-24 22:37:31 +02:00
# LVCREATE(8) -l --extents option with percentage
if ' % ' in size :
2014-02-24 23:56:08 +01:00
size_parts = size . split ( ' % ' , 1 )
2013-06-24 22:37:31 +02:00
size_percent = int ( size_parts [ 0 ] )
if size_percent > 100 :
module . fail_json ( msg = " Size percentage cannot be larger than 100 % " )
size_whole = size_parts [ 1 ]
if size_whole == ' ORIGIN ' :
module . fail_json ( msg = " Snapshot Volumes are not supported " )
elif size_whole not in [ ' VG ' , ' PVS ' , ' FREE ' ] :
module . fail_json ( msg = " Specify extents as a percentage of VG|PVS|FREE " )
size_opt = ' l '
size_unit = ' '
2015-10-05 19:16:29 +02:00
if not ' % ' in size :
2013-06-24 22:37:31 +02:00
# LVCREATE(8) -L --size option unit
2015-01-12 17:43:51 +01:00
if size [ - 1 ] . lower ( ) in ' bskmgtpe ' :
2015-10-05 19:16:29 +02:00
size_unit = size [ - 1 ] . lower ( )
size = size [ 0 : - 1 ]
try :
float ( size )
if not size [ 0 ] . isdigit ( ) : raise ValueError ( )
except ValueError :
module . fail_json ( msg = " Bad size specification of ' %s ' " % size )
2013-06-24 22:37:31 +02:00
2015-10-05 19:16:29 +02:00
# when no unit, megabytes by default
2013-06-24 22:37:31 +02:00
if size_opt == ' l ' :
unit = ' m '
else :
unit = size_unit
2014-02-24 23:56:08 +01:00
2015-12-25 14:27:48 +01:00
# Get information on volume group requested
vgs_cmd = module . get_bin_path ( " vgs " , required = True )
rc , current_vgs , err = module . run_command (
" %s --noheadings -o vg_name,size,free,vg_extent_size --units %s --separator ' ; ' %s " % ( vgs_cmd , unit , vg ) )
if rc != 0 :
if state == ' absent ' :
2016-09-08 00:40:17 +02:00
module . exit_json ( changed = False , stdout = " Volume group %s does not exist. " % vg )
2015-12-25 14:27:48 +01:00
else :
module . fail_json ( msg = " Volume group %s does not exist. " % vg , rc = rc , err = err )
vgs = parse_vgs ( current_vgs )
2016-04-11 20:18:14 +02:00
this_vg = vgs [ 0 ]
2015-12-25 14:27:48 +01:00
# Get information on logical volume requested
2014-11-26 11:27:29 +01:00
lvs_cmd = module . get_bin_path ( " lvs " , required = True )
2014-02-24 23:56:08 +01:00
rc , current_lvs , err = module . run_command (
2016-07-28 17:31:51 +02:00
" %s -a --noheadings --nosuffix -o lv_name,size,lv_attr --units %s --separator ' ; ' %s " % ( lvs_cmd , unit , vg ) )
2013-02-27 11:24:56 +01:00
if rc != 0 :
2013-08-03 03:32:56 +02:00
if state == ' absent ' :
2016-09-08 00:40:17 +02:00
module . exit_json ( changed = False , stdout = " Volume group %s does not exist. " % vg )
2013-08-03 03:32:56 +02:00
else :
2014-02-24 23:56:08 +01:00
module . fail_json ( msg = " Volume group %s does not exist. " % vg , rc = rc , err = err )
2013-02-27 11:24:56 +01:00
changed = False
lvs = parse_lvs ( current_lvs )
2015-12-16 03:45:11 +01:00
if snapshot is None :
check_lv = lv
else :
check_lv = snapshot
2013-02-27 11:24:56 +01:00
for test_lv in lvs :
2015-12-16 03:45:11 +01:00
if test_lv [ ' name ' ] == check_lv :
2013-02-27 11:24:56 +01:00
this_lv = test_lv
break
else :
this_lv = None
2014-02-24 23:56:08 +01:00
if state == ' present ' and not size :
if this_lv is None :
module . fail_json ( msg = " No size given. " )
2013-06-24 22:37:31 +02:00
msg = ' '
2013-02-27 11:24:56 +01:00
if this_lv is None :
if state == ' present ' :
### create LV
2016-05-14 10:40:49 +02:00
lvcreate_cmd = module . get_bin_path ( " lvcreate " , required = True )
if snapshot is not None :
cmd = " %s %s %s - %s %s %s -s -n %s %s %s / %s " % ( lvcreate_cmd , test_opt , yesopt , size_opt , size , size_unit , snapshot , opts , vg , lv )
else :
cmd = " %s %s %s -n %s - %s %s %s %s %s %s " % ( lvcreate_cmd , test_opt , yesopt , lv , size_opt , size , size_unit , opts , vg , pvs )
rc , _ , err = module . run_command ( cmd )
if rc == 0 :
2013-02-27 11:24:56 +01:00
changed = True
else :
2016-05-14 10:40:49 +02:00
module . fail_json ( msg = " Creating logical volume ' %s ' failed " % lv , rc = rc , err = err )
2013-02-27 11:24:56 +01:00
else :
if state == ' absent ' :
### remove LV
2014-01-26 18:07:48 +01:00
if not force :
module . fail_json ( msg = " Sorry, no removal of logical volume %s without force=yes. " % ( this_lv [ ' name ' ] ) )
2014-11-26 11:27:29 +01:00
lvremove_cmd = module . get_bin_path ( " lvremove " , required = True )
2016-05-14 10:40:49 +02:00
rc , _ , err = module . run_command ( " %s %s --force %s / %s " % ( lvremove_cmd , test_opt , vg , this_lv [ ' name ' ] ) )
2013-02-27 11:24:56 +01:00
if rc == 0 :
module . exit_json ( changed = True )
else :
2014-02-24 23:56:08 +01:00
module . fail_json ( msg = " Failed to remove logical volume %s " % ( lv ) , rc = rc , err = err )
2013-06-24 22:37:31 +02:00
2016-07-28 17:31:51 +02:00
elif not size :
pass
2013-06-24 22:37:31 +02:00
elif size_opt == ' l ' :
2015-12-25 14:27:48 +01:00
### Resize LV based on % value
tool = None
size_free = this_vg [ ' free ' ]
if size_whole == ' VG ' or size_whole == ' PVS ' :
size_requested = size_percent * this_vg [ ' size ' ] / 100
else : # size_whole == 'FREE':
size_requested = size_percent * this_vg [ ' free ' ] / 100
if ' + ' in size :
size_requested + = this_lv [ ' size ' ]
if this_lv [ ' size ' ] < size_requested :
2016-04-11 20:18:14 +02:00
if ( size_free > 0 ) and ( ( ' + ' not in size ) or ( size_free > = ( size_requested - this_lv [ ' size ' ] ) ) ) :
2015-12-25 14:27:48 +01:00
tool = module . get_bin_path ( " lvextend " , required = True )
else :
module . fail_json ( msg = " Logical Volume %s could not be extended. Not enough free space left ( %s %s required / %s %s available) " % ( this_lv [ ' name ' ] , ( size_requested - this_lv [ ' size ' ] ) , unit , size_free , unit ) )
2016-05-13 11:28:41 +02:00
elif shrink and this_lv [ ' size ' ] > size_requested + this_vg [ ' ext_size ' ] : # more than an extent too large
2015-12-25 14:27:48 +01:00
if size_requested == 0 :
module . fail_json ( msg = " Sorry, no shrinking of %s to 0 permitted. " % ( this_lv [ ' name ' ] ) )
elif not force :
module . fail_json ( msg = " Sorry, no shrinking of %s without force=yes " % ( this_lv [ ' name ' ] ) )
else :
tool = module . get_bin_path ( " lvreduce " , required = True )
tool = ' %s %s ' % ( tool , ' --force ' )
if tool :
2016-05-14 10:40:49 +02:00
cmd = " %s %s - %s %s %s %s / %s %s " % ( tool , test_opt , size_opt , size , size_unit , vg , this_lv [ ' name ' ] , pvs )
rc , out , err = module . run_command ( cmd )
if " Reached maximum COW size " in out :
module . fail_json ( msg = " Unable to resize %s to %s %s " % ( lv , size , size_unit ) , rc = rc , err = err , out = out )
elif rc == 0 :
2015-12-25 14:27:48 +01:00
changed = True
2016-05-14 10:40:49 +02:00
msg = " Volume %s resized to %s %s " % ( this_lv [ ' name ' ] , size_requested , unit )
elif " matches existing size " in err :
module . exit_json ( changed = False , vg = vg , lv = this_lv [ ' name ' ] , size = this_lv [ ' size ' ] )
elif " not larger than existing size " in err :
module . exit_json ( changed = False , vg = vg , lv = this_lv [ ' name ' ] , size = this_lv [ ' size ' ] , msg = " Original size is larger than requested size " , err = err )
2015-12-25 14:27:48 +01:00
else :
2016-05-14 10:40:49 +02:00
module . fail_json ( msg = " Unable to resize %s to %s %s " % ( lv , size , size_unit ) , rc = rc , err = err )
2015-12-25 14:27:48 +01:00
2013-06-24 22:37:31 +02:00
else :
2015-12-25 14:27:48 +01:00
### resize LV based on absolute values
2013-06-24 22:37:31 +02:00
tool = None
2015-12-16 03:45:11 +01:00
if int ( size ) > this_lv [ ' size ' ] :
2014-11-26 11:27:29 +01:00
tool = module . get_bin_path ( " lvextend " , required = True )
2016-05-13 11:28:41 +02:00
elif shrink and int ( size ) < this_lv [ ' size ' ] :
2015-12-25 14:27:48 +01:00
if int ( size ) == 0 :
module . fail_json ( msg = " Sorry, no shrinking of %s to 0 permitted. " % ( this_lv [ ' name ' ] ) )
2014-01-26 18:07:48 +01:00
if not force :
module . fail_json ( msg = " Sorry, no shrinking of %s without force=yes. " % ( this_lv [ ' name ' ] ) )
2015-12-25 14:27:48 +01:00
else :
tool = module . get_bin_path ( " lvreduce " , required = True )
tool = ' %s %s ' % ( tool , ' --force ' )
2013-06-24 22:37:31 +02:00
if tool :
2016-05-14 10:40:49 +02:00
cmd = " %s %s - %s %s %s %s / %s %s " % ( tool , test_opt , size_opt , size , size_unit , vg , this_lv [ ' name ' ] , pvs )
rc , out , err = module . run_command ( cmd )
if " Reached maximum COW size " in out :
module . fail_json ( msg = " Unable to resize %s to %s %s " % ( lv , size , size_unit ) , rc = rc , err = err , out = out )
elif rc == 0 :
2013-02-27 11:24:56 +01:00
changed = True
2016-05-14 10:40:49 +02:00
elif " matches existing size " in err :
module . exit_json ( changed = False , vg = vg , lv = this_lv [ ' name ' ] , size = this_lv [ ' size ' ] )
elif " not larger than existing size " in err :
module . exit_json ( changed = False , vg = vg , lv = this_lv [ ' name ' ] , size = this_lv [ ' size ' ] , msg = " Original size is larger than requested size " , err = err )
2013-02-27 11:24:56 +01:00
else :
2016-05-14 10:40:49 +02:00
module . fail_json ( msg = " Unable to resize %s to %s %s " % ( lv , size , size_unit ) , rc = rc , err = err )
2013-02-27 11:24:56 +01:00
2016-07-28 17:31:51 +02:00
if this_lv is not None :
if active :
lvchange_cmd = module . get_bin_path ( " lvchange " , required = True )
rc , _ , err = module . run_command ( " %s -ay %s / %s " % ( lvchange_cmd , vg , this_lv [ ' name ' ] ) )
if rc == 0 :
module . exit_json ( changed = ( ( not this_lv [ ' active ' ] ) or changed ) , vg = vg , lv = this_lv [ ' name ' ] , size = this_lv [ ' size ' ] )
else :
module . fail_json ( msg = " Failed to activate logical volume %s " % ( lv ) , rc = rc , err = err )
else :
lvchange_cmd = module . get_bin_path ( " lvchange " , required = True )
rc , _ , err = module . run_command ( " %s -an %s / %s " % ( lvchange_cmd , vg , this_lv [ ' name ' ] ) )
if rc == 0 :
module . exit_json ( changed = ( this_lv [ ' active ' ] or changed ) , vg = vg , lv = this_lv [ ' name ' ] , size = this_lv [ ' size ' ] )
else :
module . fail_json ( msg = " Failed to deactivate logical volume %s " % ( lv ) , rc = rc , err = err )
2014-02-24 23:56:08 +01:00
module . exit_json ( changed = changed , msg = msg )
2013-02-27 11:24:56 +01:00
2013-12-02 21:13:49 +01:00
# import module snippets
2013-12-02 21:11:23 +01:00
from ansible . module_utils . basic import *
2014-02-24 23:56:08 +01:00
2015-05-21 04:23:45 +02:00
if __name__ == ' __main__ ' :
main ( )