File: //usr/lib/python2.7/site-packages/salt/states/zpool.py
# -*- coding: utf-8 -*-
'''
States for managing zpools
:maintainer: Jorge Schrauwen <sjorge@blackdot.be>
:maturity: new
:depends: salt.utils.zfs, salt.modules.zpool
:platform: smartos, illumos, solaris, freebsd, linux
.. versionadded:: 2016.3.0
.. versionchanged:: 2018.3.1
Big refactor to remove duplicate code, better type conversions and improved
consistency in output.
.. code-block:: yaml
oldpool:
zpool.absent:
- export: true
newpool:
zpool.present:
- config:
import: false
force: true
- properties:
comment: salty storage pool
- layout:
- mirror:
- /dev/disk0
- /dev/disk1
- mirror:
- /dev/disk2
- /dev/disk3
partitionpool:
zpool.present:
- config:
import: false
force: true
- properties:
comment: disk partition salty storage pool
ashift: '12'
feature@lz4_compress: enabled
- filesystem_properties:
compression: lz4
atime: on
relatime: on
- layout:
- /dev/disk/by-uuid/3e43ce94-77af-4f52-a91b-6cdbb0b0f41b
simplepool:
zpool.present:
- config:
import: false
force: true
- properties:
comment: another salty storage pool
- layout:
- /dev/disk0
- /dev/disk1
.. warning::
The layout will never be updated, it will only be used at time of creation.
It's a whole lot of work to figure out if a devices needs to be detached, removed,
etc. This is best done by the sysadmin on a case per case basis.
Filesystem properties are also not updated, this should be managed by the zfs state module.
'''
from __future__ import absolute_import, print_function, unicode_literals
# Import Python libs
import os
import logging
# Import Salt libs
from salt.utils.odict import OrderedDict
log = logging.getLogger(__name__)
# Define the state's virtual name
__virtualname__ = 'zpool'
def __virtual__():
'''
Provides zpool state
'''
if not __grains__.get('zfs_support'):
return False, 'The zpool state cannot be loaded: zfs not supported'
return __virtualname__
def _layout_to_vdev(layout, device_dir=None):
'''
Turn the layout data into usable vdevs spedcification
We need to support 2 ways of passing the layout:
.. code::
layout_new:
- mirror:
- disk0
- disk1
- mirror:
- disk2
- disk3
.. code:
layout_legacy:
mirror-0:
disk0
disk1
mirror-1:
disk2
disk3
'''
vdevs = []
# NOTE: check device_dir exists
if device_dir and not os.path.exists(device_dir):
device_dir = None
# NOTE: handle list of OrderedDicts (new layout)
if isinstance(layout, list):
# NOTE: parse each vdev as a tiny layout and just append
for vdev in layout:
if isinstance(vdev, OrderedDict):
vdevs.extend(_layout_to_vdev(vdev, device_dir))
else:
if device_dir and vdev[0] != '/':
vdev = os.path.join(device_dir, vdev)
vdevs.append(vdev)
# NOTE: handle nested OrderedDict (legacy layout)
# this is also used to parse the nested OrderedDicts
# from the new layout
elif isinstance(layout, OrderedDict):
for vdev in layout:
# NOTE: extract the vdev type and disks in the vdev
vdev_type = vdev.split('-')[0]
vdev_disk = layout[vdev]
# NOTE: skip appending the dummy type 'disk'
if vdev_type != 'disk':
vdevs.append(vdev_type)
# NOTE: ensure the disks are a list (legacy layout are not)
if not isinstance(vdev_disk, list):
vdev_disk = vdev_disk.split(' ')
# NOTE: also append the actualy disks behind the type
# also prepend device_dir to disks if required
for disk in vdev_disk:
if device_dir and disk[0] != '/':
disk = os.path.join(device_dir, disk)
vdevs.append(disk)
# NOTE: we got invalid data for layout
else:
vdevs = None
return vdevs
def present(name, properties=None, filesystem_properties=None, layout=None, config=None):
'''
ensure storage pool is present on the system
name : string
name of storage pool
properties : dict
optional set of properties to set for the storage pool
filesystem_properties : dict
optional set of filesystem properties to set for the storage pool (creation only)
layout: dict
disk layout to use if the pool does not exist (creation only)
config : dict
fine grain control over this state
.. note::
The following configuration properties can be toggled in the config parameter.
- import (true) - try to import the pool before creating it if absent
- import_dirs (None) - specify additional locations to scan for devices on import (comma-seperated)
- device_dir (None, SunOS=/dev/dsk, Linux=/dev) - specify device directory to prepend for none
absolute device paths
- force (false) - try to force the import or creation
.. note::
It is no longer needed to give a unique name to each top-level vdev, the old
layout format is still supported but no longer recommended.
.. code-block:: yaml
- mirror:
- /tmp/vdisk3
- /tmp/vdisk2
- mirror:
- /tmp/vdisk0
- /tmp/vdisk1
The above yaml will always result in the following zpool create:
.. code-block:: bash
zpool create mypool mirror /tmp/vdisk3 /tmp/vdisk2 mirror /tmp/vdisk0 /tmp/vdisk1
.. warning::
The legacy format is also still supported but not recommended,
because ID's inside the layout dict must be unique they need to have a suffix.
.. code-block:: yaml
mirror-0:
/tmp/vdisk3
/tmp/vdisk2
mirror-1:
/tmp/vdisk0
/tmp/vdisk1
.. warning::
Pay attention to the order of your dict!
.. code-block:: yaml
- mirror:
- /tmp/vdisk0
- /tmp/vdisk1
- /tmp/vdisk2
The above will result in the following zpool create:
.. code-block:: bash
zpool create mypool mirror /tmp/vdisk0 /tmp/vdisk1 /tmp/vdisk2
Creating a 3-way mirror! While you probably expect it to be mirror
root vdev with 2 devices + a root vdev of 1 device!
'''
ret = {'name': name,
'changes': {},
'result': None,
'comment': ''}
# config defaults
default_config = {
'import': True,
'import_dirs': None,
'device_dir': None,
'force': False
}
if __grains__['kernel'] == 'SunOS':
default_config['device_dir'] = '/dev/dsk'
elif __grains__['kernel'] == 'Linux':
default_config['device_dir'] = '/dev'
# merge state config
if config:
default_config.update(config)
config = default_config
# ensure properties are zfs values
if properties:
properties = __utils__['zfs.from_auto_dict'](properties)
elif properties is None:
properties = {}
if filesystem_properties:
filesystem_properties = __utils__['zfs.from_auto_dict'](filesystem_properties)
elif filesystem_properties is None:
filesystem_properties = {}
# parse layout
vdevs = _layout_to_vdev(layout, config['device_dir'])
if vdevs:
vdevs.insert(0, name)
# log configuration
log.debug('zpool.present::%s::config - %s', name, config)
log.debug('zpool.present::%s::vdevs - %s', name, vdevs)
log.debug('zpool.present::%s::properties - %s', name, properties)
log.debug('zpool.present::%s::filesystem_properties - %s', name, filesystem_properties)
# ensure the pool is present
ret['result'] = False
# don't do anything because this is a test
if __opts__['test']:
ret['result'] = True
if __salt__['zpool.exists'](name):
ret['changes'][name] = 'uptodate'
else:
ret['changes'][name] = 'imported' if config['import'] else 'created'
ret['comment'] = 'storage pool {0} was {1}'.format(name, ret['changes'][name])
# update pool
elif __salt__['zpool.exists'](name):
ret['result'] = True
# fetch current pool properties
properties_current = __salt__['zpool.get'](name, parsable=True)
# build list of properties to update
properties_update = []
if properties:
for prop in properties:
# skip unexisting properties
if prop not in properties_current:
log.warning('zpool.present::%s::update - unknown property: %s', name, prop)
continue
# compare current and wanted value
if properties_current[prop] != properties[prop]:
properties_update.append(prop)
# update pool properties
for prop in properties_update:
res = __salt__['zpool.set'](name, prop, properties[prop])
if res['set']:
if name not in ret['changes']:
ret['changes'][name] = {}
ret['changes'][name][prop] = properties[prop]
else:
ret['result'] = False
if ret['comment'] == '':
ret['comment'] = 'The following properties were not updated:'
ret['comment'] = '{0} {1}'.format(ret['comment'], prop)
if ret['result']:
ret['comment'] = 'properties updated' if ret['changes'] else 'no update needed'
# import or create the pool (at least try to anyway)
else:
# import pool
if config['import']:
mod_res = __salt__['zpool.import'](
name,
force=config['force'],
dir=config['import_dirs'],
)
ret['result'] = mod_res['imported']
if ret['result']:
ret['changes'][name] = 'imported'
ret['comment'] = 'storage pool {0} was imported'.format(name)
# create pool
if not ret['result'] and vdevs:
log.debug('zpool.present::%s::creating', name)
# execute zpool.create
mod_res = __salt__['zpool.create'](
*vdevs,
force=config['force'],
properties=properties,
filesystem_properties=filesystem_properties
)
ret['result'] = mod_res['created']
if ret['result']:
ret['changes'][name] = 'created'
ret['comment'] = 'storage pool {0} was created'.format(name)
elif 'error' in mod_res:
ret['comment'] = mod_res['error']
else:
ret['comment'] = 'could not create storage pool {0}'.format(name)
# give up, we cannot import the pool and we do not have a layout to create it
if not ret['result'] and not vdevs:
ret['comment'] = 'storage pool {0} was not imported, no (valid) layout specified for creation'.format(name)
return ret
def absent(name, export=False, force=False):
'''
ensure storage pool is absent on the system
name : string
name of storage pool
export : boolean
export instead of destroy the zpool if present
force : boolean
force destroy or export
'''
ret = {'name': name,
'changes': {},
'result': None,
'comment': ''}
# log configuration
log.debug('zpool.absent::%s::config::force = %s', name, force)
log.debug('zpool.absent::%s::config::export = %s', name, export)
# ensure the pool is absent
if __salt__['zpool.exists'](name): # looks like we need to do some work
mod_res = {}
ret['result'] = False
# NOTE: handle test
if __opts__['test']:
ret['result'] = True
# NOTE: try to export the pool
elif export:
mod_res = __salt__['zpool.export'](name, force=force)
ret['result'] = mod_res['exported']
# NOTE: try to destroy the pool
else:
mod_res = __salt__['zpool.destroy'](name, force=force)
ret['result'] = mod_res['destroyed']
if ret['result']: # update the changes and comment
ret['changes'][name] = 'exported' if export else 'destroyed'
ret['comment'] = 'storage pool {0} was {1}'.format(name, ret['changes'][name])
elif 'error' in mod_res:
ret['comment'] = mod_res['error']
else: # we are looking good
ret['result'] = True
ret['comment'] = 'storage pool {0} is absent'.format(name)
return ret
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4