fuel-main/nailgun/nailgun/task/manager.py

278 lines
8.6 KiB
Python

# Copyright 2013 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.
# -*- coding: utf-8 -*-
import uuid
import logging
import itertools
import traceback
import web
from nailgun.db import orm
import nailgun.rpc as rpc
from nailgun.logger import logger
from nailgun.errors import errors
from nailgun.api.models import Cluster
from nailgun.api.models import Task
from nailgun.api.models import Network
from nailgun.task.task import TaskHelper
from nailgun.task import task as tasks
class TaskManager(object):
def __init__(self, cluster_id):
self.cluster = orm().query(Cluster).get(cluster_id)
def _call_silently(self, task, instance, *args, **kwargs):
method = getattr(instance, kwargs.pop('method_name', 'execute'))
if task.status == 'error':
return
try:
return method(task, *args, **kwargs)
except Exception as exc:
err = str(exc)
if any([
not hasattr(exc, "log_traceback"),
hasattr(exc, "log_traceback") and exc.log_traceback
]):
logger.error(traceback.format_exc())
TaskHelper.update_task_status(
task.uuid,
status="error",
progress=100,
msg=err
)
class DeploymentTaskManager(TaskManager):
def execute(self):
logger.info(
u"Trying to start deployment at cluster '{0}'".format(
self.cluster.name or self.cluster.id,
)
)
current_tasks = orm().query(Task).filter_by(
cluster_id=self.cluster.id,
name="deploy"
)
for task in current_tasks:
if task.status == "running":
raise errors.DeploymentAlreadyStarted()
elif task.status in ("ready", "error"):
for subtask in task.subtasks:
orm().delete(subtask)
orm().delete(task)
orm().commit()
nodes_to_delete = TaskHelper.nodes_to_delete(self.cluster)
nodes_to_deploy = TaskHelper.nodes_to_deploy(self.cluster)
nodes_to_provision = TaskHelper.nodes_to_provision(self.cluster)
if not any([nodes_to_provision, nodes_to_deploy, nodes_to_delete]):
raise errors.WrongNodeStatus("No changes to deploy")
self.cluster.status = 'deployment'
orm().add(self.cluster)
orm().commit()
supertask = Task(
name="deploy",
cluster=self.cluster
)
orm().add(supertask)
orm().commit()
task_deletion, task_provision, task_deployment = None, None, None
if nodes_to_delete:
task_deletion = supertask.create_subtask("node_deletion")
logger.debug("Launching deletion task: %s", task_deletion.uuid)
self._call_silently(
task_deletion,
tasks.DeletionTask
)
task_messages = []
if nodes_to_provision:
TaskHelper.update_slave_nodes_fqdn(nodes_to_provision)
logger.debug("There are nodes to provision: %s",
" ".join([n.fqdn for n in nodes_to_provision]))
task_provision = supertask.create_subtask("provision")
# we assume here that task_provision just adds system to
# cobbler and reboots it, so it has extremely small weight
task_provision.weight = 0.05
provision_message = self._call_silently(
task_provision,
tasks.ProvisionTask,
method_name='message'
)
task_provision.cache = provision_message
orm().add(task_provision)
orm().commit()
task_messages.append(provision_message)
if nodes_to_deploy:
TaskHelper.update_slave_nodes_fqdn(nodes_to_deploy)
logger.debug("There are nodes to deploy: %s",
" ".join([n.fqdn for n in nodes_to_deploy]))
task_deployment = supertask.create_subtask("deployment")
deployment_message = self._call_silently(
task_deployment,
tasks.DeploymentTask,
method_name='message'
)
task_deployment.cache = deployment_message
orm().add(task_deployment)
orm().commit()
task_messages.append(deployment_message)
if task_messages:
rpc.cast('naily', task_messages)
logger.debug(
u"Deployment: task to deploy cluster '{0}' is {1}".format(
self.cluster.name or self.cluster.id,
supertask.uuid
)
)
return supertask
class CheckBeforeDeploymentTaskManager(TaskManager):
def execute(self):
task = Task(
name='check_before_deployment',
cluster=self.cluster
)
orm().add(task)
orm().commit()
self._call_silently(task, tasks.CheckBeforeDeploymentTask)
orm().refresh(task)
if task.status == 'running':
TaskHelper.update_task_status(
task.uuid, status="ready", progress=100)
return task
class CheckNetworksTaskManager(TaskManager):
def execute(self, data):
task = Task(
name="check_networks",
cluster=self.cluster
)
orm().add(task)
orm().commit()
self._call_silently(
task,
tasks.CheckNetworksTask,
data
)
orm().refresh(task)
if task.status == 'running':
TaskHelper.update_task_status(
task.uuid,
status="ready",
progress=100
)
return task
class VerifyNetworksTaskManager(TaskManager):
def execute(self, nets, vlan_ids):
task = Task(
name="check_networks",
cluster=self.cluster
)
orm().add(task)
orm().commit()
self._call_silently(
task,
tasks.CheckNetworksTask,
nets
)
orm().refresh(task)
if task.status != 'error':
# this one is connected with UI issues - we need to
# separate if error happened inside nailgun or somewhere
# in the orchestrator, and UI does it by task name.
task.name = "verify_networks"
orm().add(task)
orm().commit()
self._call_silently(
task,
tasks.VerifyNetworksTask,
vlan_ids
)
return task
class ClusterDeletionManager(TaskManager):
def execute(self):
current_cluster_tasks = orm().query(Task).filter_by(
cluster=self.cluster,
name='cluster_deletion'
).all()
deploy_running = orm().query(Task).filter_by(
cluster=self.cluster,
name='deploy',
status='running'
).first()
if deploy_running:
logger.error(
u"Deleting cluster '{0}' "
"while deployment is still running".format(
self.cluster.name
)
)
logger.debug("Removing cluster tasks")
for task in current_cluster_tasks:
if task.status == "running":
raise errors.DeletionAlreadyStarted()
elif task.status in ("ready", "error"):
for subtask in task.subtasks:
orm().delete(subtask)
orm().delete(task)
orm().commit()
logger.debug("Labeling cluster nodes to delete")
for node in self.cluster.nodes:
node.pending_deletion = True
orm().add(node)
orm().commit()
self.cluster.status = 'remove'
orm().add(self.cluster)
orm().commit()
logger.debug("Creating cluster deletion task")
task = Task(name="cluster_deletion", cluster=self.cluster)
orm().add(task)
orm().commit()
self._call_silently(
task,
tasks.ClusterDeletionTask
)
return task