Add unit tests for host commands

As part of getting away from using calls to the shell in the
tests, we will need code coverage of the commands library which
will no longer be used when the shell is no longer called. This
change is the first of serveral to provide unnit test coverage
for commands/host.

As a result of writing these tests, I needed to tweak a few things,
some to make mocking possible, and fix some minor issues:

- move delete confirmation prompt to its own method so it can
  be mocked.
- remove extra space from failed Host check message
- fix return code to 0 in Host setup, when the host is already setup.
- in shell, move inventory check to a method so it can be mocked.

Change-Id: I63cb1afd5313959a6fdda11e9c2b03317c60197a
This commit is contained in:
Steve Noyes 2018-05-30 16:23:39 -04:00
parent 5d8343a3b9
commit 8e960e4a2b
6 changed files with 281 additions and 13 deletions

View File

@ -40,12 +40,14 @@
check:
jobs:
- openstack-tox-pep8
- openstack-tox-py27
- kollacli-tox-mypy
- kollacli-tox-functional
- kollacli-tox-functional-py35
gate:
jobs:
- openstack-tox-pep8
- openstack-tox-py27
- kollacli-tox-mypy
- kollacli-tox-functional
- kollacli-tox-functional-py35

View File

@ -90,15 +90,10 @@ class HostDestroy(Command):
if parsed_args.removeimages:
remove_images = True
if include_data:
question = ('This will delete all containers and data'
', are you sure? (y/n)')
answer = raw_input(question)
while answer != 'y' and answer != 'n':
answer = raw_input(question)
if answer is 'n':
LOG.info('Aborting destroy')
return
if include_data and not self._is_ok_to_delete_data():
LOG.info('Aborting destroy')
return
verbose_level = self.app.options.verbose_level
job = CLIENT.host_destroy(hostnames, destroy_type,
@ -123,6 +118,14 @@ class HostDestroy(Command):
except Exception as e:
raise Exception(traceback.format_exc())
def _is_ok_to_delete_data(self):
question = ('This will delete all containers and data'
', are you sure? (y/n)')
answer = raw_input(question)
while answer != 'y' and answer != 'n':
answer = raw_input(question)
return True if answer == 'y' else False
class HostRemove(Command):
"""Remove host from openstack-kolla."""
@ -231,7 +234,7 @@ class HostCheck(Command):
status = u._('success')
msg = ''
if not info['success']:
status = u._('failed- ')
status = u._('failed-')
msg = info['msg']
all_ok = False
LOG.info(u._('Host {host}: {sts} {msg}')
@ -280,7 +283,7 @@ class HostSetup(Command):
LOG.info(
u._LI('Skipping setup of host ({host}) as '
'ssh check is ok.').format(host=hostname))
return True
return 0
if parsed_args.insecure:
password = parsed_args.insecure.strip()

View File

@ -41,7 +41,7 @@ class KollaCli(App):
inventory_path = os.path.join(get_kolla_cli_etc(),
INVENTORY_PATH)
if os.path.isfile(inventory_path) is False:
if not self._is_inventory_present(inventory_path):
err_string = u._(
'Required file ({inventory}) does not exist.\n'
'Please re-install the kollacli to '
@ -57,6 +57,9 @@ class KollaCli(App):
self.dump_stack_trace = False
def _is_inventory_present(self, inventory_path):
return os.path.isfile(inventory_path)
def main(argv=sys.argv[1:]):
shell = KollaCli()

View File

@ -0,0 +1,32 @@
# Copyright (c) 2018 OpenStack Foundation
#
# 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 testtools
from kolla_cli.api.host import Host
from kolla_cli.common.ansible.job import AnsibleJob
from kolla_cli import shell
class KollaCliUnitTest(testtools.TestCase):
def run_cli_command(self, command_string):
# return 0 if command succeeded, non-0 if failed
args = command_string.split()
return shell.main(args)
def get_fake_job(self):
return AnsibleJob(None, None, None, None)
def get_fake_host(self, hostname='foo'):
return Host(hostname)

View File

@ -0,0 +1,225 @@
# Copyright (c) 2018 OpenStack Foundation
#
# 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 mock
from kolla_cli.tests.unit.common import KollaCliUnitTest
class TestUnit(KollaCliUnitTest):
@mock.patch('kolla_cli.api.client.ClientApi.host_add')
@mock.patch('kolla_cli.shell.KollaCli._is_inventory_present',
return_value=True)
def test_host_add(self, _, mock_add):
hostname = 'foo'
ret = self.run_cli_command('host add %s' % hostname)
self.assertEqual(ret, 0)
mock_add.assert_called_once_with([hostname])
@mock.patch('kolla_cli.api.client.ClientApi.host_remove')
@mock.patch('kolla_cli.shell.KollaCli._is_inventory_present',
return_value=True)
def test_host_remove(self, _, mock_remove):
hostname = 'foo'
ret = self.run_cli_command('host remove %s' % hostname)
self.assertEqual(ret, 0)
mock_remove.assert_called_once_with([hostname])
@mock.patch('kolla_cli.api.client.ClientApi.host_get_all')
@mock.patch('kolla_cli.api.client.ClientApi.host_get')
@mock.patch('kolla_cli.shell.KollaCli._is_inventory_present',
return_value=True)
def test_host_list(self, _, mock_get, mock_get_all):
# get all hosts
mock_get_all.return_value = []
ret = self.run_cli_command('host list')
self.assertEqual(ret, 0)
mock_get_all.assert_called_once_with()
# get a specific host
hostname = 'foo'
mock_get.return_value = []
ret = self.run_cli_command('host list %s' % hostname)
self.assertEqual(ret, 0)
mock_get.assert_called_once_with([hostname])
@mock.patch('kolla_cli.commands.host.HostDestroy._is_ok_to_delete_data',
return_value='y')
@mock.patch('kolla_cli.common.ansible.job.AnsibleJob.get_status')
@mock.patch('kolla_cli.api.client.ClientApi.host_get_all')
@mock.patch('kolla_cli.api.client.ClientApi.host_destroy')
@mock.patch('kolla_cli.shell.KollaCli._is_inventory_present',
return_value=True)
def test_host_destroy(self, _, mock_destroy, mock_get_all,
mock_get_status, mock_prompt):
hostname = 'foo'
mock_get_all.return_value = [self.get_fake_host(hostname)]
mock_destroy.return_value = self.get_fake_job()
mock_get_status.return_value = 0
# default destroy hostname
ret = self.run_cli_command('host destroy %s' % hostname)
self.assertEqual(ret, 0)
mock_destroy.assert_called_once_with([hostname], 'kill', 1,
False, False)
# destroy all
mock_destroy.reset_mock()
ret = self.run_cli_command('host destroy all')
self.assertEqual(ret, 0)
mock_destroy.assert_called_once_with([hostname], 'kill', 1,
False, False)
# destroy --stop
mock_destroy.reset_mock()
ret = self.run_cli_command('host destroy %s --stop' % hostname)
self.assertEqual(ret, 0)
mock_destroy.assert_called_once_with([hostname], 'stop', 1,
False, False)
# destroy --includedata
mock_destroy.reset_mock()
ret = self.run_cli_command('host destroy %s --includedata' % hostname)
self.assertEqual(ret, 0)
mock_destroy.assert_called_once_with([hostname], 'kill', 1,
True, False)
# destroy --removeimages
mock_destroy.reset_mock()
ret = self.run_cli_command('host destroy %s --removeimages'
% hostname)
self.assertEqual(ret, 0)
mock_destroy.assert_called_once_with([hostname], 'kill', 1,
False, True)
@mock.patch('kolla_cli.commands.host.LOG.info')
@mock.patch('kolla_cli.api.client.ClientApi.host_get_all')
@mock.patch('kolla_cli.api.client.ClientApi.host_ssh_check')
@mock.patch('kolla_cli.shell.KollaCli._is_inventory_present',
return_value=True)
def test_host_ssh_check(self, _, mock_ssh_check, mock_get_all, mock_log):
hostname = 'foo'
check_ok_response = {hostname: {'success': True}}
check_bad_response = {hostname: {'success': False, 'msg': 'FAILED'}}
mock_get_all.return_value = [self.get_fake_host(hostname)]
# host check hostname (success)
mock_ssh_check.return_value = check_ok_response
ret = self.run_cli_command('host check %s' % hostname)
self.assertEqual(ret, 0)
mock_ssh_check.assert_called_once_with([hostname])
mock_log.assert_called_once_with('Host %s: success ' % hostname)
# host check all (success)
mock_ssh_check.reset_mock()
mock_log.reset_mock()
mock_ssh_check.return_value = check_ok_response
ret = self.run_cli_command('host check all')
self.assertEqual(ret, 0)
mock_ssh_check.assert_called_once_with([hostname])
mock_log.assert_called_once_with('Host %s: success ' % hostname)
# host check hostname (fail)
mock_ssh_check.reset_mock()
mock_log.reset_mock()
mock_ssh_check.return_value = check_bad_response
ret = self.run_cli_command('host check %s' % hostname)
self.assertEqual(ret, 1)
mock_ssh_check.assert_called_once_with([hostname])
mock_log.assert_called_once_with('Host %s: failed- FAILED' % hostname)
@mock.patch('kolla_cli.common.ansible.job.AnsibleJob.get_status')
@mock.patch('kolla_cli.commands.host.ClientApi.host_precheck')
@mock.patch('kolla_cli.api.client.ClientApi.host_get_all')
@mock.patch('kolla_cli.shell.KollaCli._is_inventory_present',
return_value=True)
def test_host_precheck(self, _, mock_get_all, mock_precheck,
mock_get_status):
hostname = 'foo'
mock_get_all.return_value = [self.get_fake_host(hostname)]
# host check hostname --predeploy (success)
mock_precheck.return_value = self.get_fake_job()
mock_get_status.return_value = 0
ret = self.run_cli_command('host check %s --predeploy' % hostname)
self.assertEqual(ret, 0)
mock_precheck.assert_called_once_with([hostname], 1)
# host check hostname --predeploy (fail)
mock_precheck.reset_mock()
mock_get_status.return_value = 1
ret = self.run_cli_command('host check %s --predeploy' % hostname)
self.assertEqual(ret, 1)
mock_precheck.assert_called_once_with([hostname], 1)
@mock.patch('kolla_cli.commands.host.HostSetup._get_yml_data')
@mock.patch('getpass.getpass')
@mock.patch('kolla_cli.commands.host.ClientApi.host_ssh_check')
@mock.patch('kolla_cli.commands.host.ClientApi.host_setup')
@mock.patch('kolla_cli.api.client.ClientApi.host_get_all')
@mock.patch('kolla_cli.shell.KollaCli._is_inventory_present',
return_value=True)
def test_host_setup(self, _, mock_get_all, mock_setup, mock_ssh_check,
mock_passwd, mock_yml):
password = 'PASSWORD'
hostname = 'foo'
mock_get_all.return_value = [self.get_fake_host(hostname)]
mock_passwd.return_value = password
# single host setup (host not yet setup)
mock_ssh_check.return_value = {hostname: {'success': False}}
ret = self.run_cli_command('host setup %s' % hostname)
self.assertEqual(ret, 0)
mock_ssh_check.assert_called_once_with([hostname])
mock_setup.assert_called_once_with({hostname: {'password': password}})
# single host setup --insecure (host already setup)
mock_ssh_check.reset_mock()
mock_setup.reset_mock()
mock_ssh_check.return_value = {hostname: {'success': True}}
ret = self.run_cli_command('host setup %s --insecure %s'
% (hostname, password))
self.assertEqual(ret, 0)
mock_ssh_check.assert_called_once_with([hostname])
mock_setup.assert_not_called()
# multi-host setup
mock_ssh_check.reset_mock()
mock_setup.reset_mock()
fake_path = '/bogus'
mock_yml.return_value = {hostname: {'password': password}}
ret = self.run_cli_command('host setup --file %s' % fake_path)
self.assertEqual(ret, 0)
mock_setup.assert_called_once_with({hostname: {'password': password}})
mock_yml.assert_called_once_with(fake_path)
mock_ssh_check.assert_not_called()
@mock.patch('kolla_cli.common.ansible.job.AnsibleJob.get_status')
@mock.patch('kolla_cli.api.client.ClientApi.host_get_all')
@mock.patch('kolla_cli.api.client.ClientApi.host_stop')
@mock.patch('kolla_cli.shell.KollaCli._is_inventory_present',
return_value=True)
def test_host_stop(self, _, mock_stop, mock_get_all,
mock_get_status):
hostname = 'foo'
mock_get_all.return_value = [self.get_fake_host(hostname)]
mock_get_status.return_value = 0
mock_stop.return_value = self.get_fake_job()
# host stop hostname
ret = self.run_cli_command('host stop %s' % hostname)
self.assertEqual(ret, 0)
mock_stop.assert_called_once_with([hostname], 1)
# host stop all
mock_stop.reset_mock()
ret = self.run_cli_command('host stop all')
self.assertEqual(ret, 0)
mock_stop.assert_called_once_with([hostname], 1)

View File

@ -1,7 +1,7 @@
[tox]
minversion = 1.6
skipsdist = True
envlist = pep8,mypy,functional,functional-py35
envlist = pep8,mypy,functional,functional-py35,py27
[testenv]
usedevelop=True
@ -17,6 +17,9 @@ commands =
find . -type f -name "*.py[c|o]" -delete
find . -type d -name "__pycache__" -delete
[testenv:py27]
commands = ostestr {posargs}
[testenv:functional]
whitelist_externals =
{[testenv]whitelist_externals}