From ed810946df3a818d1b50aea2c6f625f578da1cf8 Mon Sep 17 00:00:00 2001 From: Cindia-blue Date: Thu, 28 Jan 2016 21:13:27 +0800 Subject: [PATCH 1/5] Add node check and recover into CLI Expose check and recover for manual health management scenario Implements: blueprint support-health-management-customization Change-Id: Ib5f3c6710459d31831f842534acaae547336d23f --- senlinclient/tests/unit/v1/test_client.py | 17 ++++++++++ senlinclient/tests/unit/v1/test_shell.py | 40 +++++++++++++++++++++++ senlinclient/v1/client.py | 6 ++++ senlinclient/v1/shell.py | 36 ++++++++++++++++++++ 4 files changed, 99 insertions(+) diff --git a/senlinclient/tests/unit/v1/test_client.py b/senlinclient/tests/unit/v1/test_client.py index 9b3b845..56240e3 100644 --- a/senlinclient/tests/unit/v1/test_client.py +++ b/senlinclient/tests/unit/v1/test_client.py @@ -356,6 +356,23 @@ class ClientTest(testtools.TestCase): self.service.delete_node.assert_called_once_with( 'FAKE_ID', True) + def test_check_node(self, mock_conn): + mock_conn.return_value = self.conn + sc = client.Client() + + res = sc.check_node('FAKE_ID') + self.assertEqual(self.service.check_node.return_value, res) + self.service.check_node.assert_called_once_with('FAKE_ID') + + def test_recover_node(self, mock_conn): + mock_conn.return_value = self.conn + sc = client.Client() + + res = sc.recover_node('FAKE_ID') + self.assertEqual(self.service.recover_node.return_value, res) + self.service.recover_node.assert_called_once_with( + 'FAKE_ID') + def test_delete_node_ignore_missing(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index 5ad0fa7..84c571c 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -1142,6 +1142,46 @@ class ShellTest(testtools.TestCase): msg = _('Failed to delete some of the specified nodes.') self.assertEqual(msg, six.text_type(ex)) + def test_do_node_check(self): + service = mock.Mock() + args = self._make_args({'id': ['node1']}) + service.check_node = mock.Mock() + + sh.do_node_check(service, args) + + service.check_node.assert_called_once_with('node1') + + def test_do_node_check_not_found(self): + service = mock.Mock() + ex = exc.HTTPNotFound + service.check_node.side_effect = ex + + args = self._make_args({'id': ['node1']}) + ex = self.assertRaises(exc.CommandError, + sh.do_node_check, service, args) + msg = _('Failed to check some of the specified nodes.') + self.assertEqual(msg, six.text_type(ex)) + + def test_do_node_recover(self): + service = mock.Mock() + args = self._make_args({'id': ['node1']}) + service.check_node = mock.Mock() + + sh.do_node_recover(service, args) + + service.recover_node.assert_called_once_with('node1') + + def test_do_node_recover_not_found(self): + service = mock.Mock() + ex = exc.HTTPNotFound + service.recover_node.side_effect = ex + + args = self._make_args({'id': ['node1']}) + ex = self.assertRaises(exc.CommandError, + sh.do_node_recover, service, args) + msg = _('Failed to recover some of the specified nodes.') + self.assertEqual(msg, six.text_type(ex)) + @mock.patch.object(sh, '_show_node') def test_do_node_update(self, mock_show): service = mock.Mock() diff --git a/senlinclient/v1/client.py b/senlinclient/v1/client.py index 7573918..c6c6bf6 100644 --- a/senlinclient/v1/client.py +++ b/senlinclient/v1/client.py @@ -128,6 +128,12 @@ class Client(object): def delete_node(self, node, ignore_missing=True): return self.service.delete_node(node, ignore_missing) + def check_node(self, node, **params): + return self.service.check_node(node, **params) + + def recover_node(self, node, **params): + return self.service.recover_node(node, **params) + def receivers(self, **queries): return self.service.receivers(**queries) diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index 75e7812..5e1bef9 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -948,6 +948,42 @@ def do_node_update(service, args): _show_node(service, node.id) +@utils.arg('id', metavar='', nargs='+', + help=_('ID of node(s) to check.')) +def do_node_check(service, args): + """Check the node(s).""" + failure_count = 0 + + for nid in args.id: + try: + service.check_node(nid) + except exc.HTTPNotFound: + failure_count += 1 + print('Node id "%s" not found' % nid) + if failure_count > 0: + msg = _('Failed to check some of the specified nodes.') + raise exc.CommandError(msg) + print('Request accepted') + + +@utils.arg('id', metavar='', nargs='+', + help=_('ID of node(s) to recover.')) +def do_node_recover(service, args): + """Recover the node(s).""" + failure_count = 0 + + for nid in args.id: + try: + service.recover_node(nid) + except exc.HTTPNotFound: + failure_count += 1 + print('Node id "%s" not found' % nid) + if failure_count > 0: + msg = _('Failed to recover some of the specified nodes.') + raise exc.CommandError(msg) + print('Request accepted') + + # RECEIVERS From b7fb9a793eff85c2cc302813f0156037ff358c0b Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 4 Mar 2016 17:48:14 +0000 Subject: [PATCH 2/5] Updated from global requirements Change-Id: I8d5a7a3562487317cc37c9d437956b8aef2c8086 --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8e9e3f1..1b408d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,9 +4,9 @@ Babel>=1.3 # BSD pbr>=1.6 # Apache-2.0 -cliff!=1.16.0,>=1.15.0 # Apache-2.0 +cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0 PrettyTable<0.8,>=0.7 # BSD -openstacksdk>=0.7.4 # Apache-2.0 +openstacksdk>=0.8.1 # Apache-2.0 oslo.i18n>=2.1.0 # Apache-2.0 oslo.serialization>=1.10.0 # Apache-2.0 oslo.utils>=3.5.0 # Apache-2.0 From 0dbef6f95ad42a0f0312c5c8fa63fbaee3a158e5 Mon Sep 17 00:00:00 2001 From: Cindia-blue Date: Mon, 14 Mar 2016 10:39:55 +0800 Subject: [PATCH 3/5] Add cluster check and recover into CLI Expose check and recover for manual health management scenario Change-Id: Ib669d4e759d74f9dc7054e91d7247d50c90d41ed Implements: blueprint support-health-management-customization --- senlinclient/tests/unit/v1/test_client.py | 17 +++++++++++++++++ senlinclient/tests/unit/v1/test_shell.py | 19 +++++++++++++++++++ senlinclient/v1/client.py | 6 ++++++ senlinclient/v1/shell.py | 16 ++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/senlinclient/tests/unit/v1/test_client.py b/senlinclient/tests/unit/v1/test_client.py index 56240e3..605300a 100644 --- a/senlinclient/tests/unit/v1/test_client.py +++ b/senlinclient/tests/unit/v1/test_client.py @@ -304,6 +304,23 @@ class ClientTest(testtools.TestCase): self.service.cluster_update_policy.assert_called_once_with( 'FOO', 'BAR', foo='bar') + def test_check_cluster(self, mock_conn): + mock_conn.return_value = self.conn + sc = client.Client() + + res = sc.check_cluster('FAKE_CLUSTER_ID') + self.assertEqual(self.service.check_cluster.return_value, res) + self.service.check_cluster.assert_called_once_with('FAKE_CLUSTER_ID') + + def test_recover_cluster(self, mock_conn): + mock_conn.return_value = self.conn + sc = client.Client() + + res = sc.recover_cluster('FAKE_CLUSTER_ID') + self.assertEqual(self.service.recover_cluster.return_value, res) + self.service.recover_cluster.assert_called_once_with( + 'FAKE_CLUSTER_ID') + def test_nodes(self, mock_conn): mock_conn.return_value = self.conn sc = client.Client() diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index ede9396..d311f42 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -1029,6 +1029,25 @@ class ShellTest(testtools.TestCase): service.cluster_update_policy.assert_called_once_with('C1', 'policy1', **kwargs) + def test_do_cluster_check(self): + service = mock.Mock() + args = self._make_args({'id': ['cluster1']}) + service.check_cluster = mock.Mock() + service.check_cluster.return_value = {'action': 'action_id'} + sh.do_cluster_check(service, args) + + service.check_cluster.assert_called_once_with(['cluster1']) + + def test_do_cluster_recover(self): + service = mock.Mock() + args = self._make_args({'id': ['cluster1']}) + service.recover_cluster = mock.Mock() + service.recover_cluster.return_value = {'action': 'action_id'} + + sh.do_cluster_recover(service, args) + + service.recover_cluster.assert_called_once_with(['cluster1']) + @mock.patch.object(utils, 'print_list') def test_do_node_list(self, mock_print): service = mock.Mock() diff --git a/senlinclient/v1/client.py b/senlinclient/v1/client.py index c6c6bf6..334f1d9 100644 --- a/senlinclient/v1/client.py +++ b/senlinclient/v1/client.py @@ -113,6 +113,12 @@ class Client(object): def cluster_update_policy(self, cluster, policy, **attrs): return self.service.cluster_update_policy(cluster, policy, **attrs) + def check_cluster(self, cluster, **params): + return self.service.check_cluster(cluster, **params) + + def recover_cluster(self, cluster, **params): + return self.service.recover_cluster(cluster, **params) + def nodes(self, **queries): return self.service.nodes(**queries) diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index ab9a2f2..b9cc55a 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -776,6 +776,22 @@ def do_cluster_policy_update(service, args): print('Request accepted by action: %s' % resp['action']) +@utils.arg('id', metavar='', + help=_('ID or name of cluster to operate on.')) +def do_cluster_check(service, args): + """Check the cluster(s).""" + resp = service.check_cluster(args.id) + print('Request accepted by action: %s' % resp['action']) + + +@utils.arg('id', metavar='', + help=_('ID or name of cluster to operate on.')) +def do_cluster_recover(service, args): + """Recover the cluster(s).""" + resp = service.recover_cluster(args.id) + print('Request accepted by action: %s' % resp['action']) + + # NODES From 2fca4157decd5b5023a7df0e59bd54def1b27984 Mon Sep 17 00:00:00 2001 From: xiaolihope Date: Tue, 15 Mar 2016 15:12:15 +0800 Subject: [PATCH 4/5] Add OSC command and enable more than one arguements This change added two commands as OSC plugin: - openstack cluster check - openstack cluster recover Based on the existing senlin command: - senlin cluster-check - senlin cluster-recover And enabled accepting more than one arguements. Change-Id: I26d615f788d9b9c8fa20e9005dcdd1e59ba2b1d1 --- senlinclient/osc/v1/cluster.py | 54 +++++++++++++++++++ .../tests/unit/osc/v1/test_cluster.py | 54 +++++++++++++++++++ senlinclient/tests/unit/v1/test_shell.py | 4 +- senlinclient/v1/shell.py | 20 ++++--- setup.cfg | 2 + 5 files changed, 124 insertions(+), 10 deletions(-) diff --git a/senlinclient/osc/v1/cluster.py b/senlinclient/osc/v1/cluster.py index fd89e7d..ebb0a6d 100644 --- a/senlinclient/osc/v1/cluster.py +++ b/senlinclient/osc/v1/cluster.py @@ -718,3 +718,57 @@ class ClusterNodeDel(command.Command): node_ids = parsed_args.nodes.split(',') resp = senlin_client.cluster_del_nodes(parsed_args.cluster, node_ids) print('Request accepted by action: %s' % resp['action']) + + +class CheckCluster(command.Command): + """Check the cluster(s).""" + log = logging.getLogger(__name__ + ".CheckCluster") + + def get_parser(self, prog_name): + parser = super(CheckCluster, self).get_parser(prog_name) + parser.add_argument( + 'cluster', + metavar='', + nargs='+', + help=_('ID or name of cluster(s) to operate on.') + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + senlin_client = self.app.client_manager.clustering + for cid in parsed_args.cluster: + try: + resp = senlin_client.check_cluster(cid) + except sdk_exc.ResourceNotFound: + raise exc.CommandError(_('Cluster not found: %s') % cid) + print('Cluster check request on cluster %(cid)s is accepted by ' + 'action %(action)s.' + % {'cid': cid, 'action': resp['action']}) + + +class RecoverCluster(command.Command): + """Recover the cluster(s).""" + log = logging.getLogger(__name__ + ".RecoverCluster") + + def get_parser(self, prog_name): + parser = super(RecoverCluster, self).get_parser(prog_name) + parser.add_argument( + 'cluster', + metavar='', + nargs='+', + help=_('ID or name of cluster(s) to operate on.') + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + senlin_client = self.app.client_manager.clustering + for cid in parsed_args.cluster: + try: + resp = senlin_client.recover_cluster(cid) + except sdk_exc.ResourceNotFound: + raise exc.CommandError(_('Cluster not found: %s') % cid) + print('Cluster recover request on cluster %(cid)s is accepted by ' + 'action %(action)s.' + % {'cid': cid, 'action': resp['action']}) diff --git a/senlinclient/tests/unit/osc/v1/test_cluster.py b/senlinclient/tests/unit/osc/v1/test_cluster.py index c133c49..093417d 100644 --- a/senlinclient/tests/unit/osc/v1/test_cluster.py +++ b/senlinclient/tests/unit/osc/v1/test_cluster.py @@ -739,3 +739,57 @@ class TestClusterNodeDel(TestCluster): self.mock_client.cluster_del_nodes.assert_called_with( 'my_cluster', ['node1', 'node2']) + + +class TestClusterCheck(TestCluster): + response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} + + def setUp(self): + super(TestClusterCheck, self).setUp() + self.cmd = osc_cluster.CheckCluster(self.app, None) + self.mock_client.check_cluster = mock.Mock( + return_value=self.response) + + def test_cluster_check(self): + arglist = ['cluster1', 'cluster2', 'cluster3'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + self.mock_client.check_cluster.assert_has_calls( + [mock.call('cluster1'), mock.call('cluster2'), + mock.call('cluster3')] + ) + + def test_cluster_check_not_found(self): + arglist = ['cluster1'] + self.mock_client.check_cluster.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('Cluster not found: cluster1', str(error)) + + +class TestClusterRecover(TestCluster): + response = {"action": "8bb476c3-0f4c-44ee-9f64-c7b0260814de"} + + def setUp(self): + super(TestClusterRecover, self).setUp() + self.cmd = osc_cluster.RecoverCluster(self.app, None) + self.mock_client.recover_cluster = mock.Mock( + return_value=self.response) + + def test_cluster_recoverk(self): + arglist = ['cluster1', 'cluster2', 'cluster3'] + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + self.mock_client.recover_cluster.assert_has_calls( + [mock.call('cluster1'), mock.call('cluster2'), + mock.call('cluster3')] + ) + + def test_cluster_recover_not_found(self): + arglist = ['cluster1'] + self.mock_client.recover_cluster.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('Cluster not found: cluster1', str(error)) diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index d311f42..36237c3 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -1036,7 +1036,7 @@ class ShellTest(testtools.TestCase): service.check_cluster.return_value = {'action': 'action_id'} sh.do_cluster_check(service, args) - service.check_cluster.assert_called_once_with(['cluster1']) + service.check_cluster.assert_called_once_with('cluster1') def test_do_cluster_recover(self): service = mock.Mock() @@ -1046,7 +1046,7 @@ class ShellTest(testtools.TestCase): sh.do_cluster_recover(service, args) - service.recover_cluster.assert_called_once_with(['cluster1']) + service.recover_cluster.assert_called_once_with('cluster1') @mock.patch.object(utils, 'print_list') def test_do_node_list(self, mock_print): diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index b9cc55a..00c561a 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -776,20 +776,24 @@ def do_cluster_policy_update(service, args): print('Request accepted by action: %s' % resp['action']) -@utils.arg('id', metavar='', - help=_('ID or name of cluster to operate on.')) +@utils.arg('id', metavar='', nargs='+', + help=_('ID or name of cluster(s) to operate on.')) def do_cluster_check(service, args): """Check the cluster(s).""" - resp = service.check_cluster(args.id) - print('Request accepted by action: %s' % resp['action']) + for cid in args.id: + resp = service.check_cluster(cid) + print('Cluster check request on cluster %(cid)s is accepted by ' + 'action %(action)s.' % {'cid': cid, 'action': resp['action']}) -@utils.arg('id', metavar='', - help=_('ID or name of cluster to operate on.')) +@utils.arg('id', metavar='', nargs='+', + help=_('ID or name of cluster(s) to operate on.')) def do_cluster_recover(service, args): """Recover the cluster(s).""" - resp = service.recover_cluster(args.id) - print('Request accepted by action: %s' % resp['action']) + for cid in args.id: + resp = service.recover_cluster(cid) + print('Cluster recover request on cluster %(cid)s is accepted by ' + 'action %(action)s.' % {'cid': cid, 'action': resp['action']}) # NODES diff --git a/setup.cfg b/setup.cfg index 132c646..6836168 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ openstack.clustering.v1 = cluster_action_list = senlinclient.osc.v1.action:ListAction cluster_action_show = senlinclient.osc.v1.action:ShowAction cluster_build_info = senlinclient.osc.v1.build_info:BuildInfo + cluster_check = senlinclient.osc.v1.cluster:CheckCluster cluster_create = senlinclient.osc.v1.cluster:CreateCluster cluster_delete = senlinclient.osc.v1.cluster:DeleteCluster cluster_event_list = senlinclient.osc.v1.event:ListEvent @@ -69,6 +70,7 @@ openstack.clustering.v1 = cluster_receiver_delete = senlinclient.osc.v1.receiver:DeleteReceiver cluster_receiver_list = senlinclient.osc.v1.receiver:ListReceiver cluster_receiver_show = senlinclient.osc.v1.receiver:ShowReceiver + cluster_recover = senlinclient.osc.v1.cluster:RecoverCluster cluster_resize = senlinclient.osc.v1.cluster:ResizeCluster cluster_scale_in = senlinclient.osc.v1.cluster:ScaleInCluster cluster_scale_out = senlinclient.osc.v1.cluster:ScaleOutCluster From 25494ac29be0272e7d612e48a1d76f14b36c7db7 Mon Sep 17 00:00:00 2001 From: tengqm Date: Thu, 17 Mar 2016 22:49:07 -0400 Subject: [PATCH 5/5] Fix event list sorting At the client side, we should not attempt to do sorting again when listing events. Events are already sorted based on timestamp and id. Closes-Bug: #1558882 Change-Id: I2c42123bbde532080d752769a0d821f107e3aad1 --- senlinclient/tests/unit/v1/test_shell.py | 4 +--- senlinclient/v1/shell.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/senlinclient/tests/unit/v1/test_shell.py b/senlinclient/tests/unit/v1/test_shell.py index 36237c3..01b19bd 100644 --- a/senlinclient/tests/unit/v1/test_shell.py +++ b/senlinclient/tests/unit/v1/test_shell.py @@ -1238,7 +1238,6 @@ class ShellTest(testtools.TestCase): del queries['filters'] queries['action'] = 'NODE_DELETE' args = self._make_args(args) - sortby_index = None formatters = {} events = mock.Mock() service.events.return_value = events @@ -1247,8 +1246,7 @@ class ShellTest(testtools.TestCase): service.events.assert_called_once_with(**queries) mock_print.assert_called_once_with(events, fields, - formatters=formatters, - sortby_index=sortby_index) + formatters=formatters) @mock.patch.object(utils, 'print_dict') def test_do_event_show(self, mock_print): diff --git a/senlinclient/v1/shell.py b/senlinclient/v1/shell.py index 00c561a..95ea120 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -1148,15 +1148,13 @@ def do_event_list(service, args): if args.filters: queries.update(utils.format_parameters(args.filters)) - sortby_index = None if args.sort else 0 formatters = {} if not args.full_id: formatters['id'] = lambda x: x.id[:8] formatters['obj_id'] = lambda x: x.obj_id[:8] if x.obj_id else '' events = service.events(**queries) - utils.print_list(events, fields, formatters=formatters, - sortby_index=sortby_index) + utils.print_list(events, fields, formatters=formatters) @utils.arg('id', metavar='',