From 7af83ce20d7bff123397c415aae7aa06877d31bb Mon Sep 17 00:00:00 2001 From: dixiaoli Date: Sat, 20 Feb 2016 15:32:37 +0800 Subject: [PATCH] Add OpenstackClient plugin for cluster node delete This change implements the "openstack cluster node delete" command Based on the existing senlin command: senlin node-delete Change-Id: I9c7e14a6370cd7e6a193e55407b73b71dd80bafe Blueprint: senlin-support-python-openstackclient --- senlinclient/osc/v1/node.py | 58 ++++++++++++++++ senlinclient/tests/unit/osc/v1/test_node.py | 74 +++++++++++++++++++++ setup.cfg | 1 + 3 files changed, 133 insertions(+) diff --git a/senlinclient/osc/v1/node.py b/senlinclient/osc/v1/node.py index ccf19162..7cbe732d 100644 --- a/senlinclient/osc/v1/node.py +++ b/senlinclient/osc/v1/node.py @@ -14,7 +14,9 @@ import logging import six +import sys +from cliff import command from cliff import lister from cliff import show from openstack import exceptions as sdk_exc @@ -22,6 +24,7 @@ from openstackclient.common import exceptions as exc from openstackclient.common import utils from senlinclient.common.i18n import _ +from senlinclient.common.i18n import _LI from senlinclient.common import utils as senlin_utils @@ -275,3 +278,58 @@ class UpdateNode(show.ShowOne): senlin_client.update_node(parsed_args.node, **attrs) return _show_node(senlin_client, node.id) + + +class DeleteNode(command.Command): + """Delete the node(s).""" + + log = logging.getLogger(__name__ + ".DeleteNode") + + def get_parser(self, prog_name): + parser = super(DeleteNode, self).get_parser(prog_name) + parser.add_argument( + 'node', + metavar='', + nargs='+', + help=_('Name or ID of node(s) to delete') + ) + parser.add_argument( + '--force', + action='store_true', + help=_('Skip yes/no prompt (assume yes)') + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + senlin_client = self.app.client_manager.clustering + + try: + if not parsed_args.force and sys.stdin.isatty(): + sys.stdout.write( + _("Are you sure you want to delete this node(s)" + " [y/N]?")) + prompt_response = sys.stdin.readline().lower() + if not prompt_response.startswith('y'): + return + except KeyboardInterrupt: # Ctrl-c + self.log.info(_LI('Ctrl-c detected.')) + return + except EOFError: # Ctrl-d + self.log.info(_LI('Ctrl-d detected')) + return + + failure_count = 0 + + for nid in parsed_args.node: + try: + senlin_client.delete_node(nid, False) + except Exception as ex: + failure_count += 1 + print(ex) + if failure_count: + raise exc.CommandError(_('Failed to delete %(count)s of the ' + '%(total)s specified node(s).') % + {'count': failure_count, + 'total': len(parsed_args.node)}) + print('Request accepted') diff --git a/senlinclient/tests/unit/osc/v1/test_node.py b/senlinclient/tests/unit/osc/v1/test_node.py index a63e9c8f..4bdfb8b3 100644 --- a/senlinclient/tests/unit/osc/v1/test_node.py +++ b/senlinclient/tests/unit/osc/v1/test_node.py @@ -12,6 +12,7 @@ import copy import mock +import six from openstack.cluster.v1 import node as sdk_node from openstack import exceptions as sdk_exc @@ -327,3 +328,76 @@ class TestNodeUpdate(TestNode): error = self.assertRaises(exc.CommandError, self.cmd.take_action, parsed_args) self.assertIn('Node not found: c6b8b252', str(error)) + + +class TestNodeDelete(TestNode): + def setUp(self): + super(TestNodeDelete, self).setUp() + self.cmd = osc_node.DeleteNode(self.app, None) + self.mock_client.delete_node = mock.Mock() + + def test_node_delete(self): + arglist = ['node1', 'node2', 'node3'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + self.mock_client.delete_node.assert_has_calls( + [mock.call('node1', False), mock.call('node2', False), + mock.call('node3', False)] + ) + + def test_node_delete_force(self): + arglist = ['node1', 'node2', 'node3', '--force'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + self.mock_client.delete_node.assert_has_calls( + [mock.call('node1', False), mock.call('node2', False), + mock.call('node3', False)] + ) + + def test_node_delete_not_found(self): + arglist = ['my_node'] + self.mock_client.delete_node.side_effect = sdk_exc.ResourceNotFound + parsed_args = self.check_parser(self.cmd, arglist, []) + error = self.assertRaises(exc.CommandError, self.cmd.take_action, + parsed_args) + self.assertIn('Failed to delete 1 of the 1 specified node(s).', + str(error)) + + def test_node_delete_one_found_one_not_found(self): + arglist = ['node1', 'node2'] + self.mock_client.delete_node.side_effect = ( + [None, sdk_exc.ResourceNotFound] + ) + parsed_args = self.check_parser(self.cmd, arglist, []) + error = self.assertRaises(exc.CommandError, + self.cmd.take_action, parsed_args) + self.mock_client.delete_node.assert_has_calls( + [mock.call('node1', False), mock.call('node2', False)] + ) + self.assertEqual('Failed to delete 1 of the 2 specified node(s).', + str(error)) + + @mock.patch('sys.stdin', spec=six.StringIO) + def test_node_delete_prompt_yes(self, mock_stdin): + arglist = ['my_node'] + mock_stdin.isatty.return_value = True + mock_stdin.readline.return_value = 'y' + parsed_args = self.check_parser(self.cmd, arglist, []) + + self.cmd.take_action(parsed_args) + + mock_stdin.readline.assert_called_with() + self.mock_client.delete_node.assert_called_with('my_node', + False) + + @mock.patch('sys.stdin', spec=six.StringIO) + def test_node_delete_prompt_no(self, mock_stdin): + arglist = ['my_node'] + mock_stdin.isatty.return_value = True + mock_stdin.readline.return_value = 'n' + parsed_args = self.check_parser(self.cmd, arglist, []) + + self.cmd.take_action(parsed_args) + + mock_stdin.readline.assert_called_with() + self.mock_client.delete_node.assert_not_called() diff --git a/setup.cfg b/setup.cfg index 8f78b62d..41f4ce0a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,7 @@ openstack.cli.extension = openstack.clustering.v1 = cluster_node_create = senlinclient.osc.v1.node:CreateNode + cluster_node_delete = senlinclient.osc.v1.node:DeleteNode cluster_node_list = senlinclient.osc.v1.node:ListNode cluster_node_show = senlinclient.osc.v1.node:ShowNode cluster_node_update = senlinclient.osc.v1.node:UpdateNode