Add --force flag for `fuel deploy-changes` command

The flag allows to apply the changes to the cluster if the cluster
is in the operational state. This allows you to deploy the changes
without the reprovisioning procedure.

Example: `fuel deploy-changes --env <env_id> --force`

Partial-Bug: 1540558
Change-Id: Ibc89fdbfbd0a36a890412cd8e861d35bcf930690
This commit is contained in:
Vitalii Myhal 2016-02-05 12:55:17 -06:00
parent b0cba9a677
commit 7e5607b376
6 changed files with 83 additions and 11 deletions

View File

@ -37,6 +37,7 @@ from nailgun.api.v1.validators.cluster import VmwareAttributesValidator
from nailgun.logger import logger
from nailgun import objects
from nailgun.task.manager import ApplyChangesForceTaskManager
from nailgun.task.manager import ApplyChangesTaskManager
from nailgun.task.manager import ClusterDeletionManager
from nailgun.task.manager import ResetEnvironmentTaskManager
@ -87,6 +88,15 @@ class ClusterChangesHandler(DeferredTaskHandler):
validator = ClusterChangesValidator
class ClusterChangesForceRedeployHandler(DeferredTaskHandler):
log_message = u"Trying to force deployment of the environment '{env_id}'"
log_error = u"Error during execution of a forced deployment task " \
u"on environment '{env_id}': {error}"
task_manager = ApplyChangesForceTaskManager
validator = ClusterChangesValidator
class ClusterStopDeploymentHandler(DeferredTaskHandler):
log_message = u"Trying to stop deployment on environment '{env_id}'"

View File

@ -26,6 +26,7 @@ from nailgun.api.v1.handlers.capacity import CapacityLogHandler
from nailgun.api.v1.handlers.cluster import ClusterAttributesDefaultsHandler
from nailgun.api.v1.handlers.cluster import ClusterAttributesHandler
from nailgun.api.v1.handlers.cluster import ClusterChangesForceRedeployHandler
from nailgun.api.v1.handlers.cluster import ClusterChangesHandler
from nailgun.api.v1.handlers.cluster import ClusterCollectionHandler
from nailgun.api.v1.handlers.cluster import ClusterDeploymentTasksHandler
@ -159,6 +160,8 @@ urls = (
ClusterHandler,
r'/clusters/(?P<cluster_id>\d+)/changes/?$',
ClusterChangesHandler,
r'/clusters/(?P<cluster_id>\d+)/changes/redeploy/?$',
ClusterChangesForceRedeployHandler,
r'/clusters/(?P<cluster_id>\d+)/attributes/?$',
ClusterAttributesHandler,
r'/clusters/(?P<cluster_id>\d+)/attributes/defaults/?$',

View File

@ -153,7 +153,7 @@ class TaskHelper(object):
# TODO(aroma): considering moving this code to
# nailgun Cluster object's methods
@classmethod
def nodes_to_deploy(cls, cluster):
def nodes_to_deploy(cls, cluster, force=False):
from nailgun import objects # preventing cycle import error
nodes_to_deploy = []
@ -166,9 +166,9 @@ class TaskHelper(object):
cluster_roles.update(node.roles)
for node in cluster.nodes:
if any([node.pending_addition,
node.needs_reprovision,
node.needs_redeploy]):
if force or any([node.pending_addition,
node.needs_reprovision,
node.needs_redeploy]):
nodes_to_deploy.append(node)
for role_name in node.pending_roles:
update_required.update(

View File

@ -162,7 +162,8 @@ class ApplyChangesTaskManager(TaskManager, DeploymentCheckMixin):
db().delete(task)
db().flush()
def execute(self, nodes_to_provision_deploy=None, deployment_tasks=None):
def execute(self, nodes_to_provision_deploy=None, deployment_tasks=None,
force=False):
logger.info(
u"Trying to start deployment at cluster '{0}'".format(
self.cluster.name or self.cluster.id
@ -178,7 +179,7 @@ class ApplyChangesTaskManager(TaskManager, DeploymentCheckMixin):
nodes_to_delete = TaskHelper.nodes_to_delete(self.cluster)
nodes_to_deploy = nodes_to_provision_deploy or \
TaskHelper.nodes_to_deploy(self.cluster)
TaskHelper.nodes_to_deploy(self.cluster, force)
nodes_to_provision = TaskHelper.nodes_to_provision(self.cluster)
if not any([nodes_to_provision, nodes_to_deploy, nodes_to_delete]):
@ -198,13 +199,14 @@ class ApplyChangesTaskManager(TaskManager, DeploymentCheckMixin):
self.cluster.id,
supertask.id,
nodes_to_provision_deploy=nodes_ids_to_deploy,
deployment_tasks=deployment_tasks
deployment_tasks=deployment_tasks,
force=force
)
return supertask
def _execute_async(self, supertask_id, deployment_tasks=None,
nodes_to_provision_deploy=None):
nodes_to_provision_deploy=None, force=False):
"""Function for execute task in the mule
:param supertask_id: id of parent task
@ -218,7 +220,8 @@ class ApplyChangesTaskManager(TaskManager, DeploymentCheckMixin):
self._execute_async_content(
supertask,
deployment_tasks=deployment_tasks,
nodes_to_provision_deploy=nodes_to_provision_deploy)
nodes_to_provision_deploy=nodes_to_provision_deploy,
force=force)
except Exception as e:
logger.exception('Error occurred when running task')
data = {
@ -243,7 +246,7 @@ class ApplyChangesTaskManager(TaskManager, DeploymentCheckMixin):
return task_deletion
def _execute_async_content(self, supertask, deployment_tasks=None,
nodes_to_provision_deploy=None):
nodes_to_provision_deploy=None, force=False):
"""Processes supertask async in mule
:param supertask: SqlAlchemy task object
@ -260,7 +263,7 @@ class ApplyChangesTaskManager(TaskManager, DeploymentCheckMixin):
n.needs_reprovision]),
nodes_to_deploy)
else:
nodes_to_deploy = TaskHelper.nodes_to_deploy(self.cluster)
nodes_to_deploy = TaskHelper.nodes_to_deploy(self.cluster, force)
nodes_to_provision = TaskHelper.nodes_to_provision(self.cluster)
nodes_to_delete = TaskHelper.nodes_to_delete(self.cluster)
@ -543,6 +546,13 @@ class ApplyChangesTaskManager(TaskManager, DeploymentCheckMixin):
db().flush()
class ApplyChangesForceTaskManager(ApplyChangesTaskManager):
def execute(self, **kwargs):
kwargs['force'] = True
return super(ApplyChangesForceTaskManager, self).execute(**kwargs)
class SpawnVMsTaskManager(ApplyChangesTaskManager):
deployment_type = consts.TASK_NAMES.spawn_vms

