Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Backport 52699) btrfs: Add properties state #53439

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 140 additions & 13 deletions salt/states/btrfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import logging
import os.path
import tempfile
import traceback

from salt.exceptions import CommandExecutionError
from salt.ext import six
Expand All @@ -41,13 +42,14 @@
__virtualname__ = 'btrfs'


def _mount(device):
def _mount(device, use_default):
'''
Mount the device in a temporary place.
'''
opts = 'defaults' if use_default else 'subvol=/'
dest = tempfile.mkdtemp()
res = __states__['mount.mounted'](dest, device=device, fstype='btrfs',
opts='subvol=/', persist=False)
opts=opts, persist=False)
if not res['result']:
log.error('Cannot mount device %s in %s', device, dest)
_umount(dest)
Expand Down Expand Up @@ -104,6 +106,7 @@ def __mount_device(action):
def wrapper(*args, **kwargs):
name = kwargs['name']
device = kwargs['device']
use_default = kwargs.get('use_default', False)

ret = {
'name': name,
Expand All @@ -112,17 +115,20 @@ def wrapper(*args, **kwargs):
'comment': ['Some error happends during the operation.'],
}
try:
dest = _mount(device)
if not dest:
msg = 'Device {} cannot be mounted'.format(device)
ret['comment'].append(msg)
kwargs['__dest'] = dest
if device:
dest = _mount(device, use_default)
if not dest:
msg = 'Device {} cannot be mounted'.format(device)
ret['comment'].append(msg)
kwargs['__dest'] = dest
ret = action(*args, **kwargs)
except Exception as e:
log.exception('Encountered error mounting %s', device)
ret['comment'].append(six.text_type(e))
except Exception:
tb = six.text_type(traceback.format_exc())
log.exception('Exception captured in wrapper %s', tb)
ret['comment'].append(tb)
finally:
_umount(dest)
if device:
_umount(dest)
return ret
return wrapper

Expand Down Expand Up @@ -175,7 +181,7 @@ def subvolume_created(name, device, qgroupids=None, set_default=False,
if __opts__['test']:
ret['result'] = None
if not exists:
ret['comment'].append('Subvolume {} will be created'.format(name))
ret['changes'][name] = 'Subvolume {} will be created'.format(name)
return ret

if not exists:
Expand Down Expand Up @@ -239,7 +245,7 @@ def subvolume_deleted(name, device, commit=False, __dest=None):
if __opts__['test']:
ret['result'] = None
if exists:
ret['comment'].append('Subvolume {} will be removed'.format(name))
ret['changes'][name] = 'Subvolume {} will be removed'.format(name)
return ret

# If commit is set, we wait until all is over
Expand All @@ -256,3 +262,124 @@ def subvolume_deleted(name, device, commit=False, __dest=None):

ret['result'] = True
return ret


def _diff_properties(expected, current):
'''Calculate the difference between the current and the expected
properties

* 'expected' is expressed in a dictionary like: {'property': value}

* 'current' contains the same format retuned by 'btrfs.properties'

If the property is not available, will throw an exception.

'''
difference = {}
for _property, value in expected.items():
current_value = current[_property]['value']
if value is False and current_value == 'N/A':
needs_update = False
elif value != current_value:
needs_update = True
else:
needs_update = False
if needs_update:
difference[_property] = value
return difference


@__mount_device
def properties(name, device, use_default=False, __dest=None, **properties):
'''
Makes sure that a list of properties are set in a subvolume, file
or device.

name
Name of the object to change

device
Device where the object lives, if None, the device will be in
name

use_default
If True, this subvolume will be resolved to the default
subvolume assigned during the create operation

properties
Dictionary of properties

Valid properties are 'ro', 'label' or 'compression'. Check the
documentation to see where those properties are valid for each
object.

'''
ret = {
'name': name,
'result': False,
'changes': {},
'comment': [],
}

# 'name' will have always the name of the object that we want to
# change, but if the object is a device, we do not repeat it again
# in 'device'. This makes device sometimes optional.
if device:
if os.path.isabs(name):
path = os.path.join(__dest, os.path.relpath(name, os.path.sep))
else:
path = os.path.join(__dest, name)
else:
path = name

if not os.path.exists(path):
ret['comment'].append('Object {} not found'.format(name))
return ret

# Convert the booleans to lowercase
properties = {k: v if type(v) is not bool else str(v).lower()
for k, v in properties.items()}

current_properties = {}
try:
current_properties = __salt__['btrfs.properties'](path)
except CommandExecutionError as e:
ret['comment'].append('Error reading properties from {}'.format(name))
ret['comment'].append('Current error {}'.format(e))
return ret

try:
properties_to_set = _diff_properties(properties, current_properties)
except KeyError:
ret['comment'].append('Some property not found in {}'.format(name))
return ret

if __opts__['test']:
ret['result'] = None
if properties_to_set:
ret['changes'] = properties_to_set
else:
msg = 'No properties will be changed in {}'.format(name)
ret['comment'].append(msg)
return ret
aplanas marked this conversation as resolved.
Show resolved Hide resolved

if properties_to_set:
_properties = ','.join(
'{}={}'.format(k, v) for k, v in properties_to_set.items())
__salt__['btrfs.properties'](path, set=_properties)

current_properties = __salt__['btrfs.properties'](path)
properties_failed = _diff_properties(properties, current_properties)
if properties_failed:
msg = 'Properties {} failed to be changed in {}'.format(
properties_failed, name)
ret['comment'].append(msg)
return ret

ret['comment'].append('Properties changed in {}'.format(name))
ret['changes'] = properties_to_set
else:
ret['comment'].append('Properties not changed in {}'.format(name))

ret['result'] = True
aplanas marked this conversation as resolved.
Show resolved Hide resolved
return ret
Loading