diff --git a/blazarclient/command.py b/blazarclient/command.py index e605130..432fbdc 100644 --- a/blazarclient/command.py +++ b/blazarclient/command.py @@ -26,6 +26,11 @@ from cliff import show from blazarclient import exception from blazarclient import utils +HEX_ELEM = '[0-9A-Fa-f]' +UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', + HEX_ELEM + '{4}', HEX_ELEM + '{4}', + HEX_ELEM + '{12}']) + class OpenStackCommand(command.Command): """Base class for OpenStack commands.""" @@ -65,6 +70,8 @@ class BlazarCommand(OpenStackCommand): json_indent = None resource = None allow_names = True + name_key = None + id_pattern = UUID_PATTERN def __init__(self, app, app_args): super(BlazarCommand, self).__init__(app, app_args) @@ -164,7 +171,9 @@ class UpdateCommand(BlazarCommand): if self.allow_names: res_id = utils.find_resource_id_by_name_or_id(blazar_client, self.resource, - parsed_args.id) + parsed_args.id, + self.name_key, + self.id_pattern) else: res_id = parsed_args.id resource_manager = getattr(blazar_client, self.resource) @@ -199,7 +208,9 @@ class DeleteCommand(BlazarCommand): if self.allow_names: res_id = utils.find_resource_id_by_name_or_id(blazar_client, self.resource, - parsed_args.id) + parsed_args.id, + self.name_key, + self.id_pattern) else: res_id = parsed_args.id resource_manager.delete(res_id) @@ -284,7 +295,9 @@ class ShowCommand(BlazarCommand, show.ShowOne): if self.allow_names: res_id = utils.find_resource_id_by_name_or_id(blazar_client, self.resource, - parsed_args.id) + parsed_args.id, + self.name_key, + self.id_pattern) else: res_id = parsed_args.id diff --git a/blazarclient/tests/v1/shell_commands/test_hosts.py b/blazarclient/tests/v1/shell_commands/test_hosts.py new file mode 100644 index 0000000..611ed7d --- /dev/null +++ b/blazarclient/tests/v1/shell_commands/test_hosts.py @@ -0,0 +1,244 @@ +# Copyright (c) 2018 NTT +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import mock + +from blazarclient import shell +from blazarclient import tests +from blazarclient.v1.shell_commands import hosts + + +class CreateHostTest(tests.TestCase): + + def setUp(self): + super(CreateHostTest, self).setUp() + self.create_host = hosts.CreateHost(shell.BlazarShell(), mock.Mock()) + + def test_args2body(self): + args = argparse.Namespace( + name='test-host', + extra_capabilities=[ + 'extra_key1=extra_value1', + 'extra_key2=extra_value2', + ] + ) + + expected = { + 'name': 'test-host', + 'extra_key1': 'extra_value1', + 'extra_key2': 'extra_value2', + } + + ret = self.create_host.args2body(args) + self.assertDictEqual(ret, expected) + + +class UpdateHostTest(tests.TestCase): + + def create_update_command(self, list_value): + mock_host_manager = mock.Mock() + mock_host_manager.list.return_value = list_value + + mock_client = mock.Mock() + mock_client.host = mock_host_manager + + blazar_shell = shell.BlazarShell() + blazar_shell.client = mock_client + return hosts.UpdateHost(blazar_shell, mock.Mock()), mock_host_manager + + def test_update_host(self): + list_value = [ + {'id': '101', 'hypervisor_hostname': 'host-1'}, + {'id': '201', 'hypervisor_hostname': 'host-2'}, + ] + update_host, host_manager = self.create_update_command(list_value) + args = argparse.Namespace( + id='101', + extra_capabilities=[ + 'key1=value1', + 'key2=value2' + ]) + expected = { + 'values': { + 'key1': 'value1', + 'key2': 'value2' + } + } + update_host.run(args) + host_manager.update.assert_called_once_with('101', **expected) + + def test_update_host_with_name(self): + list_value = [ + {'id': '101', 'hypervisor_hostname': 'host-1'}, + {'id': '201', 'hypervisor_hostname': 'host-2'}, + ] + update_host, host_manager = self.create_update_command(list_value) + args = argparse.Namespace( + id='host-1', + extra_capabilities=[ + 'key1=value1', + 'key2=value2' + ]) + expected = { + 'values': { + 'key1': 'value1', + 'key2': 'value2' + } + } + update_host.run(args) + host_manager.update.assert_called_once_with('101', **expected) + + def test_update_host_with_name_startwith_number(self): + list_value = [ + {'id': '101', 'hypervisor_hostname': '1-host'}, + {'id': '201', 'hypervisor_hostname': '2-host'}, + ] + update_host, host_manager = self.create_update_command(list_value) + args = argparse.Namespace( + id='1-host', + extra_capabilities=[ + 'key1=value1', + 'key2=value2' + ]) + expected = { + 'values': { + 'key1': 'value1', + 'key2': 'value2' + } + } + update_host.run(args) + host_manager.update.assert_called_once_with('101', **expected) + + +class ShowHostTest(tests.TestCase): + + def create_show_command(self, list_value, get_value): + mock_host_manager = mock.Mock() + mock_host_manager.list.return_value = list_value + mock_host_manager.get.return_value = get_value + + mock_client = mock.Mock() + mock_client.host = mock_host_manager + + blazar_shell = shell.BlazarShell() + blazar_shell.client = mock_client + return hosts.ShowHost(blazar_shell, mock.Mock()), mock_host_manager + + def test_show_host(self): + list_value = [ + {'id': '101', 'hypervisor_hostname': 'host-1'}, + {'id': '201', 'hypervisor_hostname': 'host-2'}, + ] + get_value = { + 'id': '101', 'hypervisor_hostname': 'host-1'} + + show_host, host_manager = self.create_show_command(list_value, + get_value) + + args = argparse.Namespace(id='101') + expected = [('hypervisor_hostname', 'id'), ('host-1', '101')] + + ret = show_host.get_data(args) + self.assertEqual(ret, expected) + + host_manager.get.assert_called_once_with('101') + + def test_show_host_with_name(self): + list_value = [ + {'id': '101', 'hypervisor_hostname': 'host-1'}, + {'id': '201', 'hypervisor_hostname': 'host-2'}, + ] + get_value = { + 'id': '101', 'hypervisor_hostname': 'host-1'} + + show_host, host_manager = self.create_show_command(list_value, + get_value) + + args = argparse.Namespace(id='host-1') + expected = [('hypervisor_hostname', 'id'), ('host-1', '101')] + + ret = show_host.get_data(args) + self.assertEqual(ret, expected) + + host_manager.get.assert_called_once_with('101') + + def test_show_host_with_name_startwith_number(self): + list_value = [ + {'id': '101', 'hypervisor_hostname': '1-host'}, + {'id': '201', 'hypervisor_hostname': '2-host'}, + ] + get_value = { + 'id': '101', 'hypervisor_hostname': '1-host'} + + show_host, host_manager = self.create_show_command(list_value, + get_value) + args = argparse.Namespace(id='1-host') + expected = [('hypervisor_hostname', 'id'), ('1-host', '101')] + + ret = show_host.get_data(args) + self.assertEqual(ret, expected) + + host_manager.get.assert_called_once_with('101') + + +class DeleteHostTest(tests.TestCase): + + def create_delete_command(self, list_value): + mock_host_manager = mock.Mock() + mock_host_manager.list.return_value = list_value + + mock_client = mock.Mock() + mock_client.host = mock_host_manager + + blazar_shell = shell.BlazarShell() + blazar_shell.client = mock_client + return hosts.DeleteHost(blazar_shell, mock.Mock()), mock_host_manager + + def test_delete_host(self): + list_value = [ + {'id': '101', 'hypervisor_hostname': 'host-1'}, + {'id': '201', 'hypervisor_hostname': 'host-2'}, + ] + delete_host, host_manager = self.create_delete_command(list_value) + + args = argparse.Namespace(id='101') + delete_host.run(args) + + host_manager.delete.assert_called_once_with('101') + + def test_delete_host_with_name(self): + list_value = [ + {'id': '101', 'hypervisor_hostname': 'host-1'}, + {'id': '201', 'hypervisor_hostname': 'host-2'}, + ] + delete_host, host_manager = self.create_delete_command(list_value) + + args = argparse.Namespace(id='host-1') + delete_host.run(args) + + host_manager.delete.assert_called_once_with('101') + + def test_delete_host_with_name_startwith_number(self): + list_value = [ + {'id': '101', 'hypervisor_hostname': '1-host'}, + {'id': '201', 'hypervisor_hostname': '2-host'}, + ] + delete_host, host_manager = self.create_delete_command(list_value) + + args = argparse.Namespace(id='1-host') + delete_host.run(args) + + host_manager.delete.assert_called_once_with('101') diff --git a/blazarclient/utils.py b/blazarclient/utils.py index 51398ac..0dd94a2 100644 --- a/blazarclient/utils.py +++ b/blazarclient/utils.py @@ -22,10 +22,6 @@ from oslo_serialization import jsonutils as json from blazarclient import exception from blazarclient.i18n import _ -HEX_ELEM = '[0-9A-Fa-f]' -UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', - HEX_ELEM + '{4}', HEX_ELEM + '{4}', - HEX_ELEM + '{12}']) ELAPSED_TIME_REGEX = '^(\d+)([s|m|h|d])$' LEASE_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.%f' @@ -106,9 +102,10 @@ def get_item_properties(item, fields, mixed_case_fields=None, formatters=None): return tuple(row) -def find_resource_id_by_name_or_id(client, resource, name_or_id): +def find_resource_id_by_name_or_id(client, resource, name_or_id, + name_key, id_pattern): resource_manager = getattr(client, resource) - is_id = re.match(UUID_PATTERN, name_or_id) + is_id = re.match(id_pattern, name_or_id) if is_id: resources = resource_manager.list() for resource in resources: @@ -116,17 +113,18 @@ def find_resource_id_by_name_or_id(client, resource, name_or_id): return name_or_id raise exception.BlazarClientException('No resource found with ID %s' % name_or_id) - return _find_resource_id_by_name(client, resource, name_or_id) + return _find_resource_id_by_name(client, resource, name_or_id, name_key) -def _find_resource_id_by_name(client, resource, name): +def _find_resource_id_by_name(client, resource, name, name_key): resource_manager = getattr(client, resource) resources = resource_manager.list() named_resources = [] + key = name_key if name_key else 'name' for resource in resources: - if resource['name'] == name: + if resource[key] == name: named_resources.append(resource['id']) if len(named_resources) > 1: raise exception.NoUniqueMatch(message="There are more than one " diff --git a/blazarclient/v1/shell_commands/hosts.py b/blazarclient/v1/shell_commands/hosts.py index 166eb3e..b5d832f 100644 --- a/blazarclient/v1/shell_commands/hosts.py +++ b/blazarclient/v1/shell_commands/hosts.py @@ -17,6 +17,8 @@ import logging from blazarclient import command +HOST_ID_PATTERN = '^[0-9]+$' + class ListHosts(command.ListCommand): """Print a list of hosts.""" @@ -39,9 +41,8 @@ class ShowHost(command.ShowCommand): """Show host details.""" resource = 'host' json_indent = 4 - # NOTE(sbauza): We can't find by name as there is currently no column - # called 'name' but rather 'hypervisor_hostname' - allow_names = False + name_key = 'hypervisor_hostname' + id_pattern = HOST_ID_PATTERN log = logging.getLogger(__name__ + '.ShowHost') @@ -85,8 +86,9 @@ class UpdateHost(command.UpdateCommand): """Update attributes of a host.""" resource = 'host' json_indent = 4 - allow_names = False log = logging.getLogger(__name__ + '.UpdateHost') + name_key = 'hypervisor_hostname' + id_pattern = HOST_ID_PATTERN def get_parser(self, prog_name): parser = super(UpdateHost, self).get_parser(prog_name) @@ -115,7 +117,6 @@ class UpdateHost(command.UpdateCommand): class DeleteHost(command.DeleteCommand): """Delete a host.""" resource = 'host' - # NOTE(sbauza): We can't find by name as there is currently no column - # called 'name' but rather 'hypervisor_hostname' - allow_names = False log = logging.getLogger(__name__ + '.DeleteHost') + name_key = 'hypervisor_hostname' + id_pattern = HOST_ID_PATTERN