Skip to content

Commit

Permalink
Merge pull request #33984 from jfindlay/disk_capacity
Browse files Browse the repository at this point in the history
Add docs and tests to disk state
  • Loading branch information
Mike Place committed Jun 14, 2016
2 parents fa5efb6 + 6cbe31e commit 53baae6
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 58 deletions.
97 changes: 75 additions & 22 deletions salt/states/disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,34 @@
'''
Disk monitoring state
Monitor the state of disk resources
Monitor the state of disk resources.
The ``disk.status`` function can be used to report that the used space of a
filesystem is within the specified limits.
.. code-block:: sls
used_space:
disk.status:
- name: /dev/xda1
- maximum: 79%
- minumum: 11%
It can be used with an ``onfail`` requisite, for example, to take additional
action in response to or in preparation for other states.
.. code-block:: sls
storage_threshold:
disk.status:
- name: /dev/xda1
- maximum: 97%
clear_cache:
cmd.run:
- name: rm -r /var/cache/app
- onfail:
- disk: storage_threshold
'''
from __future__ import absolute_import

Expand All @@ -14,9 +41,37 @@
]


def _validate_percent(name, value):
'''
Validate ``name`` as an integer in the range [0, 100]
'''
comment = ''
# Must be integral
try:
if isinstance(value, string_types):
value = value.strip('%')
value = int(value)
except (TypeError, ValueError):
comment += '{0} must be an integer '.format(name)
# Must be in percent range
else:
if value < 0 or value > 100:
comment += '{0} must be in the range [0, 100] '.format(name)
return value, comment


def status(name, maximum=None, minimum=None):
'''
Return the current disk usage stats for the named mount point
name
Filesystem with which to check used space
minimum
The required minimum amount of used space in percent
maximum
The required maximum amount of used space in percent
'''
# Monitoring state, no changes will be made so no test interface needed
ret = {'name': name,
Expand All @@ -26,38 +81,36 @@ def status(name, maximum=None, minimum=None):
'data': {}} # Data field for monitoring state

data = __salt__['disk.usage']()

# Validate name
if name not in data:
ret['result'] = False
ret['comment'] += 'Named disk mount not present '
return ret
# Validate extrema
if maximum:
try:
if isinstance(maximum, string_types):
maximum = int(maximum.strip('%'))
except Exception:
ret['comment'] += 'Max argument must be an integer '
maximum, comment = _validate_percent('maximum', maximum)
ret['comment'] += comment
if minimum:
try:
if isinstance(minimum, string_types):
minimum = int(minimum.strip('%'))
except Exception:
ret['comment'] += 'Min argument must be an integer '
if minimum and maximum:
minimum, comment = _validate_percent('minimum', minimum)
ret['comment'] += comment
if minimum is not None and maximum is not None:
if minimum >= maximum:
ret['comment'] += 'Min must be less than max'
ret['comment'] += 'Min must be less than max '
if ret['comment']:
return ret
cap = int(data[name]['capacity'].strip('%'))

