851 lines
30 KiB
Python
851 lines
30 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# 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.
|
|
|
|
"""Deployment serializers for orchestrator"""
|
|
|
|
from copy import deepcopy
|
|
from distutils.version import StrictVersion
|
|
|
|
import six
|
|
|
|
from nailgun import consts
|
|
from nailgun.extensions import fire_callback_on_deployment_data_serialization
|
|
from nailgun.logger import logger
|
|
from nailgun import objects
|
|
from nailgun.plugins import adapters
|
|
from nailgun.settings import settings
|
|
from nailgun import utils
|
|
from nailgun.utils.role_resolver import NameMatchingPolicy
|
|
from nailgun.utils.role_resolver import RoleResolver
|
|
|
|
from nailgun.orchestrator.base_serializers import MuranoMetadataSerializerMixin
|
|
from nailgun.orchestrator.base_serializers import \
|
|
VmwareDeploymentSerializerMixin
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkDeploymentSerializer
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkDeploymentSerializer51
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkDeploymentSerializer60
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkDeploymentSerializer61
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkDeploymentSerializer70
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkDeploymentSerializer80
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkDeploymentSerializer90
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkTemplateSerializer70
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkTemplateSerializer80
|
|
from nailgun.orchestrator.neutron_serializers import \
|
|
NeutronNetworkTemplateSerializer90
|
|
from nailgun.orchestrator.nova_serializers import \
|
|
NovaNetworkDeploymentSerializer
|
|
from nailgun.orchestrator.nova_serializers import \
|
|
NovaNetworkDeploymentSerializer61
|
|
from nailgun.orchestrator.nova_serializers import \
|
|
NovaNetworkDeploymentSerializer70
|
|
|
|
|
|
class DeploymentMultinodeSerializer(object):
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer
|
|
|
|
critical_roles = frozenset(('controller', 'ceph-osd', 'primary-mongo'))
|
|
|
|
def __init__(self, tasks_graph=None):
|
|
self.task_graph = tasks_graph
|
|
self.all_nodes = None
|
|
self.role_resolver = None
|
|
self.initialized = None
|
|
|
|
def initialize(self, cluster):
|
|
self.all_nodes = objects.Cluster.get_nodes_not_for_deletion(cluster)
|
|
self.role_resolver = RoleResolver(self.all_nodes)
|
|
self.initialized = cluster.id
|
|
|
|
def finalize(self):
|
|
self.all_nodes = None
|
|
self.role_resolver = None
|
|
self.initialized = None
|
|
|
|
def _ensure_initialized_for(self, cluster):
|
|
# TODO(bgaifullin) need to move initialize into __init__
|
|
if self.initialized != cluster.id:
|
|
self.initialize(cluster)
|
|
|
|
def serialize(self, cluster, nodes, ignore_customized=False):
|
|
"""Method generates facts which are passed to puppet."""
|
|
try:
|
|
self.initialize(cluster)
|
|
serialized_nodes = []
|
|
|
|
origin_nodes = []
|
|
customized_nodes = []
|
|
if ignore_customized:
|
|
origin_nodes = nodes
|
|
else:
|
|
for node in nodes:
|
|
if node.replaced_deployment_info:
|
|
customized_nodes.append(node)
|
|
else:
|
|
origin_nodes.append(node)
|
|
|
|
serialized_nodes.extend(
|
|
self.serialize_generated(cluster, origin_nodes)
|
|
)
|
|
serialized_nodes.extend(
|
|
self.serialize_customized(cluster, customized_nodes)
|
|
)
|
|
|
|
# NOTE(dshulyak) tasks should not be preserved from replaced
|
|
# deployment info, there is different mechanism to control
|
|
# changes in tasks introduced during granular deployment,
|
|
# and that mech should be used
|
|
self.set_tasks(serialized_nodes)
|
|
finally:
|
|
self.finalize()
|
|
|
|
return serialized_nodes
|
|
|
|
def serialize_generated(self, cluster, nodes):
|
|
nodes = self.serialize_nodes(nodes)
|
|
common_attrs = self.get_common_attrs(cluster)
|
|
|
|
self.set_deployment_priorities(nodes)
|
|
for node in nodes:
|
|
yield utils.dict_merge(node, common_attrs)
|
|
|
|
def serialize_customized(self, cluster, nodes):
|
|
for node in nodes:
|
|
for role_data in node.replaced_deployment_info:
|
|
yield role_data
|
|
|
|
def get_common_attrs(self, cluster):
|
|
"""Cluster attributes."""
|
|
|
|
# tests call this method directly.
|
|
# and we need this workaround to avoid refactoring a lot of tests.
|
|
self._ensure_initialized_for(cluster)
|
|
attrs = objects.Cluster.get_attributes(cluster)
|
|
attrs = objects.Attributes.merged_attrs_values(attrs)
|
|
|
|
attrs['deployment_mode'] = cluster.mode
|
|
attrs['deployment_id'] = cluster.id
|
|
attrs['openstack_version'] = cluster.release.version
|
|
attrs['fuel_version'] = cluster.fuel_version
|
|
attrs['nodes'] = self.node_list(self.all_nodes)
|
|
|
|
# Adding params to workloads_collector
|
|
if 'workloads_collector' not in attrs:
|
|
attrs['workloads_collector'] = {}
|
|
attrs['workloads_collector']['create_user'] = \
|
|
objects.MasterNodeSettings.must_send_stats()
|
|
username = attrs['workloads_collector'].pop('user', None)
|
|
attrs['workloads_collector']['username'] = username
|
|
|
|
if self.role_resolver.resolve(['cinder']):
|
|
attrs['use_cinder'] = True
|
|
|
|
net_serializer = self.get_net_provider_serializer(cluster)
|
|
net_common_attrs = net_serializer.get_common_attrs(cluster, attrs)
|
|
attrs = utils.dict_merge(attrs, net_common_attrs)
|
|
|
|
self.inject_list_of_plugins(attrs, cluster)
|
|
|
|
return attrs
|
|
|
|
@classmethod
|
|
def node_list(cls, nodes):
|
|
"""Generate nodes list. Represents as "nodes" parameter in facts."""
|
|
node_list = []
|
|
|
|
for node in nodes:
|
|
for role in objects.Node.all_roles(node):
|
|
node_list.append(cls.serialize_node_for_node_list(node, role))
|
|
|
|
return node_list
|
|
|
|
@classmethod
|
|
def serialize_node_for_node_list(cls, node, role):
|
|
return {
|
|
'uid': node.uid,
|
|
'fqdn': objects.Node.get_node_fqdn(node),
|
|
'name': objects.Node.get_slave_name(node),
|
|
'role': role}
|
|
|
|
# TODO(apopovych): we have more generical method 'filter_by_roles'
|
|
def by_role(self, nodes, role):
|
|
return filter(lambda node: node['role'] == role, nodes)
|
|
|
|
def not_roles(self, nodes, roles):
|
|
return filter(lambda node: node['role'] not in roles, nodes)
|
|
|
|
def serialize_nodes(self, nodes):
|
|
"""Serialize node for each role.
|
|
|
|
For example if node has two roles then
|
|
in orchestrator will be passed two serialized
|
|
nodes.
|
|
"""
|
|
serialized_nodes = []
|
|
for node in nodes:
|
|
for role in objects.Node.all_roles(node):
|
|
serialized_nodes.append(self.serialize_node(node, role))
|
|
return serialized_nodes
|
|
|
|
def serialize_node(self, node, role):
|
|
"""Serialize node, then it will be merged with common attributes."""
|
|
node_attrs = {
|
|
# Yes, uid is really should be a string
|
|
'uid': node.uid,
|
|
'fqdn': objects.Node.get_node_fqdn(node),
|
|
'status': node.status,
|
|
'role': role,
|
|
'vms_conf': node.vms_conf,
|
|
'fail_if_error': role in self.critical_roles,
|
|
# TODO(eli): need to remove, requried for the fake thread only
|
|
'online': node.online,
|
|
}
|
|
|
|
net_serializer = self.get_net_provider_serializer(node.cluster)
|
|
node_attrs.update(net_serializer.get_node_attrs(node))
|
|
node_attrs.update(net_serializer.network_ranges(node.group_id))
|
|
node_attrs.update(self.generate_test_vm_image_data(node))
|
|
|
|
return node_attrs
|
|
|
|
def generate_properties_arguments(self, properties_data):
|
|
"""build a string of properties from a key value hash"""
|
|
properties = []
|
|
for key, value in six.iteritems(properties_data):
|
|
properties.append('--property {key}={value}'.format(
|
|
key=key, value=value))
|
|
return ' '.join(properties)
|
|
|
|
def generate_test_vm_image_data(self, node):
|
|
# Instantiate all default values in dict.
|
|
image_data = {
|
|
'container_format': 'bare',
|
|
'public': 'true',
|
|
'disk_format': 'qcow2',
|
|
'img_name': 'TestVM',
|
|
'img_path': '',
|
|
'os_name': 'cirros',
|
|
'min_ram': 64,
|
|
'glance_properties': '',
|
|
'properties': {},
|
|
}
|
|
# Generate a right path to image.
|
|
c_attrs = node.cluster.attributes
|
|
if 'ubuntu' in c_attrs['generated']['cobbler']['profile']:
|
|
img_dir = '/usr/share/cirros-testvm/'
|
|
else:
|
|
img_dir = '/opt/vm/'
|
|
image_data['img_path'] = '{0}cirros-x86_64-disk.img'.format(img_dir)
|
|
|
|
properties_data = {}
|
|
|
|
# Alternate VMWare specific values.
|
|
if c_attrs['editable']['common']['libvirt_type']['value'] == 'vcenter':
|
|
image_data.update({
|
|
'disk_format': 'vmdk',
|
|
'img_path': '{0}cirros-i386-disk.vmdk'.format(img_dir),
|
|
})
|
|
properties_data = {
|
|
'vmware_disktype': 'sparse',
|
|
'vmware_adaptertype': 'lsiLogic',
|
|
'hypervisor_type': 'vmware'
|
|
}
|
|
|
|
# NOTE(aschultz): properties was added as part of N and should be
|
|
# used infavor of glance_properties
|
|
image_data['glance_properties'] = self.generate_properties_arguments(
|
|
properties_data)
|
|
image_data['properties'] = properties_data
|
|
|
|
return {'test_vm_image': image_data}
|
|
|
|
@classmethod
|
|
def get_net_provider_serializer(cls, cluster):
|
|
if cluster.net_provider == 'nova_network':
|
|
return cls.nova_network_serializer
|
|
else:
|
|
return cls.neutron_network_serializer
|
|
|
|
def filter_by_roles(self, nodes, roles):
|
|
return filter(
|
|
lambda node: node['role'] in roles, nodes)
|
|
|
|
def set_deployment_priorities(self, nodes):
|
|
if self.task_graph is not None:
|
|
self.task_graph.add_priorities(nodes)
|
|
|
|
def set_tasks(self, serialized_nodes):
|
|
if self.task_graph is not None:
|
|
for node in serialized_nodes:
|
|
node['tasks'] = self.task_graph.deploy_task_serialize(node)
|
|
|
|
def inject_list_of_plugins(self, attributes, cluster):
|
|
"""Added information about plugins to serialized attributes.
|
|
|
|
:param attributes: the serialized attributes
|
|
:param cluster: the cluster object
|
|
"""
|
|
plugins = objects.ClusterPlugins.get_enabled(cluster.id)
|
|
attributes['plugins'] = [
|
|
self.serialize_plugin(cluster, p) for p in plugins
|
|
]
|
|
|
|
@classmethod
|
|
def serialize_plugin(cls, cluster, plugin):
|
|
"""Gets plugin information to include into serialized attributes.
|
|
|
|
:param cluster: the cluster object
|
|
:param plugin: the plugin object
|
|
"""
|
|
return plugin['name']
|
|
|
|
|
|
class DeploymentHASerializer(DeploymentMultinodeSerializer):
|
|
"""Serializer for HA mode."""
|
|
|
|
critical_roles = frozenset((
|
|
'primary-controller',
|
|
'primary-mongo',
|
|
'primary-swift-proxy',
|
|
'ceph-osd',
|
|
'controller'
|
|
))
|
|
|
|
def get_last_controller(self, nodes):
|
|
sorted_nodes = sorted(
|
|
nodes, key=lambda node: int(node['uid']))
|
|
|
|
controller_nodes = self.filter_by_roles(
|
|
sorted_nodes, ['controller', 'primary-controller'])
|
|
|
|
last_controller = None
|
|
if len(controller_nodes) > 0:
|
|
last_controller = controller_nodes[-1]['name']
|
|
|
|
return {'last_controller': last_controller}
|
|
|
|
@classmethod
|
|
def node_list(cls, nodes):
|
|
"""Node list."""
|
|
node_list = super(
|
|
DeploymentHASerializer,
|
|
cls
|
|
).node_list(nodes)
|
|
|
|
for node in node_list:
|
|
node['swift_zone'] = node['uid']
|
|
|
|
return node_list
|
|
|
|
def get_common_attrs(self, cluster):
|
|
"""Common attributes for all facts."""
|
|
common_attrs = super(
|
|
DeploymentHASerializer,
|
|
self
|
|
).get_common_attrs(cluster)
|
|
|
|
common_attrs.update(self.get_assigned_vips(cluster))
|
|
|
|
common_attrs['mp'] = [
|
|
{'point': '1', 'weight': '1'},
|
|
{'point': '2', 'weight': '2'}]
|
|
|
|
last_controller = self.get_last_controller(common_attrs['nodes'])
|
|
common_attrs.update(last_controller)
|
|
|
|
return common_attrs
|
|
|
|
def get_assigned_vips(self, cluster):
|
|
"""Assign and get vips for net groups."""
|
|
return objects.Cluster.get_network_manager(cluster).\
|
|
assign_vips_for_net_groups(cluster)
|
|
|
|
|
|
class DeploymentMultinodeSerializer50(MuranoMetadataSerializerMixin,
|
|
DeploymentMultinodeSerializer):
|
|
pass
|
|
|
|
|
|
class DeploymentHASerializer50(MuranoMetadataSerializerMixin,
|
|
DeploymentHASerializer):
|
|
pass
|
|
|
|
|
|
class DeploymentMultinodeSerializer51(DeploymentMultinodeSerializer50):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer51
|
|
|
|
|
|
class DeploymentHASerializer51(DeploymentHASerializer50):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer51
|
|
|
|
|
|
class DeploymentMultinodeSerializer60(DeploymentMultinodeSerializer50):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer60
|
|
|
|
|
|
class DeploymentHASerializer60(DeploymentHASerializer50):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer60
|
|
|
|
|
|
class DeploymentMultinodeSerializer61(DeploymentMultinodeSerializer,
|
|
VmwareDeploymentSerializerMixin):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer61
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer61
|
|
|
|
def serialize_node(self, node, role):
|
|
serialized_node = super(
|
|
DeploymentMultinodeSerializer61, self).serialize_node(node, role)
|
|
serialized_node['user_node_name'] = node.name
|
|
serialized_node.update(self.generate_vmware_data(node))
|
|
|
|
return serialized_node
|
|
|
|
@classmethod
|
|
def serialize_node_for_node_list(cls, node, role):
|
|
serialized_node = super(
|
|
DeploymentMultinodeSerializer61,
|
|
cls).serialize_node_for_node_list(node, role)
|
|
serialized_node['user_node_name'] = node.name
|
|
return serialized_node
|
|
|
|
|
|
class DeploymentHASerializer61(DeploymentHASerializer,
|
|
VmwareDeploymentSerializerMixin):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer61
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer61
|
|
|
|
def serialize_node(self, node, role):
|
|
serialized_node = super(
|
|
DeploymentHASerializer61, self).serialize_node(node, role)
|
|
serialized_node['user_node_name'] = node.name
|
|
serialized_node.update(self.generate_vmware_data(node))
|
|
|
|
return serialized_node
|
|
|
|
@classmethod
|
|
def serialize_node_for_node_list(cls, node, role):
|
|
serialized_node = super(
|
|
DeploymentHASerializer61,
|
|
cls).serialize_node_for_node_list(node, role)
|
|
serialized_node['user_node_name'] = node.name
|
|
return serialized_node
|
|
|
|
# Alternate VMWare specific values.
|
|
# FiXME(who): srogov
|
|
# This a temporary workaround to keep existing functioanality
|
|
# after fully implementation of the multi HV support and astute part
|
|
# for multiple images support, it is need to change
|
|
# dict image_data['test_vm_image'] to list of dicts
|
|
def generate_test_vm_image_data(self, node):
|
|
attrs = node.cluster.attributes
|
|
image_data = super(
|
|
DeploymentHASerializer61,
|
|
self).generate_test_vm_image_data(node)
|
|
|
|
images_data = {}
|
|
images_data['test_vm_image'] = []
|
|
if attrs.get('editable', {}).get('common', {}). \
|
|
get('use_vcenter', {}).get('value') is True:
|
|
image_vmdk_data = deepcopy(image_data['test_vm_image'])
|
|
img_path = image_vmdk_data['img_path']. \
|
|
replace('x86_64-disk.img', 'i386-disk.vmdk')
|
|
image_vmdk_data.update({
|
|
'img_name': 'TestVM-VMDK',
|
|
'disk_format': 'vmdk',
|
|
'img_path': img_path,
|
|
})
|
|
properties_data = {
|
|
'vmware_disktype': 'sparse',
|
|
'vmware_adaptertype': 'lsiLogic',
|
|
'hypervisor_type': 'vmware'
|
|
}
|
|
glance_properties = self.generate_properties_arguments(
|
|
properties_data)
|
|
|
|
# NOTE(aschultz): properties was added as part of N and should be
|
|
# used infavor of glance_properties
|
|
image_vmdk_data['glance_properties'] = glance_properties
|
|
image_vmdk_data['properties'] = properties_data
|
|
images_data['test_vm_image'].append(image_vmdk_data)
|
|
images_data['test_vm_image'].append(image_data['test_vm_image'])
|
|
else:
|
|
images_data = image_data
|
|
|
|
return images_data
|
|
|
|
|
|
class DeploymentHASerializer70(DeploymentHASerializer61):
|
|
# nova_network_serializer is just for compatibility with current BVTs
|
|
# and other tests. It can be removed when tests are fixed.
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer70
|
|
|
|
@classmethod
|
|
def get_net_provider_serializer(cls, cluster):
|
|
if cluster.net_provider == consts.CLUSTER_NET_PROVIDERS.nova_network:
|
|
return cls.nova_network_serializer
|
|
elif cluster.network_config.configuration_template:
|
|
return NeutronNetworkTemplateSerializer70
|
|
else:
|
|
return NeutronNetworkDeploymentSerializer70
|
|
|
|
def get_assigned_vips(self, cluster):
|
|
return {}
|
|
|
|
|
|
class DeploymentHASerializer80(DeploymentHASerializer70):
|
|
|
|
@classmethod
|
|
def get_net_provider_serializer(cls, cluster):
|
|
if cluster.network_config.configuration_template:
|
|
return NeutronNetworkTemplateSerializer80
|
|
else:
|
|
return NeutronNetworkDeploymentSerializer80
|
|
|
|
|
|
class DeploymentHASerializer90(DeploymentHASerializer80):
|
|
|
|
def inject_murano_settings(self, data):
|
|
return data
|
|
|
|
def get_common_attrs(self, cluster):
|
|
attrs = super(DeploymentHASerializer90, self).get_common_attrs(cluster)
|
|
|
|
for node in objects.Cluster.get_nodes_not_for_deletion(cluster):
|
|
name = objects.Node.permanent_id(node)
|
|
node_attrs = attrs['network_metadata']['nodes'][name]
|
|
|
|
node_attrs['nova_cpu_pinning_enabled'] = \
|
|
objects.NodeAttributes.is_nova_cpu_pinning_enabled(node)
|
|
node_attrs['nova_hugepages_enabled'] = (
|
|
objects.NodeAttributes.is_nova_hugepages_enabled(node))
|
|
|
|
return attrs
|
|
|
|
@classmethod
|
|
def get_net_provider_serializer(cls, cluster):
|
|
if cluster.network_config.configuration_template:
|
|
return NeutronNetworkTemplateSerializer90
|
|
else:
|
|
return NeutronNetworkDeploymentSerializer90
|
|
|
|
def serialize_node(self, node, role):
|
|
serialized_node = super(
|
|
DeploymentHASerializer90, self).serialize_node(node, role)
|
|
self.serialize_node_attributes(node, serialized_node)
|
|
return serialized_node
|
|
|
|
def serialize_node_attributes(self, node, serialized_node):
|
|
self.generate_cpu_pinning(node, serialized_node)
|
|
self.generate_node_hugepages(node, serialized_node)
|
|
|
|
def generate_cpu_pinning(self, node, serialized_node):
|
|
pinning_info = objects.NodeAttributes.distribute_node_cpus(node)
|
|
cpu_pinning = pinning_info['components']
|
|
|
|
self._generate_nova_cpu_pinning(
|
|
serialized_node,
|
|
cpu_pinning.get('nova')
|
|
)
|
|
self._generate_dpdk_cpu_pinning(
|
|
serialized_node,
|
|
cpu_pinning.get('dpdk')
|
|
)
|
|
|
|
def generate_node_hugepages(self, node, serialized_node):
|
|
self._generate_nova_hugepages(node, serialized_node)
|
|
self._generate_dpdk_hugepages(node, serialized_node)
|
|
self._generate_hugepages_distribution(node, serialized_node)
|
|
|
|
@staticmethod
|
|
def _generate_nova_cpu_pinning(serialized_node, cpus):
|
|
if not cpus:
|
|
return
|
|
|
|
serialized_node.setdefault('nova', {})['cpu_pinning'] = cpus
|
|
|
|
@staticmethod
|
|
def _generate_dpdk_cpu_pinning(serialized_node, cpus):
|
|
if not cpus:
|
|
return
|
|
|
|
ovs_core_mask = 1 << cpus[0]
|
|
ovs_pmd_core_mask = 0
|
|
for cpu in cpus[1:]:
|
|
ovs_pmd_core_mask |= 1 << cpu
|
|
|
|
core_masks = {'ovs_core_mask': hex(ovs_core_mask)}
|
|
if ovs_pmd_core_mask > 0:
|
|
core_masks['ovs_pmd_core_mask'] = hex(ovs_pmd_core_mask)
|
|
serialized_node.setdefault('dpdk', {}).update(core_masks)
|
|
|
|
@staticmethod
|
|
def _generate_nova_hugepages(node, serialized_node):
|
|
serialized_node.setdefault('nova', {})['enable_hugepages'] = (
|
|
objects.NodeAttributes.is_nova_hugepages_enabled(node))
|
|
|
|
@staticmethod
|
|
def _generate_dpdk_hugepages(node, serialized_node):
|
|
serialized_node.setdefault('dpdk', {}).update(
|
|
objects.NodeAttributes.dpdk_hugepages_attrs(node))
|
|
|
|
@classmethod
|
|
def _generate_hugepages_distribution(self, node, serialized_node):
|
|
hugepages = objects.NodeAttributes.distribute_hugepages(node)
|
|
|
|
# FIXME(asvechnikov): We should skip our distribution
|
|
# due to LP bug #1560532, so we can't configure 1G hugepages
|
|
# in runtime. This limitation should gone with kernel 3.16
|
|
skip = any((x['size'] == 1024 ** 2) for x in hugepages)
|
|
if hugepages and not skip:
|
|
serialized_node.setdefault('hugepages', []).extend(
|
|
hugepages)
|
|
|
|
|
|
class DeploymentLCMSerializer(DeploymentHASerializer90):
|
|
_configs = None
|
|
_priorities = {
|
|
consts.OPENSTACK_CONFIG_TYPES.cluster: 0,
|
|
consts.OPENSTACK_CONFIG_TYPES.role: 1,
|
|
consts.OPENSTACK_CONFIG_TYPES.node: 2,
|
|
}
|
|
|
|
def initialize(self, cluster):
|
|
super(DeploymentLCMSerializer, self).initialize(cluster)
|
|
self._configs = sorted(
|
|
objects.OpenstackConfigCollection.filter_by(
|
|
None, cluster_id=cluster.id
|
|
),
|
|
key=lambda x: self._priorities[x.config_type]
|
|
)
|
|
|
|
def finalize(self):
|
|
self._configs = None
|
|
super(DeploymentLCMSerializer, self).finalize()
|
|
|
|
def get_common_attrs(self, cluster):
|
|
attrs = super(DeploymentLCMSerializer, self).get_common_attrs(
|
|
cluster
|
|
)
|
|
attrs['cluster'] = objects.Cluster.to_dict(cluster)
|
|
attrs['release'] = objects.Release.to_dict(cluster.release)
|
|
return attrs
|
|
|
|
def serialize_customized(self, cluster, nodes):
|
|
for node in nodes:
|
|
data = {}
|
|
roles = []
|
|
for role_data in node.replaced_deployment_info:
|
|
roles.append(role_data.pop('role'))
|
|
data = utils.dict_merge(data, role_data)
|
|
data['roles'] = roles
|
|
yield data
|
|
|
|
def serialize_nodes(self, nodes):
|
|
serialized_nodes = []
|
|
for node in nodes:
|
|
roles = objects.Node.all_roles(node)
|
|
if roles:
|
|
serialized_nodes.append(self.serialize_node(node, roles))
|
|
# added master node
|
|
serialized_nodes.append({
|
|
'uid': consts.MASTER_NODE_UID,
|
|
'roles': [consts.TASK_ROLES.master]
|
|
})
|
|
return serialized_nodes
|
|
|
|
def serialize_node(self, node, roles):
|
|
# serialize all roles to one config
|
|
# Since there is no role depended things except
|
|
# OpenStack configs, we can do this
|
|
serialized_node = super(
|
|
DeploymentLCMSerializer, self).serialize_node(node, roles[0])
|
|
del serialized_node['role']
|
|
serialized_node['roles'] = roles
|
|
serialized_node['fail_if_error'] = bool(
|
|
self.critical_roles.intersection(roles)
|
|
)
|
|
self.inject_configs(node, roles, serialized_node)
|
|
return serialized_node
|
|
|
|
@classmethod
|
|
def serialize_plugin(cls, cluster, plugin):
|
|
os_name = cluster.release.operating_system
|
|
adapter = adapters.wrap_plugin(plugin)
|
|
result = {
|
|
'name': plugin['name'],
|
|
'scripts': [
|
|
{
|
|
'remote_url': adapter.master_scripts_path(cluster),
|
|
'local_path': adapter.slaves_scripts_path
|
|
}
|
|
]
|
|
}
|
|
|
|
if not adapter.repo_files(cluster):
|
|
return result
|
|
|
|
# TODO(bgaifullin) move priority to plugin metadata
|
|
if os_name == consts.RELEASE_OS.centos:
|
|
repo = {
|
|
'type': 'rpm',
|
|
'name': adapter.full_name,
|
|
'uri': adapter.repo_url(cluster),
|
|
'priority': settings.REPO_PRIORITIES['plugins']['centos']
|
|
}
|
|
elif os_name == consts.RELEASE_OS.ubuntu:
|
|
repo = {
|
|
'type': 'deb',
|
|
'name': adapter.full_name,
|
|
'uri': adapter.repo_url(cluster),
|
|
'suite': '/',
|
|
'section': '',
|
|
'priority': settings.REPO_PRIORITIES['plugins']['ubuntu']
|
|
}
|
|
else:
|
|
logger.warning("Unsupported OS: %s.", os_name)
|
|
return result
|
|
|
|
result['repositories'] = [repo]
|
|
return result
|
|
|
|
def inject_configs(self, node, roles, output):
|
|
node_config = output.setdefault('configuration', {})
|
|
for config in self._configs:
|
|
if config.config_type == consts.OPENSTACK_CONFIG_TYPES.cluster:
|
|
utils.dict_update(node_config, config.configuration, 1)
|
|
elif config.config_type == consts.OPENSTACK_CONFIG_TYPES.role:
|
|
for role in roles:
|
|
if NameMatchingPolicy.create(config.node_role).match(role):
|
|
utils.dict_update(node_config, config.configuration, 1)
|
|
elif config.config_type == consts.OPENSTACK_CONFIG_TYPES.node:
|
|
if config.node_id == node.id:
|
|
utils.dict_update(node_config, config.configuration, 1)
|
|
|
|
|
|
def get_serializer_for_cluster(cluster):
|
|
"""Returns a serializer depends on a given `cluster`.
|
|
|
|
:param cluster: cluster to process
|
|
:returns: a serializer for a given cluster
|
|
"""
|
|
serializers_map = {
|
|
'5.0': {
|
|
'multinode': DeploymentMultinodeSerializer50,
|
|
'ha': DeploymentHASerializer50,
|
|
},
|
|
'5.1': {
|
|
'multinode': DeploymentMultinodeSerializer51,
|
|
'ha': DeploymentHASerializer51,
|
|
},
|
|
'6.0': {
|
|
'multinode': DeploymentMultinodeSerializer60,
|
|
'ha': DeploymentHASerializer60,
|
|
},
|
|
'6.1': {
|
|
'multinode': DeploymentMultinodeSerializer61,
|
|
'ha': DeploymentHASerializer61,
|
|
},
|
|
'7.0': {
|
|
# Multinode is not supported anymore
|
|
'ha': DeploymentHASerializer70,
|
|
},
|
|
'8.0': {
|
|
'ha': DeploymentHASerializer80,
|
|
},
|
|
'9.0': {
|
|
'ha': DeploymentHASerializer90,
|
|
}
|
|
}
|
|
|
|
env_mode = 'ha' if cluster.is_ha_mode else 'multinode'
|
|
for version, serializers in six.iteritems(serializers_map):
|
|
if cluster.release.environment_version.startswith(version):
|
|
return serializers[env_mode]
|
|
|
|
# return latest serializer by default
|
|
latest_version = max(serializers_map, key=lambda v: StrictVersion(v))
|
|
|
|
return serializers_map[latest_version][env_mode]
|
|
|
|
|
|
def _execute_pipeline(data, cluster, nodes, ignore_customized):
|
|
"Executes pipelines depending on ignore_customized boolean."
|
|
if ignore_customized:
|
|
return fire_callback_on_deployment_data_serialization(
|
|
data, cluster, nodes)
|
|
|
|
nodes_without_customized = {n.uid: n for n in nodes
|
|
if not n.replaced_deployment_info}
|
|
|
|
def keyfunc(node):
|
|
return node['uid'] in nodes_without_customized
|
|
|
|
# not customized nodes
|
|
nodes_data_for_pipeline = list(six.moves.filter(keyfunc, data))
|
|
|
|
# NOTE(sbrzeczkowski): pipelines must be executed for nodes
|
|
# which don't have replaced_deployment_info specified
|
|
updated_data = fire_callback_on_deployment_data_serialization(
|
|
nodes_data_for_pipeline, cluster,
|
|
list(six.itervalues(nodes_without_customized)))
|
|
|
|
# customized nodes
|
|
updated_data.extend(six.moves.filterfalse(keyfunc, data))
|
|
return updated_data
|
|
|
|
|
|
def _invoke_serializer(serializer, cluster, nodes, ignore_customized):
|
|
objects.Cluster.set_primary_roles(cluster, nodes)
|
|
# TODO(apply only for specified subset of nodes)
|
|
objects.Cluster.prepare_for_deployment(cluster, cluster.nodes)
|
|
data = serializer.serialize(
|
|
cluster, nodes, ignore_customized=ignore_customized
|
|
)
|
|
return _execute_pipeline(data, cluster, nodes, ignore_customized)
|
|
|
|
|
|
def serialize(orchestrator_graph, cluster, nodes, ignore_customized=False):
|
|
"""Serialization depends on deployment mode."""
|
|
return _invoke_serializer(
|
|
get_serializer_for_cluster(cluster)(orchestrator_graph),
|
|
cluster, nodes, ignore_customized
|
|
)
|
|
|
|
|
|
def serialize_for_lcm(cluster, nodes, ignore_customized=False):
|
|
return _invoke_serializer(
|
|
DeploymentLCMSerializer(), cluster, nodes, ignore_customized
|
|
)
|