diff --git a/doc/ref/configuration/minion.rst b/doc/ref/configuration/minion.rst index 6f5acaffde47..f34d04d49e11 100644 --- a/doc/ref/configuration/minion.rst +++ b/doc/ref/configuration/minion.rst @@ -893,6 +893,20 @@ minion. Since this grain is expensive, it is disabled by default. iscsi_grains: True +.. conf_minion:: nvme_grains + +``nvme_grains`` +------------------------ + +Default: ``False`` + +The ``nvme_grains`` setting will enable the ``nvme_nqn`` grain on the +minion. Since this grain is expensive, it is disabled by default. + +.. code-block:: yaml + + nvme_grains: True + .. conf_minion:: mine_enabled ``mine_enabled`` diff --git a/salt/grains/nvme.py b/salt/grains/nvme.py new file mode 100644 index 000000000000..34726914b5b4 --- /dev/null +++ b/salt/grains/nvme.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +''' +Grains for NVMe Qualified Names (NQN). + +.. versionadded:: Flourine + +To enable these grains set `nvme_grains: True`. + +.. code-block:: yaml + + nvme_grains: True +''' +# Import Python libs +from __future__ import absolute_import, print_function, unicode_literals + +import errno +import logging + +# Import Salt libs +import salt.utils.files +import salt.utils.path +import salt.utils.platform + +__virtualname__ = 'nvme' + +# Get logging started +log = logging.getLogger(__name__) + + +def __virtual__(): + if __opts__.get('nvme_grains', False) is False: + return False + return __virtualname__ + + +def nvme_nqn(): + ''' + Return NVMe NQN + ''' + grains = {} + grains['nvme_nqn'] = False + if salt.utils.platform.is_linux(): + grains['nvme_nqn'] = _linux_nqn() + return grains + + +def _linux_nqn(): + ''' + Return NVMe NQN from a Linux host. + ''' + ret = [] + + initiator = '/etc/nvme/hostnqn' + try: + with salt.utils.files.fopen(initiator, 'r') as _nvme: + for line in _nvme: + line = line.strip() + if line.startswith('nqn.'): + ret.append(line) + except IOError as ex: + if ex.errno != errno.ENOENT: + log.debug("Error while accessing '%s': %s", initiator, ex) + + return ret diff --git a/tests/unit/grains/test_nvme.py b/tests/unit/grains/test_nvme.py new file mode 100644 index 000000000000..aaa2f0004cfe --- /dev/null +++ b/tests/unit/grains/test_nvme.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Simon Dodsley ` +''' +# Import Python libs +from __future__ import absolute_import, print_function, unicode_literals +import errno +import textwrap + +# Import Salt Testing Libs +from tests.support.unit import TestCase, skipIf +from tests.support.mock import ( + patch, + mock_open, + MagicMock, + NO_MOCK, + NO_MOCK_REASON +) + +# Import Salt Libs +import salt.grains.nvme as nvme + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class NvmeGrainsTestCase(TestCase): + ''' + Test cases for nvme grains + ''' + + def test_linux_nvme_nqn_grains(self): + _nvme_file = textwrap.dedent('''\ + nqn.2014-08.org.nvmexpress:fc_lif:uuid:2cd61a74-17f9-4c22-b350-3020020c458d + ''') + + with patch('salt.utils.files.fopen', mock_open(read_data=_nvme_file)): + nqn = nvme._linux_nqn() + + assert isinstance(nqn, list) + assert len(nqn) == 1 + assert nqn == ['nqn.2014-08.org.nvmexpress:fc_lif:uuid:2cd61a74-17f9-4c22-b350-3020020c458d'] + + @patch('salt.utils.files.fopen', MagicMock(side_effect=IOError(errno.EPERM, + 'The cables are not the same length.'))) + @patch('salt.grains.nvme.log', MagicMock()) + def test_linux_nqn_non_root(self): + ''' + Test if linux_nqn is running on salt-master as non-root + and handling access denial properly. + :return: + ''' + assert nvme._linux_nqn() == [] + nvme.log.debug.assert_called() + assert 'Error while accessing' in nvme.log.debug.call_args[0][0] + assert 'cables are not the same' in nvme.log.debug.call_args[0][2].strerror + assert nvme.log.debug.call_args[0][2].errno == errno.EPERM + assert nvme.log.debug.call_args[0][1] == '/etc/nvme/hostnqn' + + @patch('salt.utils.files.fopen', MagicMock(side_effect=IOError(errno.ENOENT, ''))) + @patch('salt.grains.nvme.log', MagicMock()) + def test_linux_nqn_no_nvme_initiator(self): + ''' + Test if linux_nqn is running on salt-master as root. + nvme initiator is not there accessible or is not supported. + :return: + ''' + assert nvme._linux_nqn() == [] + nvme.log.debug.assert_not_called()