View File

@ -1824,3 +1824,36 @@ class TestHandlers(BaseIntegrationTest):
supertask = self.env.launch_deployment()
self.env.wait_ready(supertask, timeout=60)
@fake_tasks()
def test_force_redeploy_changes(self):
self.env.create(
nodes_kwargs=[{'name': '', 'pending_addition': True}]
)
def _send_request(handler):
return self.app.put(
reverse(
handler,
kwargs={'cluster_id': self.env.clusters[0].id}
),
headers=self.default_headers,
expect_errors=True
)
# Initial deployment
task = self.env.launch_deployment()
self.env.wait_ready(task, timeout=60)
# Trying to redeploy on cluster in the operational state
resp = _send_request('ClusterChangesHandler')
self.assertEqual(resp.status_code, 400)
self.assertEqual(resp.json_body.get('message'), 'No changes to deploy')
# Trying to force redeploy on cluster in the operational state
resp = _send_request('ClusterChangesForceRedeployHandler')
self.assertEqual(resp.status_code, 202)
self.assertEqual(resp.json_body.get('name'),
consts.TASK_NAMES.deploy)
self.assertEqual(resp.json_body.get('status'),
consts.TASK_STATUSES.pending)

View File

@ -678,6 +678,22 @@ class TestTaskManagers(BaseIntegrationTest):
manager_ = manager.ApplyChangesTaskManager(cluster_db.id)
self.assertRaises(errors.WrongNodeStatus, manager_.execute)
@fake_tasks()
def test_force_deploy_changes(self):
self.env.create(
nodes_kwargs=[
{"status": NODE_STATUSES.ready}
]
)
cluster_db = self.env.clusters[0]
objects.Cluster.clear_pending_changes(cluster_db)
manager_ = manager.ApplyChangesForceTaskManager(cluster_db.id)
supertask = manager_.execute()
self.assertEqual(supertask.name, TASK_NAMES.deploy)
self.assertIn(supertask.status, (TASK_STATUSES.pending,
TASK_STATUSES.running,
TASK_STATUSES.ready))
@fake_tasks()
@mock.patch('nailgun.task.manager.tasks.DeletionTask.execute')
def test_apply_changes_exception_caught(self, mdeletion_execute):