From 2215cd566238923c68aedbf9e82668ede0919528 Mon Sep 17 00:00:00 2001 From: Przemyslaw Kaminski Date: Tue, 13 Jan 2015 17:04:03 +0100 Subject: [PATCH] Add --remove option for fuel plugins This option deletes the plugin that was previously installed. Optionally == can be specified as in: fuel plugins --remove fuel_plugin_example==1.0.0 DocImpact related to blueprint cinder-neutron-plugins-in-fuel Change-Id: I8d8ac913c74710843c4a617fac214625af7eda1e --- fuelclient/cli/actions/plugins.py | 23 +++++ fuelclient/cli/arguments.py | 8 ++ fuelclient/objects/plugins.py | 61 +++++++++++- fuelclient/tests/test_plugins_action.py | 120 ++++++++++++++++++++++++ 4 files changed, 210 insertions(+), 2 deletions(-) diff --git a/fuelclient/cli/actions/plugins.py b/fuelclient/cli/actions/plugins.py index 83793d6..b688a61 100644 --- a/fuelclient/cli/actions/plugins.py +++ b/fuelclient/cli/actions/plugins.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +from fuelclient.cli import error + from fuelclient.cli.actions.base import Action import fuelclient.cli.arguments as Args from fuelclient.cli.formatting import format_table @@ -35,10 +37,12 @@ class PluginAction(Action): self.args = [ Args.get_list_arg("List all available plugins."), Args.get_plugin_install_arg("Install action"), + Args.get_plugin_remove_arg("Remove action"), Args.get_force_arg("Update action"), ] self.flag_func_map = ( ("install", self.install), + ("remove", self.remove), (None, self.list), ) @@ -65,3 +69,22 @@ class PluginAction(Action): results, "Plugin {0} was successfully installed.".format( params.install)) + + def remove(self, params): + """Remove plugin from environment + fuel plugins --remove plugin_sample + fuel plugins --remove plugin_sample==1.0.1 + """ + s = params.remove.split('==') + plugin_name = s[0] + plugin_version = None + if len(s) == 2: + plugin_version = s[1] + elif len(s) > 2: + raise error.ArgumentException( + 'Syntax: fuel plugins --remove fuel_plugin==1.0') + results = Plugins.remove_plugin( + plugin_name, plugin_version=plugin_version) + self.serializer.print_to_output( + results, + "Plugin {0} was successfully removed.".format(params.remove)) diff --git a/fuelclient/cli/arguments.py b/fuelclient/cli/arguments.py index 872a32d..d33a418 100644 --- a/fuelclient/cli/arguments.py +++ b/fuelclient/cli/arguments.py @@ -395,3 +395,11 @@ def get_plugin_install_arg(help_msg): flags=("--install",), help=help_msg ) + + +def get_plugin_remove_arg(help_msg): + return get_str_arg( + "remove", + flags=("--remove",), + help=help_msg + ) diff --git a/fuelclient/objects/plugins.py b/fuelclient/objects/plugins.py index a4b9846..babc220 100644 --- a/fuelclient/objects/plugins.py +++ b/fuelclient/objects/plugins.py @@ -13,6 +13,7 @@ # under the License. import os +import shutil import tarfile import yaml @@ -41,8 +42,45 @@ class Plugins(base.BaseObject): for member_name in plugin_tar.getnames(): if cls.metadata_config in member_name: return yaml.load(plugin_tar.extractfile(member_name).read()) - raise error.BadDataException("Tarfile {0} doesn't have {1}".format( - plugin_tar.name, cls.metadata_config)) + raise error.BadDataException( + "Tarfile {name} doesn't have {config}".format( + name=plugin_tar.name, config=cls.metadata_config)) + + @classmethod + def get_plugin(cls, plugin_name, plugin_version=None): + """Returns plugin fetched by name and optionally by version. + + If multiple plugin versions are found and version is not specified + error is returned. + + :returns: dictionary with plugin data + """ + checker = lambda p: p['name'] == plugin_name + if plugin_version is not None: + checker = lambda p: \ + (p['name'], p['version']) == (plugin_name, plugin_version) + plugins = filter(checker, cls.get_all_data()) + if len(plugins) == 0: + if plugin_version is None: + raise error.BadDataException( + 'Plugin {plugin_name} does not exist'.format( + plugin_name=plugin_name) + ) + else: + raise error.BadDataException( + 'Plugin {plugin_name}, version {plugin_version} does ' + 'not exist'.format( + plugin_name=plugin_name, + plugin_version=plugin_version) + ) + if len(plugins) > 1 and plugin_version is None: + raise error.BadDataException( + 'Multiple versions of plugin {plugin_name} found. ' + 'Please consider specifying a version in the ' + '{plugin_name}== format'.format( + plugin_name=plugin_name) + ) + return plugins[0] @classmethod def add_plugin(cls, plugin_meta, plugin_tar): @@ -68,3 +106,22 @@ class Plugins(base.BaseObject): finally: plugin_tar.close() return resp + + @classmethod + def remove_plugin(cls, plugin_name, plugin_version=None): + if not cls.validate_environment(): + raise error.WrongEnvironmentError( + 'Plugin can be removed only from master node.') + plugin = cls.get_plugin(plugin_name, plugin_version) + + resp = cls.connection.delete_request( + cls.class_instance_path.format(**plugin) + ) + + plugin_path = os.path.join( + EXTRACT_PATH, + '{name}-{version}'.format(**plugin) + ) + shutil.rmtree(plugin_path) + + return resp diff --git a/fuelclient/tests/test_plugins_action.py b/fuelclient/tests/test_plugins_action.py index 3784cbe..64c5803 100644 --- a/fuelclient/tests/test_plugins_action.py +++ b/fuelclient/tests/test_plugins_action.py @@ -60,3 +60,123 @@ class TestPluginsActions(base.UnitTestCase): ['fuel', 'plugins', '--install', '/tmp/sample.fp', '--force']) self.assertEqual(mrequests.post.call_count, 1) self.assertEqual(mrequests.put.call_count, 1) + + @patch('fuelclient.objects.plugins.os') + @patch('fuelclient.objects.plugins.shutil') + def test_remove_plugin_single(self, mshutil, mos, mrequests): + mos.path.exists.return_value = True + mresponse = Mock(status_code=201) + mresponse.json.return_value = [ + { + 'id': 1, + 'name': 'test', + 'version': '1.0.0', + } + ] + mrequests.get.return_value = mresponse + mrequests.delete.return_value = Mock(status_code=200) + + self.execute( + ['fuel', 'plugins', '--remove', 'test'] + ) + + self.assertEqual(mrequests.delete.call_count, 1) + self.assertEqual(mshutil.rmtree.call_count, 1) + self.assertEqual( + 'test-1.0.0', + mos.path.join.call_args[0][1]) + + @patch('fuelclient.objects.plugins.os') + @patch('fuelclient.objects.plugins.shutil') + def test_remove_plugin_multi(self, mshutil, mos, mrequests): + mos.path.exists.return_value = True + mresponse = Mock(status_code=201) + mresponse.json.return_value = [ + { + 'id': 1, + 'name': 'test', + 'version': '1.0.0', + }, + { + 'id': 2, + 'name': 'test', + 'version': '1.1.0', + } + ] + mrequests.get.return_value = mresponse + mrequests.delete.return_value = Mock(status_code=200) + + self.execute( + ['fuel', 'plugins', '--remove', 'test==1.0.0'] + ) + + self.assertEqual(mrequests.delete.call_count, 1) + self.assertEqual(mshutil.rmtree.call_count, 1) + + @patch('fuelclient.objects.plugins.os') + def test_remove_nonexisting_plugin(self, mos, mrequests): + mos.path.exists.return_value = True + mresponse = Mock(status_code=201) + mresponse.json.return_value = [ + { + 'id': 1, + 'name': 'test', + 'version': '1.0.0', + } + ] + mrequests.get.return_value = mresponse + + self.assertRaises( + SystemExit, + self.execute, + ['fuel', 'plugins', '--remove', 'test-fail'] + ) + + self.assertEqual(mrequests.delete.call_count, 0) + + @patch('fuelclient.objects.plugins.os') + def test_remove_when_multiple_versions(self, mos, mrequests): + mos.path.exists.return_value = True + mresponse = Mock(status_code=201) + mresponse.json.return_value = [ + { + 'id': 1, + 'name': 'test', + 'version': '1.0.0', + }, + { + 'id': 2, + 'name': 'test', + 'version': '1.1.0', + } + ] + mrequests.get.return_value = mresponse + + self.assertRaises( + SystemExit, + self.execute, + ['fuel', 'plugins', '--remove', 'test'] + ) + + self.assertEqual(mrequests.delete.call_count, 0) + + @patch('fuelclient.objects.plugins.os') + def test_remove_nonexisting_plugin_version(self, mos, mrequests): + mos.path.exists.return_value = True + mresponse = Mock(status_code=201) + mresponse.json.return_value = [ + { + 'id': 1, + 'name': 'test', + 'version': '1.0.0', + } + ] + mrequests.get.return_value = mresponse + + self.assertRaises( + SystemExit, + self.execute, + ['fuel', 'plugins', '--remove', 'test==1.1.0'] + ) + + self.assertEqual(mrequests.delete.call_count, 0)