From e549d967233879981d3639c83d757bafd8274265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Wir=C3=A9n?= Date: Sun, 6 Dec 2015 21:34:55 +0100 Subject: [PATCH] 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. --- system/zfs.py | 353 ++++++++++++-------------------------------------- 1 file changed, 80 insertions(+), 273 deletions(-) diff --git a/system/zfs.py b/system/zfs.py index 343f6ea3202..f9c7a5bad30 100644 --- a/system/zfs.py +++ b/system/zfs.py @@ -24,7 +24,7 @@ DOCUMENTATION = ''' module: zfs short_description: Manage zfs description: - - Manages ZFS file systems on Solaris and FreeBSD. Can manage file systems, volumes and snapshots. See zfs(1M) for more information about the properties. + - Manages ZFS file systems, volumes, clones and snapshots. version_added: "1.1" options: name: @@ -34,183 +34,21 @@ options: state: description: - Whether to create (C(present)), or remove (C(absent)) a file system, snapshot or volume. + choices: ['present', 'absent'] required: true - choices: [present, absent] - aclinherit: - description: - - The aclinherit property. - required: False - choices: [discard,noallow,restricted,passthrough,passthrough-x] - aclmode: - description: - - The aclmode property. - required: False - choices: [discard,groupmask,passthrough] - atime: - description: - - The atime property. - required: False - choices: ['on','off'] - canmount: - description: - - The canmount property. - required: False - choices: ['on','off','noauto'] - casesensitivity: - description: - - The casesensitivity property. - required: False - choices: [sensitive,insensitive,mixed] - checksum: - description: - - The checksum property. - required: False - choices: ['on','off',fletcher2,fletcher4,sha256] - compression: - description: - - The compression property. - required: False - choices: ['on','off',lzjb,gzip,gzip-1,gzip-2,gzip-3,gzip-4,gzip-5,gzip-6,gzip-7,gzip-8,gzip-9,lz4,zle] - copies: - description: - - The copies property. - required: False - choices: [1,2,3] - dedup: - description: - - The dedup property. - required: False - choices: ['on','off'] - devices: - description: - - The devices property. - required: False - choices: ['on','off'] - exec: - description: - - The exec property. - required: False - choices: ['on','off'] - jailed: - description: - - The jailed property. - required: False - choices: ['on','off'] - logbias: - description: - - The logbias property. - required: False - choices: [latency,throughput] - mountpoint: - description: - - The mountpoint property. - required: False - nbmand: - description: - - The nbmand property. - required: False - choices: ['on','off'] - normalization: - description: - - The normalization property. - required: False - choices: [none,formC,formD,formKC,formKD] origin: description: - - Name of the snapshot to clone - required: False - version_added: "2.0" - primarycache: - description: - - The primarycache property. - required: False - choices: [all,none,metadata] - quota: - description: - - The quota property. - required: False - readonly: - description: - - The readonly property. - required: False - choices: ['on','off'] - recordsize: - description: - - The recordsize property. - required: False - refquota: - description: - - The refquota property. - required: False - refreservation: - description: - - The refreservation property. - required: False - reservation: - description: - - The reservation property. - required: False - secondarycache: - description: - - The secondarycache property. - required: False - choices: [all,none,metadata] - setuid: + - Snapshot from which to create a clone + required: false + createparent: description: - - The setuid property. - required: False - choices: ['on','off'] - shareiscsi: + - Creates all non-existing parent file systems. + required: false + default: "on" + key_value: description: - - The shareiscsi property. - required: False - choices: ['on','off'] - sharenfs: - description: - - The sharenfs property. - required: False - sharesmb: - description: - - The sharesmb property. - required: False - snapdir: - description: - - The snapdir property. - required: False - choices: [hidden,visible] - sync: - description: - - The sync property. - required: False - choices: ['standard','always','disabled'] - utf8only: - description: - - The utf8only property. - required: False - choices: ['on','off'] - volsize: - description: - - The volsize property. - required: False - volblocksize: - description: - - The volblocksize property. - required: False - vscan: - description: - - The vscan property. - required: False - choices: ['on','off'] - xattr: - description: - - The xattr property. - required: False - choices: ['on','off'] - zoned: - description: - - The zoned property. - required: False - choices: ['on','off'] + - 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)" ''' @@ -218,7 +56,7 @@ 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. +# 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. @@ -237,20 +75,33 @@ EXAMPLES = ''' import os + class Zfs(object): + def __init__(self, module, name, properties): self.module = module self.name = name self.properties = properties self.changed = False - - self.immutable_properties = [ 'casesensitivity', 'normalization', 'utf8only' ] + 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.module.get_bin_path('zfs', True)] - cmd.append('list') - cmd.append('-t all') - cmd.append(self.name) + cmd = [self.zfs_cmd, 'list', '-t', 'all', self.name] (rc, out, err) = self.module.run_command(' '.join(cmd)) if rc == 0: return True @@ -265,7 +116,9 @@ class Zfs(object): volsize = properties.pop('volsize', None) volblocksize = properties.pop('volblocksize', None) origin = properties.pop('origin', None) - createparent = properties.pop('createparent', None) + createparent = self.module.params.get('createparent') + cmd = [self.zfs_cmd] + if "@" in self.name: action = 'snapshot' elif origin: @@ -273,135 +126,83 @@ class Zfs(object): else: action = 'create' - cmd = [self.module.get_bin_path('zfs', True)] cmd.append(action) - if createparent: - cmd.append('-p') + if action in ['create', 'clone']: + if createparent: + cmd += ['-p'] + if volsize: + cmd += ['-V', volsize] if volblocksize: - cmd.append('-b %s' % volblocksize) + cmd += ['-b', 'volblocksize'] if properties: for prop, value in properties.iteritems(): - cmd.append('-o %s="%s"' % (prop, value)) - if volsize: - cmd.append('-V') - cmd.append(volsize) + cmd += ['-o', '%s="%s"' % (prop, value)] if origin: cmd.append(origin) cmd.append(self.name) - (rc, err, out) = self.module.run_command(' '.join(cmd)) + (rc, out, err) = self.module.run_command(' '.join(cmd)) if rc == 0: self.changed = True else: - self.module.fail_json(msg=out) + self.module.fail_json(msg=err) def destroy(self): if self.module.check_mode: self.changed = True return - cmd = [self.module.get_bin_path('zfs', True)] - cmd.append('destroy') - cmd.append(self.name) - (rc, err, out) = self.module.run_command(' '.join(cmd)) + 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=out) + self.module.fail_json(msg=err) def set_property(self, prop, value): if self.module.check_mode: self.changed = True return - cmd = self.module.get_bin_path('zfs', True) - args = [cmd, 'set', prop + '=' + value, self.name] - (rc, err, out) = self.module.run_command(args) + 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=out) + 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: - if prop in self.immutable_properties: - self.module.fail_json(msg='Cannot change property %s after creation.' % prop) - else: - self.set_property(prop, value) + self.set_property(prop, value) def get_current_properties(self): - def get_properties_by_name(propname): - cmd = [self.module.get_bin_path('zfs', True)] - cmd += ['get', '-H', propname, self.name] - rc, out, err = self.module.run_command(cmd) - return [l.split('\t')[1:3] for l in out.splitlines()] - properties = dict(get_properties_by_name('all')) - if 'share.*' in properties: - # Some ZFS pools list the sharenfs and sharesmb properties - # hierarchically as share.nfs and share.smb respectively. - del properties['share.*'] - for p, v in get_properties_by_name('share.all'): - alias = p.replace('.', '') # share.nfs -> sharenfs (etc) - properties[alias] = v + 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 run_command(self, cmd): - progname = cmd[0] - cmd[0] = module.get_bin_path(progname, True) - return module.run_command(cmd) def main(): - # FIXME: should use dict() constructor like other modules, required=False is default module = AnsibleModule( - argument_spec = { - 'name': {'required': True}, - 'state': {'required': True, 'choices':['present', 'absent']}, - 'aclinherit': {'required': False, 'choices':['discard', 'noallow', 'restricted', 'passthrough', 'passthrough-x']}, - 'aclmode': {'required': False, 'choices':['discard', 'groupmask', 'passthrough']}, - 'atime': {'required': False, 'choices':['on', 'off']}, - 'canmount': {'required': False, 'choices':['on', 'off', 'noauto']}, - 'casesensitivity': {'required': False, 'choices':['sensitive', 'insensitive', 'mixed']}, - 'checksum': {'required': False, 'choices':['on', 'off', 'fletcher2', 'fletcher4', 'sha256']}, - 'compression': {'required': False, 'choices':['on', 'off', 'lzjb', 'gzip', 'gzip-1', 'gzip-2', 'gzip-3', 'gzip-4', 'gzip-5', 'gzip-6', 'gzip-7', 'gzip-8', 'gzip-9', 'lz4', 'zle']}, - 'copies': {'required': False, 'choices':['1', '2', '3']}, - 'createparent': {'required': False, 'choices':['on', 'off']}, - 'dedup': {'required': False, 'choices':['on', 'off']}, - 'devices': {'required': False, 'choices':['on', 'off']}, - 'exec': {'required': False, 'choices':['on', 'off']}, - # Not supported - #'groupquota': {'required': False}, - 'jailed': {'required': False, 'choices':['on', 'off']}, - 'logbias': {'required': False, 'choices':['latency', 'throughput']}, - 'mountpoint': {'required': False}, - 'nbmand': {'required': False, 'choices':['on', 'off']}, - 'normalization': {'required': False, 'choices':['none', 'formC', 'formD', 'formKC', 'formKD']}, - 'origin': {'required': False}, - 'primarycache': {'required': False, 'choices':['all', 'none', 'metadata']}, - 'quota': {'required': False}, - 'readonly': {'required': False, 'choices':['on', 'off']}, - 'recordsize': {'required': False}, - 'refquota': {'required': False}, - 'refreservation': {'required': False}, - 'reservation': {'required': False}, - 'secondarycache': {'required': False, 'choices':['all', 'none', 'metadata']}, - 'setuid': {'required': False, 'choices':['on', 'off']}, - 'shareiscsi': {'required': False, 'choices':['on', 'off']}, - 'sharenfs': {'required': False}, - 'sharesmb': {'required': False}, - 'snapdir': {'required': False, 'choices':['hidden', 'visible']}, - 'sync': {'required': False, 'choices':['standard', 'always', 'disabled']}, - # Not supported - #'userquota': {'required': False}, - 'utf8only': {'required': False, 'choices':['on', 'off']}, - 'volsize': {'required': False}, - 'volblocksize': {'required': False}, - 'vscan': {'required': False, 'choices':['on', 'off']}, - 'xattr': {'required': False, 'choices':['on', 'off']}, - 'zoned': {'required': False, 'choices':['on', 'off']}, - }, - supports_check_mode=True + 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') @@ -410,10 +211,16 @@ def main(): # Get all valid zfs-properties properties = dict() for prop, value in module.params.iteritems(): - if prop in ['CHECKMODE']: - continue - if value: - properties[prop] = value + # 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