From 820c7c488f95c6843aee414fa964891805fa358b Mon Sep 17 00:00:00 2001 From: Vitalii Myhal Date: Sat, 12 Mar 2016 14:26:45 -0600 Subject: [PATCH] Introduced command 'env redeploy' in v2 version of fuelclient Example: `fuel2 env redeploy ` Fixed deployment tests. Fuel-web repo has been changed from 'stackforge' to 'openstack'. Partial-Bug: 1540558 Change-Id: I6bc18ff672bc119c523841033fde4bbacb9290fd Depends-On: Ibc89fdbfbd0a36a890412cd8e861d35bcf930690 --- fuelclient/commands/environment.py | 25 +++++- fuelclient/consts.py | 17 ++++ fuelclient/tests/functional/base.py | 77 +++++++++++++++++-- fuelclient/tests/functional/v1/test_client.py | 52 +++++++------ fuelclient/tests/functional/v2/test_client.py | 56 ++++++++++++++ fuelclient/tests/unit/v2/cli/test_env.py | 7 ++ fuelclient/v1/environment.py | 6 ++ setup.cfg | 1 + tox.ini | 2 +- 9 files changed, 213 insertions(+), 30 deletions(-) create mode 100644 fuelclient/tests/functional/v2/test_client.py diff --git a/fuelclient/commands/environment.py b/fuelclient/commands/environment.py index c5fad0c0..98d14c8e 100644 --- a/fuelclient/commands/environment.py +++ b/fuelclient/commands/environment.py @@ -201,14 +201,35 @@ class EnvDeploy(EnvMixIn, base.BaseCommand): parser.add_argument('id', type=int, - help='Id of the nailgun entity to be processed.') + help='Id of the environment to be deployed.') return parser def take_action(self, parsed_args): task_id = self.client.deploy_changes(parsed_args.id) - msg = 'Deploy task with id {t} for the environment {e} '\ + msg = 'Deployment task with id {t} for the environment {e} '\ + 'has been started.\n'.format(t=task_id, e=parsed_args.id) + + self.app.stdout.write(msg) + + +class EnvRedeploy(EnvMixIn, base.BaseCommand): + """Redeploys changes on the specified environment.""" + + def get_parser(self, prog_name): + parser = super(EnvRedeploy, self).get_parser(prog_name) + + parser.add_argument('id', + type=int, + help='Id of the environment to be redeployed.') + + return parser + + def take_action(self, parsed_args): + task_id = self.client.redeploy_changes(parsed_args.id) + + msg = 'Deployment task with id {t} for the environment {e} '\ 'has been started.\n'.format(t=task_id, e=parsed_args.id) self.app.stdout.write(msg) diff --git a/fuelclient/consts.py b/fuelclient/consts.py index 583dc629..f76aebc0 100644 --- a/fuelclient/consts.py +++ b/fuelclient/consts.py @@ -14,4 +14,21 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import namedtuple + + +def Enum(*values, **kwargs): + names = kwargs.get('names') + if names: + return namedtuple('Enum', names)(*values) + return namedtuple('Enum', values)(*values) + + SERIALIZATION_FORMAT_FLAG = 'serialization_format' + +TASK_STATUSES = Enum( + 'error', + 'pending', + 'ready', + 'running' +) diff --git a/fuelclient/tests/functional/base.py b/fuelclient/tests/functional/base.py index 349652cf..1933830b 100644 --- a/fuelclient/tests/functional/base.py +++ b/fuelclient/tests/functional/base.py @@ -12,24 +12,27 @@ # License for the specific language governing permissions and limitations # under the License. +import json import logging import os +import re import shutil import subprocess import sys import tempfile +import time -from oslotest import base as oslo_base - +from fuelclient import consts from fuelclient.objects import Release +from oslotest import base as oslo_base logging.basicConfig(stream=sys.stderr) log = logging.getLogger("CliTest.ExecutionLog") log.setLevel(logging.DEBUG) -class CliExectutionResult(object): +class CliExecutionResult(object): def __init__(self, process_handle, out, err): self.return_code = process_handle.returncode self.stdout = out @@ -45,6 +48,8 @@ class CliExectutionResult(object): class BaseTestCase(oslo_base.BaseTestCase): + + handler = '' nailgun_root = os.environ.get('NAILGUN_ROOT', '/tmp/fuel_web/nailgun') def setUp(self): @@ -91,7 +96,7 @@ class BaseTestCase(oslo_base.BaseTestCase): def run_cli_command(self, command_line, check_errors=True, env=os.environ.copy()): - command_args = [" ".join(('fuel', command_line))] + command_args = [" ".join((self.handler, command_line))] process_handle = subprocess.Popen( command_args, stdout=subprocess.PIPE, @@ -100,7 +105,7 @@ class BaseTestCase(oslo_base.BaseTestCase): env=env ) out, err = process_handle.communicate() - result = CliExectutionResult(process_handle, out, err) + result = CliExecutionResult(process_handle, out, err) log.debug("command_args: '%s',stdout: '%s', stderr: '%s'", command_args[0], out, err) if check_errors: @@ -129,6 +134,12 @@ class BaseTestCase(oslo_base.BaseTestCase): call = self.run_cli_command(command, check_errors=check_errors) self.assertEqual(call.stdout, msg) + def check_for_stdout_by_regexp(self, command, pattern, check_errors=True): + call = self.run_cli_command(command, check_errors=check_errors) + result = re.search(pattern, call.stdout) + self.assertIsNotNone(result) + return result + def check_for_stderr(self, command, msg, check_errors=True): call = self.run_cli_command(command, check_errors=check_errors) self.assertIn(msg, call.stderr) @@ -147,3 +158,59 @@ class BaseTestCase(oslo_base.BaseTestCase): def check_number_of_rows_in_table(self, command, number_of_rows): output = self.run_cli_command(command) self.assertEqual(len(output.stdout.split("\n")), number_of_rows + 3) + + def _get_task_info(self, task_id): + """Get info about task with given ID. + + :param task_id: Task ID + :type task_id: str or int + :return: Task info + :rtype: dict + """ + return {} + + def wait_task_ready(self, task_id, timeout=60, interval=3): + """Wait for changing task status to 'ready'. + + :param task_id: Task ID + :type task_id: str or int + :param timeout: Max time of waiting, in seconds + :type timeout: int + :param interval: Interval of getting task info, in seconds + :type interval: int + """ + wait_until_in_statuses = (consts.TASK_STATUSES.running, + consts.TASK_STATUSES.pending) + timer = time.time() + while True: + task = self._get_task_info(task_id) + status = task.get('status', '') + if status not in wait_until_in_statuses: + self.assertEqual(status, consts.TASK_STATUSES.ready) + break + + if time.time() - timer > timeout: + raise Exception( + "Task '{0}' seems to be hanged".format(task['name']) + ) + time.sleep(interval) + + +class CLIv1TestCase(BaseTestCase): + + handler = 'fuel' + + def _get_task_info(self, task_id): + command = "task --task {0} --json".format(str(task_id)) + call = self.run_cli_command(command) + return json.loads(call.stdout)[0] + + +class CLIv2TestCase(BaseTestCase): + + handler = 'fuel2' + + def _get_task_info(self, task_id): + command = "task show -f json {0}".format(str(task_id)) + call = self.run_cli_command(command) + return json.loads(call.stdout) diff --git a/fuelclient/tests/functional/v1/test_client.py b/fuelclient/tests/functional/v1/test_client.py index a8ada80c..06c31c92 100644 --- a/fuelclient/tests/functional/v1/test_client.py +++ b/fuelclient/tests/functional/v1/test_client.py @@ -20,7 +20,7 @@ import tempfile from fuelclient.tests.functional import base -class TestHandlers(base.BaseTestCase): +class TestHandlers(base.CLIv1TestCase): def test_env_action(self): # check env help @@ -289,7 +289,7 @@ class TestHandlers(base.BaseTestCase): ) -class TestCharset(base.BaseTestCase): +class TestCharset(base.CLIv1TestCase): def test_charset_problem(self): self.load_data_to_nailgun_server() @@ -301,7 +301,7 @@ class TestCharset(base.BaseTestCase): )) -class TestFiles(base.BaseTestCase): +class TestFiles(base.CLIv1TestCase): def test_file_creation(self): self.load_data_to_nailgun_server() @@ -393,7 +393,7 @@ class TestFiles(base.BaseTestCase): )) -class TestDownloadUploadNodeAttributes(base.BaseTestCase): +class TestDownloadUploadNodeAttributes(base.CLIv1TestCase): def test_upload_download_interfaces(self): self.load_data_to_nailgun_server() @@ -415,35 +415,43 @@ class TestDownloadUploadNodeAttributes(base.BaseTestCase): self.upload_command(cmd))) -class TestDeployChanges(base.BaseTestCase): +class TestDeployChanges(base.CLIv1TestCase): - create_env = "env create --name=test --release={0}" - add_node = "--env-id=1 node set --node 1 --role=controller" - deploy_changes = "deploy-changes --env 1" - redeploy_changes = "redeploy-changes --env 1" + cmd_create_env = "env create --name=test --release={0}" + cmd_add_node = "--env-id=1 node set --node 1 --role=controller" + cmd_deploy_changes = "deploy-changes --env 1" + cmd_redeploy_changes = "redeploy-changes --env 1" + + messages_success = [ + "Deploying changes to environment with id=1\n", + "Finished deployment!\n" + ] + message_error = "(No changes to deploy)\n" def setUp(self): super(TestDeployChanges, self).setUp() self.load_data_to_nailgun_server() release_id = self.get_first_deployable_release_id() - self.create_env = self.create_env.format(release_id) - self.run_cli_commands((self.create_env, self.add_node)) + self.cmd_create_env = self.cmd_create_env.format(release_id) + self.run_cli_commands(( + self.cmd_create_env, + self.cmd_add_node + )) def test_deploy_changes(self): - self.run_cli_commands((self.deploy_changes,)) - - def test_no_changes_to_deploy(self): - self.run_cli_commands((self.deploy_changes,)) - self.check_for_stderr(self.deploy_changes, - "(No changes to deploy)\n", - check_errors=False) + self.check_all_in_msg(self.cmd_deploy_changes, + self.messages_success) def test_redeploy_changes(self): - self.run_cli_commands((self.deploy_changes, - self.redeploy_changes)) + self.run_cli_command(self.cmd_deploy_changes) + self.check_for_stderr(self.cmd_deploy_changes, + self.message_error, + check_errors=False) + self.check_all_in_msg(self.cmd_redeploy_changes, + self.messages_success) -class TestDirectoryDoesntExistErrorMessages(base.BaseTestCase): +class TestDirectoryDoesntExistErrorMessages(base.CLIv1TestCase): def test_settings_upload(self): self.check_for_stderr( @@ -520,7 +528,7 @@ class TestDirectoryDoesntExistErrorMessages(base.BaseTestCase): ) -class TestUploadSettings(base.BaseTestCase): +class TestUploadSettings(base.CLIv1TestCase): create_env = "env create --name=test --release={0}" add_node = "--env-id=1 node set --node 1 --role=controller" diff --git a/fuelclient/tests/functional/v2/test_client.py b/fuelclient/tests/functional/v2/test_client.py new file mode 100644 index 00000000..c0891804 --- /dev/null +++ b/fuelclient/tests/functional/v2/test_client.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2013-2014 Mirantis, Inc. +# +# 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. + +from fuelclient.tests.functional import base + + +class TestDeployChanges(base.CLIv2TestCase): + + cmd_create_env = "env create -r {0} cluster-test" + cmd_add_node = "env add nodes -e 1 -n 1 -r controller" + cmd_deploy_changes = "env deploy 1" + cmd_redeploy_changes = "env redeploy 1" + + pattern_success = (r"^Deployment task with id (\d{1,}) " + r"for the environment 1 has been started.\n$") + message_error = "(No changes to deploy)\n" + + def setUp(self): + super(TestDeployChanges, self).setUp() + self.load_data_to_nailgun_server() + release_id = self.get_first_deployable_release_id() + self.cmd_create_env = self.cmd_create_env.format(release_id) + self.run_cli_commands(( + self.cmd_create_env, + self.cmd_add_node + )) + + def test_deploy_changes(self): + self.check_for_stdout_by_regexp(self.cmd_deploy_changes, + self.pattern_success) + + def test_redeploy_changes(self): + result = self.check_for_stdout_by_regexp(self.cmd_deploy_changes, + self.pattern_success) + task_id = result.group(1) + self.wait_task_ready(task_id) + + self.check_for_stderr(self.cmd_deploy_changes, + self.message_error, + check_errors=False) + + self.check_for_stdout_by_regexp(self.cmd_redeploy_changes, + self.pattern_success) diff --git a/fuelclient/tests/unit/v2/cli/test_env.py b/fuelclient/tests/unit/v2/cli/test_env.py index 680f67f2..83298e58 100644 --- a/fuelclient/tests/unit/v2/cli/test_env.py +++ b/fuelclient/tests/unit/v2/cli/test_env.py @@ -95,6 +95,13 @@ class TestEnvCommand(test_engine.BaseCLITest): self.m_get_client.assert_called_once_with('environment', mock.ANY) self.m_client.deploy_changes.assert_called_once_with(42) + def test_env_redeploy(self): + args = 'env redeploy 42' + self.exec_command(args) + + self.m_get_client.assert_called_once_with('environment', mock.ANY) + self.m_client.redeploy_changes.assert_called_once_with(42) + def test_env_add_nodes(self): args = 'env add nodes -e 42 -n 24 25 -r compute cinder' self.exec_command(args) diff --git a/fuelclient/v1/environment.py b/fuelclient/v1/environment.py index d5678df7..5f7ebea5 100644 --- a/fuelclient/v1/environment.py +++ b/fuelclient/v1/environment.py @@ -71,6 +71,12 @@ class EnvironmentClient(base_v1.BaseV1Client): return deploy_task.id + def redeploy_changes(self, environment_id): + env = self._entity_wrapper(obj_id=environment_id) + redeploy_task = env.redeploy_changes() + + return redeploy_task.id + def spawn_vms(self, environment_id): env = self._entity_wrapper(obj_id=environment_id) return env.spawn_vms() diff --git a/setup.cfg b/setup.cfg index ac8bd81e..6068ed6f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,6 +34,7 @@ fuelclient = env_delete=fuelclient.commands.environment:EnvDelete env_deploy=fuelclient.commands.environment:EnvDeploy env_list=fuelclient.commands.environment:EnvList + env_redeploy=fuelclient.commands.environment:EnvRedeploy env_show=fuelclient.commands.environment:EnvShow env_spawn-vms=fuelclient.commands.environment:EnvSpawnVms env_update=fuelclient.commands.environment:EnvUpdate diff --git a/tox.ini b/tox.ini index 28c02f60..71ec9f8e 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ setenv = VIRTUAL_ENV={envdir} # Functional env settings FUEL_WEB_CLONE={env:FUEL_WEB_CLONE:yes} - FUEL_WEB_REPO={env:FUEL_WEB_REPO:https://github.com/stackforge/fuel-web.git} + FUEL_WEB_REPO={env:FUEL_WEB_REPO:https://github.com/openstack/fuel-web.git} FUEL_WEB_ROOT={env:FUEL_WEB_ROOT:/tmp/fuel_web} FETCH_REPO={env:FETCH_REPO:} FETCH_REFSPEC={env:FETCH_REFSPEC:}