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 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_client.py b/senlinclient/tests/unit/v1/test_client.py index 9b3b845..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() @@ -356,6 +373,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 60a2cd9..01b19bd 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() @@ -1136,6 +1155,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() @@ -1179,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 @@ -1188,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/client.py b/senlinclient/v1/client.py index 7573918..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) @@ -128,6 +134,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 73d4c20..95ea120 100644 --- a/senlinclient/v1/shell.py +++ b/senlinclient/v1/shell.py @@ -776,6 +776,26 @@ def do_cluster_policy_update(service, args): print('Request accepted by action: %s' % resp['action']) +@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).""" + 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='', nargs='+', + help=_('ID or name of cluster(s) to operate on.')) +def do_cluster_recover(service, args): + """Recover the cluster(s).""" + 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 @@ -940,6 +960,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 @@ -1092,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='', 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