diff --git a/fuelclient/objects/plugins.py b/fuelclient/objects/plugins.py index 3682cab..1bcd342 100644 --- a/fuelclient/objects/plugins.py +++ b/fuelclient/objects/plugins.py @@ -15,6 +15,7 @@ import abc import os import shutil +import subprocess import sys import tarfile @@ -27,10 +28,10 @@ from fuelclient.cli import error from fuelclient.objects import base from fuelclient import utils - +IS_MASTER = None +FUEL_PACKAGE = 'fuel' PLUGINS_PATH = '/var/www/nailgun/plugins/' METADATA_MASK = '/var/www/nailgun/plugins/*/metadata.yaml' -VERSIONS_PATH = '/etc/fuel/version.yaml' def raise_error_if_not_master(): @@ -38,9 +39,25 @@ def raise_error_if_not_master(): :raises: error.WrongEnvironmentError """ - if not os.path.exists(VERSIONS_PATH): - raise error.WrongEnvironmentError( - 'Action can be performed from Fuel master node only.') + msg_tail = 'Action can be performed from Fuel master node only.' + global IS_MASTER + if IS_MASTER is None: + IS_MASTER = False + rpm_exec = utils.find_exec('rpm') + if not rpm_exec: + msg = 'Command "rpm" not found. ' + msg_tail + raise error.WrongEnvironmentError(msg) + command = [rpm_exec, '-q', FUEL_PACKAGE] + p = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + p.communicate() + if p.poll() == 0: + IS_MASTER = True + if not IS_MASTER: + msg = 'Package "fuel" is not installed. ' + msg_tail + raise error.WrongEnvironmentError(msg) def master_only(f): diff --git a/fuelclient/tests/unit/v1/test_plugins_object.py b/fuelclient/tests/unit/v1/test_plugins_object.py index e7a524b..f4f7328 100644 --- a/fuelclient/tests/unit/v1/test_plugins_object.py +++ b/fuelclient/tests/unit/v1/test_plugins_object.py @@ -14,10 +14,13 @@ # License for the specific language governing permissions and limitations # under the License. +import fixtures +import mock from mock import MagicMock from mock import patch from fuelclient.cli import error +from fuelclient.objects import plugins from fuelclient.objects.plugins import Plugins from fuelclient.objects.plugins import PluginV1 from fuelclient.objects.plugins import PluginV2 @@ -410,3 +413,56 @@ class TestPluginsObject(base.UnitTestCase): self.assertEqual(self.plugin.get_plugin('name2', '1.0.0'), {'name': 'name2', 'version': '1.0.0'}) get_mock.assert_called_once_with() + + @patch('fuelclient.objects.plugins.utils.find_exec') + @patch('fuelclient.objects.plugins.subprocess.Popen') + def test_raise_error_if_not_master(self, mpop, mfe): + plugins.IS_MASTER = None + process = MagicMock() + mfe.return_value = '/bin/rpm' + mpop.return_value = process + process.poll.return_value = 0 + self.assertIsNone(plugins.raise_error_if_not_master()) + mpop.assert_called_once_with( + ['/bin/rpm', '-q', plugins.FUEL_PACKAGE], + stdout=mock.ANY, stderr=mock.ANY) + process.poll.assert_called_once_with() + process.communicate.assert_called_once_with() + + @patch('fuelclient.objects.plugins.utils.find_exec') + @patch('fuelclient.objects.plugins.subprocess.Popen') + def test_raise_error_if_not_master_fuel_not_installed(self, mpop, mfe): + plugins.IS_MASTER = None + process = MagicMock() + mpop.return_value = process + process.poll.return_value = 1 + self.assertRaises(error.WrongEnvironmentError, + plugins.raise_error_if_not_master) + + @patch('fuelclient.objects.plugins.utils.find_exec') + def test_raise_error_if_not_master_rpm_not_found(self, mfe): + plugins.IS_MASTER = None + mfe.return_value = None + self.assertRaises(error.WrongEnvironmentError, + plugins.raise_error_if_not_master) + + @patch('fuelclient.objects.plugins.os.access') + @patch('fuelclient.objects.plugins.os.path.isfile') + def test_find_exec(self, misf, macc): + misf.side_effect = [False, True, True] + macc.side_effect = [False, True] + # The combination will be like: + # 1) /foo/program does not exist + # 2) /bar/program does exist but isn't executable + # 3) /baz/program does exist and is executable + # So the function is to search 'program' in paths /foo, /bar, /baz + # and return /baz/program + self.useFixture(fixtures.EnvironmentVariable('PATH', '/foo:/bar:/baz')) + self.assertEqual('/baz/program', plugins.utils.find_exec('program')) + + @patch('fuelclient.objects.plugins.os.access') + @patch('fuelclient.objects.plugins.os.path.isfile') + def test_find_exec_not_found(self, misf, macc): + misf.return_value = False + self.useFixture(fixtures.EnvironmentVariable('PATH', '/foo')) + self.assertIsNone(plugins.utils.find_exec('program')) diff --git a/fuelclient/utils.py b/fuelclient/utils.py index 6aba841..a7dd88c 100644 --- a/fuelclient/utils.py +++ b/fuelclient/utils.py @@ -161,3 +161,15 @@ def str_to_unicode(string): """ return string if six.PY3 else string.decode(sys.getfilesystemencoding()) + + +def find_exec(program): + """Tries to find an executable in PATHs. + + :param str program: Name of executable to find + """ + for path in os.getenv('PATH').split(os.pathsep): + path = path.strip('"') + candidate = os.path.join(path, program) + if os.path.isfile(candidate) and os.access(candidate, os.X_OK): + return candidate