fuel-web/nailgun/nailgun/orchestrator/tasks_serializer.py

348 lines
11 KiB
Python

# -*- coding: utf-8 -*-
# Copyright 2015 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.
import abc
import six
import yaml
from nailgun import consts
from nailgun.errors import errors
from nailgun.expression import Expression
from nailgun.logger import logger
from nailgun import objects
from nailgun.orchestrator import deployment_serializers
from nailgun.orchestrator import tasks_templates as templates
from nailgun.settings import settings
def get_uids_for_tasks(nodes, tasks):
"""Return node uids where particular tasks should be executed
:param nodes: list of Node db objects
:param tasks: list of dicts
:returns: list of strings
"""
roles = []
for task in tasks:
if task['role'] == consts.ALL_ROLES:
return get_uids_for_roles(nodes, consts.ALL_ROLES)
elif task['role'] == consts.MASTER_ROLE:
return ['master']
elif isinstance(task['role'], list):
roles.extend(task['role'])
else:
logger.warn(
'Wrong roles format, `roles` should be a list or "*" in %s',
task)
return get_uids_for_roles(nodes, roles)
def get_uids_for_roles(nodes, roles):
"""Returns list of uids for nodes that matches roles
:param nodes: list of nodes
:param roles: list of roles or consts.ALL_ROLES
:returns: list of strings
"""
uids = set()
if roles == consts.ALL_ROLES:
uids.update([n.uid for n in nodes])
elif roles == consts.MASTER_ROLE:
return ['master']
elif isinstance(roles, list):
for node in nodes:
if set(roles) & set(objects.Node.all_roles(node)):
uids.add(node.uid)
else:
logger.warn(
'Wrong roles format, `roles` should be a list or "*": %s',
roles)
return list(uids)
@six.add_metaclass(abc.ABCMeta)
class DeploymentHook(object):
def should_execute(self):
"""Should be used to define conditions when task should be executed."""
return True
@abc.abstractmethod
def serialize(self):
"""Serialize task in expected by orchestrator format.
This interface should return generator, because in some cases one
external task - should serialize several tasks internally.
"""
class ExpressionBasedTask(DeploymentHook):
def __init__(self, task, cluster):
self.task = task
self.cluster = cluster
@property
def _expression_context(self):
return {'cluster': self.cluster,
'settings': self.cluster.attributes.editable}
def should_execute(self):
if 'condition' not in self.task:
return True
return Expression(
self.task['condition'], self._expression_context).evaluate()
class GenericNodeHook(ExpressionBasedTask):
"""Should be used for node serialization.
"""
hook_type = abc.abstractproperty
def __init__(self, task, cluster, node):
self.node = node
super(GenericNodeHook, self).__init__(task, cluster)
class PuppetHook(GenericNodeHook):
hook_type = 'puppet'
def serialize(self):
yield templates.make_puppet_task([self.node['uid']], self.task)
class StandartConfigRolesHook(ExpressionBasedTask):
"""Role hooks that serializes task based on config file only."""
def __init__(self, task, cluster, nodes):
self.nodes = nodes
super(StandartConfigRolesHook, self).__init__(task, cluster)
def get_uids(self):
return get_uids_for_roles(self.nodes, self.task['role'])
def serialize(self):
uids = self.get_uids()
if uids:
yield templates.make_generic_task(uids, self.task)
class GenericRolesHook(StandartConfigRolesHook):
identity = abc.abstractproperty
class UploadMOSRepo(GenericRolesHook):
identity = 'upload_core_repos'
def get_uids(self):
return get_uids_for_roles(self.nodes, consts.ALL_ROLES)
def serialize(self):
uids = self.get_uids()
operating_system = self.cluster.release.operating_system
repos = objects.Attributes.merged_attrs_values(
self.cluster.attributes)['repo_setup']['repos']
if operating_system == consts.RELEASE_OS.centos:
for repo in repos:
yield templates.make_centos_repo_task(uids, repo)
yield templates.make_yum_clean(uids)
elif operating_system == consts.RELEASE_OS.ubuntu:
# NOTE(ikalnitsky):
# We have to clear /etc/apt/sources.list, because it
# has a lot of invalid repos right after provisioning
# and that lead us to deployment failures.
yield templates.make_shell_task(uids, {
'parameters': {
'cmd': '> /etc/apt/sources.list',
'timeout': 10
}})
yield templates.make_ubuntu_apt_disable_ipv6(uids)
# NOTE(kozhukalov):
# This task is to allow installing packages from
# unauthenticated repositories.
yield templates.make_ubuntu_unauth_repos_task(uids)
for repo in repos:
yield templates.make_ubuntu_sources_task(uids, repo)
if repo.get('priority'):
# do not add preferences task to task list if we can't
# complete it (e.g. can't retrieve or parse Release file)
task = templates.make_ubuntu_preferences_task(uids, repo)
if task is not None:
yield task
yield templates.make_apt_update_task(uids)
class RsyncPuppet(GenericRolesHook):
identity = 'rsync_core_puppet'
def get_uids(self):
return get_uids_for_roles(self.nodes, consts.ALL_ROLES)
def serialize(self):
src_path = self.task['parameters']['src'].format(
MASTER_IP=settings.MASTER_IP,
OPENSTACK_VERSION=self.cluster.release.version)
uids = self.get_uids()
yield templates.make_sync_scripts_task(
uids, src_path, self.task['parameters']['dst'])
class GenerateKeys(GenericRolesHook):
identity = 'generate_keys'
def serialize(self):
uids = self.get_uids()
self.task['parameters']['cmd'] = self.task['parameters']['cmd'].format(
CLUSTER_ID=self.cluster.id)
yield templates.make_shell_task(uids, self.task)
class CopyKeys(GenericRolesHook):
identity = 'copy_keys'
def serialize(self):
for file_path in self.task['parameters']['files']:
file_path['src'] = file_path['src'].format(
CLUSTER_ID=self.cluster.id)
uids = self.get_uids()
yield templates.make_generic_task(
uids, self.task)
class RestartRadosGW(GenericRolesHook):
identity = 'restart_radosgw'
def should_execute(self):
for node in self.nodes:
if 'ceph-osd' in node.all_roles:
return True
return False
class UploadNodesInfo(GenericRolesHook):
"""Hook that uploads info about all nodes in cluster."""
identity = 'upload_nodes_info'
def serialize(self):
nodes = deployment_serializers.get_nodes_not_for_deletion(
self.cluster)
uids = [n.uid for n in nodes]
serialized_nodes = self._serialize_nodes(nodes)
data = yaml.safe_dump({
'nodes': serialized_nodes,
})
path = self.task['parameters']['path']
yield templates.make_upload_task(uids, path=path, data=data)
def _serialize_nodes(self, nodes):
serializer = deployment_serializers.get_serializer_for_cluster(
self.cluster)
net_serializer = serializer.get_net_provider_serializer(self.cluster)
serialized_nodes = serializer.node_list(nodes)
serialized_nodes = net_serializer.update_nodes_net_info(
self.cluster, serialized_nodes)
return serialized_nodes
class UpdateHosts(GenericRolesHook):
"""Updates hosts info on all nodes in cluster."""
identity = 'update_hosts'
def serialize(self):
nodes = deployment_serializers.get_nodes_not_for_deletion(
self.cluster)
uids = [n.uid for n in nodes]
yield templates.make_puppet_task(uids, self.task)
class TaskSerializers(object):
"""Class serves as fabric for different types of task serializers."""
stage_serializers = [UploadMOSRepo, RsyncPuppet, CopyKeys, RestartRadosGW,
UploadNodesInfo, UpdateHosts, GenerateKeys]
deploy_serializers = [PuppetHook]
def __init__(self, stage_serializers=None, deploy_serializers=None):
"""Task serializers for stage (pre/post) are different from
serializers used for main deployment.
This should be considered as limitation of current architecture,
and will be solved in next releases.
:param stage_serializers: list of GenericRoleHook classes
:param deploy_serializers: list of GenericNodeHook classes
"""
self._stage_serializers_map = {}
self._deploy_serializers_map = {}
if stage_serializers is None:
stage_serializers = self.stage_serializers
for serializer in stage_serializers:
self.add_stage_serializer(serializer)
if deploy_serializers is None:
deploy_serializers = self.deploy_serializers
for serializer in deploy_serializers:
self.add_deploy_serializer(serializer)
def add_stage_serializer(self, serializer):
self._stage_serializers_map[serializer.identity] = serializer
def add_deploy_serializer(self, serializer):
self._deploy_serializers_map[serializer.hook_type] = serializer
def get_deploy_serializer(self, task):
if 'type' not in task:
raise errors.InvalidData('Task %s should have type', task)
if task['type'] in self._deploy_serializers_map:
return self._deploy_serializers_map[task['type']]
else:
# Currently we are not supporting anything except puppet as main
# deployment engine, therefore exception should be raised,
# but it should be verified by validation as well
raise errors.SerializerNotSupported(
'Serialization of type {0} not supported. Task {1}'.format(
task['type'], task))
def get_stage_serializer(self, task):
if 'id' not in task:
raise errors.InvalidData('Task %s should have id', task)
if task['id'] in self._stage_serializers_map:
return self._stage_serializers_map[task['id']]
else:
return StandartConfigRolesHook