ansible/system/zfs.py
Johan Wirén e549d96723 Changes how zfs properties are handled
This moves the validation of properties to the zfs command itself. The
properties and their choices were not really correct anyway due to
differences between OpenZFS and Solaris/ZFS.
2015-12-08 23:59:31 +01:00

247 lines
7.5 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Johan Wiren <johan.wiren.se@gmail.com>
#
# 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 = '''
---
module: zfs
short_description: Manage zfs
description:
- Manages ZFS file systems, volumes, clones and snapshots.
version_added: "1.1"
options:
name:
description:
- File system, snapshot or volume name e.g. C(rpool/myfs)
required: true
state:
description:
- Whether to create (C(present)), or remove (C(absent)) a file system, snapshot or volume.
choices: ['present', 'absent']
required: true
origin:
description:
- Snapshot from which to create a clone
required: false
createparent:
description:
- Creates all non-existing parent file systems.
required: false
default: "on"
key_value:
description:
- The C(zfs) module takes key=value pairs for zfs properties to be set. See the zfs(8) man page for more information.
author: "Johan Wiren (@johanwiren)"
'''
EXAMPLES = '''
# Create a new file system called myfs in pool rpool
- zfs: name=rpool/myfs state=present
# Create a new volume called myvol in pool rpool.
- zfs: name=rpool/myvol state=present volsize=10M
# Create a snapshot of rpool/myfs file system.
- zfs: name=rpool/myfs@mysnapshot state=present
# Create a new file system called myfs2 with snapdir enabled
- zfs: name=rpool/myfs2 state=present snapdir=enabled
# Create a new file system by cloning a snapshot
- zfs: name=rpool/cloned_fs state=present origin=rpool/myfs@mysnapshot
# Destroy a filesystem
- zfs: name=rpool/myfs state=absent
'''
import os
class Zfs(object):
def __init__(self, module, name, properties):
self.module = module
self.name = name
self.properties = properties
self.changed = False
self.is_solaris = os.uname()[0] == 'SunOS'
self.pool = name.split('/')[0]
self.zfs_cmd = module.get_bin_path('zfs', True)
self.zpool_cmd = module.get_bin_path('zpool', True)
self.enhanced_sharing = self.check_enhanced_sharing()
def check_enhanced_sharing(self):
if os.uname()[0] == 'SunOS':
cmd = [self.zpool_cmd]
cmd.extend(['get', 'version'])
cmd.append(self.pool)
(rc, out, err) = self.module.run_command(cmd, check_rc=True)
version = out.splitlines()[-1].split()[2]
if int(version) >= 34:
return True
return False
def exists(self):
cmd = [self.zfs_cmd, 'list', '-t', 'all', self.name]
(rc, out, err) = self.module.run_command(' '.join(cmd))
if rc == 0:
return True
else:
return False
def create(self):
if self.module.check_mode:
self.changed = True
return
properties = self.properties
volsize = properties.pop('volsize', None)
volblocksize = properties.pop('volblocksize', None)
origin = properties.pop('origin', None)
createparent = self.module.params.get('createparent')
cmd = [self.zfs_cmd]
if "@" in self.name:
action = 'snapshot'
elif origin:
action = 'clone'
else:
action = 'create'
cmd.append(action)
if action in ['create', 'clone']:
if createparent:
cmd += ['-p']
if volsize:
cmd += ['-V', volsize]
if volblocksize:
cmd += ['-b', 'volblocksize']
if properties:
for prop, value in properties.iteritems():
cmd += ['-o', '%s="%s"' % (prop, value)]
if origin:
cmd.append(origin)
cmd.append(self.name)
(rc, out, err) = self.module.run_command(' '.join(cmd))
if rc == 0:
self.changed = True
else:
self.module.fail_json(msg=err)
def destroy(self):
if self.module.check_mode:
self.changed = True
return
cmd = [self.zfs_cmd, 'destroy', '-R', self.name]
(rc, out, err) = self.module.run_command(' '.join(cmd))
if rc == 0:
self.changed = True
else:
self.module.fail_json(msg=err)
def set_property(self, prop, value):
if self.module.check_mode:
self.changed = True
return
cmd = [self.zfs_cmd, 'set', prop + '=' + str(value), self.name]
(rc, out, err) = self.module.run_command(cmd)
if rc == 0:
self.changed = True
else:
self.module.fail_json(msg=err)
def set_properties_if_changed(self):
current_properties = self.get_current_properties()
for prop, value in self.properties.iteritems():
if prop not in current_properties:
self.module.fail_json(msg="invalid property '%s'" % prop)
if current_properties[prop] != value:
self.set_property(prop, value)
def get_current_properties(self):
cmd = [self.zfs_cmd, 'get', '-H']
if self.enhanced_sharing:
cmd += ['-e']
cmd += ['all', self.name]
rc, out, err = self.module.run_command(" ".join(cmd))
properties = dict()
for p, v in [l.split('\t')[1:3] for l in out.splitlines()]:
properties[p] = v
# Add alias for enhanced sharing properties
properties['sharenfs'] = properties.get('share.nfs', None)
properties['sharesmb'] = properties.get('share.smb', None)
return properties
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(type='str', required=True),
state = dict(type='str', required=True, choices=['present', 'absent']),
createparent = dict(type='bool', required=False, default=True),
),
supports_check_mode=True,
check_invalid_arguments=False
)
state = module.params.pop('state')
name = module.params.pop('name')
# Get all valid zfs-properties
properties = dict()
for prop, value in module.params.iteritems():
# All freestyle params are zfs properties
if prop not in module.argument_spec:
# Reverse the boolification of freestyle zfs properties
if type(value) == bool:
if value is True:
properties[prop] = 'on'
else:
properties[prop] = 'off'
else:
properties[prop] = value
result = {}
result['name'] = name
result['state'] = state
zfs = Zfs(module, name, properties)
if state == 'present':
if zfs.exists():
zfs.set_properties_if_changed()
else:
zfs.create()
elif state == 'absent':
if zfs.exists():
zfs.destroy()
result.update(zfs.properties)
result['changed'] = zfs.changed
module.exit_json(**result)
# import module snippets
from ansible.module_utils.basic import *
main()