capacity = int(data[name]['capacity'].strip('%'))
ret['data'] = data[name]
if minimum:
if cap < minimum:
ret['comment'] = 'Disk is below minimum of {0} at {1}'.format(
minimum, cap)
if minimum is not None:
if capacity < minimum:
ret['comment'] = 'Disk used space is below minimum of {0}% at {1}%'.format(
minimum, capacity)
return ret
if maximum:
if cap > maximum:
ret['comment'] = 'Disk is above maximum of {0} at {1}'.format(
maximum, cap)
if maximum is not None:
if capacity > maximum:
ret['comment'] = 'Disk used space is above maximum of {0}% at {1}%'.format(
maximum, capacity)
return ret
ret['comment'] = 'Disk in acceptable range'
ret['result'] = True
Expand Down
188 changes: 152 additions & 36 deletions tests/unit/states/disk_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Jayesh Kariya <jayeshk@saltstack.com>`
Tests for disk state
'''
# Import Python libs
from __future__ import absolute_import
Expand All @@ -26,46 +26,162 @@
@skipIf(NO_MOCK, NO_MOCK_REASON)
class DiskTestCase(TestCase):
'''
Test cases for salt.states.disk
Test disk state
'''
# 'status' function tests: 1
def setUp(self):
'''
setup common test info
'''
self.mock_data = {
'/': {
'1K-blocks': '41147472',
'available': '37087976',
'capacity': '6%',
'filesystem': '/dev/xvda1',
'used': '2172880'},
'/dev': {
'1K-blocks': '10240',
'available': '10240',
'capacity': '0%',
'filesystem': 'udev',
'used': '0'},
'/run': {
'1K-blocks': '410624',
'available': '379460',
'capacity': '8%',
'filesystem': 'tmpfs',
'used': '31164'},
'/sys/fs/cgroup': {
'1K-blocks': '1026556',
'available': '1026556',
'capacity': '0%',
'filesystem': 'tmpfs',
'used': '0'}
}
self.__salt__ = {
'disk.usage': MagicMock(return_value=self.mock_data),
}

def test_status_missing(self):
'''
Test disk.status when name not found
'''
mock_fs = '/mnt/cheese'
mock_ret = {'name': mock_fs,
'result': False,
'comment': 'Named disk mount not present ',
'changes': {},
'data': {}}

with patch.dict(disk.__salt__, self.__salt__):
ret = disk.status(mock_fs)
self.assertEqual(ret, mock_ret)

def test_status_type_error(self):
'''
Test disk.status with incorrectly formatted arguments
'''
mock_fs = '/'
mock_ret = {'name': mock_fs,
'result': False,
'comment': 'maximum must be an integer ',
'changes': {},
'data': {}}

with patch.dict(disk.__salt__, self.__salt__):
ret = disk.status(mock_fs, maximum=r'e^{i\pi}')
self.assertEqual(ret, mock_ret)

with patch.dict(disk.__salt__, self.__salt__):
mock_ret['comment'] = 'minimum must be an integer '
ret = disk.status(mock_fs, minimum=r'\cos\pi + i\sin\pi')
self.assertEqual(ret, mock_ret)

def test_status_range_error(self):
'''
Test disk.status with excessive extrema
'''
mock_fs = '/'
mock_ret = {'name': mock_fs,
'result': False,
'comment': 'maximum must be in the range [0, 100] ',
'changes': {},
'data': {}}

with patch.dict(disk.__salt__, self.__salt__):
ret = disk.status(mock_fs, maximum='-1')
self.assertEqual(ret, mock_ret)

with patch.dict(disk.__salt__, self.__salt__):
mock_ret['comment'] = 'minimum must be in the range [0, 100] '
ret = disk.status(mock_fs, minimum='101')
self.assertEqual(ret, mock_ret)

def test_status_inverted_range(self):
'''
Test disk.status when minimum > maximum
'''
mock_fs = '/'
mock_ret = {'name': mock_fs,
'result': False,
'comment': 'Min must be less than max ',
'changes': {},
'data': {}}

with patch.dict(disk.__salt__, self.__salt__):
ret = disk.status(mock_fs, maximum='0', minimum='1')
self.assertEqual(ret, mock_ret)

def test_status_threshold(self):
'''
Test disk.status when filesystem triggers thresholds
'''
mock_min = 100
mock_max = 0
mock_fs = '/'
mock_used = int(self.mock_data[mock_fs]['capacity'].strip('%'))
mock_ret = {'name': mock_fs,
'result': False,
'comment': '',
'changes': {},
'data': self.mock_data[mock_fs]}

with patch.dict(disk.__salt__, self.__salt__):
mock_ret['comment'] = 'Disk used space is below minimum of {0}% at {1}%'.format(
mock_min,
mock_used
)
ret = disk.status(mock_fs, minimum=mock_min)
self.assertEqual(ret, mock_ret)

with patch.dict(disk.__salt__, self.__salt__):
mock_ret['comment'] = 'Disk used space is above maximum of {0}% at {1}%'.format(
mock_max,
mock_used
)
ret = disk.status(mock_fs, maximum=mock_max)
self.assertEqual(ret, mock_ret)

def test_status(self):
'''
Test to return the current disk usage stats for the named mount point
Test disk.status when filesystem meets thresholds
'''
name = 'mydisk'

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

mock = MagicMock(side_effect=[[], [name], {name: {'capacity': '8 %'}},
{name: {'capacity': '22 %'}},
{name: {'capacity': '15 %'}}])
with patch.dict(disk.__salt__, {'disk.usage': mock}):
comt = ('Named disk mount not present ')
ret.update({'comment': comt})
self.assertDictEqual(disk.status(name), ret)

comt = ('Min must be less than max')
ret.update({'comment': comt})
self.assertDictEqual(disk.status(name, '10 %', '20 %'), ret)

comt = ('Disk is below minimum of 10 at 8')
ret.update({'comment': comt, 'data': {'capacity': '8 %'}})
self.assertDictEqual(disk.status(name, '20 %', '10 %'), ret)

comt = ('Disk is above maximum of 20 at 22')
ret.update({'comment': comt, 'data': {'capacity': '22 %'}})
self.assertDictEqual(disk.status(name, '20 %', '10 %'), ret)

comt = ('Disk in acceptable range')
ret.update({'comment': comt, 'result': True,
'data': {'capacity': '15 %'}})
self.assertDictEqual(disk.status(name, '20 %', '10 %'), ret)
mock_min = 0
mock_max = 100
mock_fs = '/'
mock_ret = {'name': mock_fs,
'result': True,
'comment': 'Disk in acceptable range',
'changes': {},
'data': self.mock_data[mock_fs]}

with patch.dict(disk.__salt__, self.__salt__):
ret = disk.status(mock_fs, minimum=mock_min)
self.assertEqual(ret, mock_ret)

with patch.dict(disk.__salt__, self.__salt__):
ret = disk.status(mock_fs, maximum=mock_max)
self.assertEqual(ret, mock_ret)


if __name__ == '__main__':
Expand Down

0 comments on commit 53baae6

Please sign in to comment.