# -*- 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 collections import copy from distutils import version import six from nailgun import consts from nailgun.extensions.network_manager.objects.serializers import \ network_configuration from nailgun import objects from nailgun import utils from . import transformations # That's weird, but that's how hacking likes from .objects import adapters from .transformations import cluster as cluster_trs def merge_attributes(a, b): """Merge values of editable attributes. The values of the b attributes have precedence over the values of the a attributes. """ attrs = copy.deepcopy(b) for section, pairs in six.iteritems(attrs): if section == "repo_setup" or section not in a: continue a_values = a[section] for key, values in six.iteritems(pairs): if key != "metadata" and key in a_values: values["value"] = a_values[key]["value"] return attrs def merge_nets(a, b): new_settings = copy.deepcopy(b) source_networks = dict((n["name"], n) for n in a["networks"]) for net in new_settings["networks"]: if net["name"] not in source_networks: continue source_net = source_networks[net["name"]] for key, value in six.iteritems(net): if (key not in ("cluster_id", "id", "meta", "group_id") and key in source_net): net[key] = source_net[key] networking_params = new_settings["networking_parameters"] source_params = a["networking_parameters"] for key, value in six.iteritems(networking_params): if key not in source_params: continue networking_params[key] = source_params[key] return new_settings class UpgradeHelper(object): network_serializers = { consts.CLUSTER_NET_PROVIDERS.neutron: network_configuration.NeutronNetworkConfigurationSerializer, consts.CLUSTER_NET_PROVIDERS.nova_network: network_configuration.NovaNetworkConfigurationSerializer, } cluster_transformations = transformations.Lazy(cluster_trs.Manager) @classmethod def clone_cluster(cls, orig_cluster, data): from .objects import relations new_cluster = cls.create_cluster_clone(orig_cluster, data) cls.copy_attributes(orig_cluster, new_cluster) cls.copy_network_config(orig_cluster, new_cluster) relations.UpgradeRelationObject.create_relation(orig_cluster.id, new_cluster.id) cls.change_env_settings(orig_cluster, new_cluster) return new_cluster @classmethod def create_cluster_clone(cls, orig_cluster, data): create_data = orig_cluster.get_create_data() create_data["name"] = data["name"] create_data["release_id"] = data["release_id"] new_cluster = adapters.NailgunClusterAdapter.create(create_data) return new_cluster @classmethod def copy_attributes(cls, orig_cluster, new_cluster): attrs = cls.cluster_transformations.apply( orig_cluster.release.environment_version, new_cluster.release.environment_version, { 'editable': orig_cluster.editable_attrs, 'generated': orig_cluster.generated_attrs, }, ) new_cluster.generated_attrs = utils.dict_merge( new_cluster.generated_attrs, attrs['generated'], ) new_cluster.editable_attrs = merge_attributes( attrs['editable'], new_cluster.editable_attrs, ) @classmethod def change_env_settings(cls, orig_cluster, new_cluster): attrs = new_cluster.attributes attrs['editable']['provision']['method']['value'] = 'image' @classmethod def transform_vips_for_net_groups_70(cls, vips): """Rename or remove types of VIPs for 7.0 network groups. This method renames types of VIPs from older releases (<7.0) to be compatible with network groups of the 7.0 release according to the rules: management: haproxy -> management public: haproxy -> public public: vrouter -> vrouter_pub Note, that in the result VIPs are present only those IPs that correspond to the given rules. """ rename_vip_rules = { "management": { "haproxy": "management", "vrouter": "vrouter", }, "public": { "haproxy": "public", "vrouter": "vrouter_pub", }, } renamed_vips = collections.defaultdict(dict) for ng_name, vips in six.iteritems(vips): ng_vip_rules = rename_vip_rules[ng_name] for vip_name, vip_addr in six.iteritems(vips): if vip_name not in ng_vip_rules: continue new_vip_name = ng_vip_rules[vip_name] renamed_vips[ng_name][new_vip_name] = vip_addr return renamed_vips @classmethod def copy_network_config(cls, orig_cluster, new_cluster): nets_serializer = cls.network_serializers[orig_cluster.net_provider] nets = merge_nets( nets_serializer.serialize_for_cluster(orig_cluster.cluster), nets_serializer.serialize_for_cluster(new_cluster.cluster)) new_net_manager = new_cluster.get_network_manager() new_net_manager.update(nets) @classmethod def copy_vips(cls, orig_cluster, new_cluster): orig_net_manager = orig_cluster.get_network_manager() new_net_manager = new_cluster.get_network_manager() vips = {} assigned_vips = orig_net_manager.get_assigned_vips() for ng_name in (consts.NETWORKS.public, consts.NETWORKS.management): vips[ng_name] = assigned_vips[ng_name] # NOTE(akscram): In the 7.0 release was introduced networking # templates that use the vip_name column as # unique names of VIPs. if version.LooseVersion(orig_cluster.release.environment_version) < \ version.LooseVersion("7.0"): vips = cls.transform_vips_for_net_groups_70(vips) new_net_manager.assign_given_vips_for_net_groups(vips) new_net_manager.assign_vips_for_net_groups() @classmethod def get_node_roles(cls, reprovision, current_roles, given_roles): """Return roles depending on the reprovisioning status. In case the node should be re-provisioned, only pending roles should be set, otherwise for an already provisioned and deployed node only actual roles should be set. In the both case the given roles will have precedence over the existing. :param reprovision: boolean, if set to True then the node should be re-provisioned :param current_roles: a list of current roles of the node :param given_roles: a list of roles that should be assigned to the node :returns: a tuple of a list of roles and a list of pending roles that will be assigned to the node """ roles_to_assign = given_roles if given_roles else current_roles if reprovision: roles, pending_roles = [], roles_to_assign else: roles, pending_roles = roles_to_assign, [] return roles, pending_roles @classmethod def assign_node_to_cluster(cls, node, seed_cluster, roles, pending_roles): orig_cluster = adapters.NailgunClusterAdapter.get_by_uid( node.cluster_id) orig_manager = orig_cluster.get_network_manager() netgroups_id_mapping = cls.get_netgroups_id_mapping( orig_cluster, seed_cluster) node.update_cluster_assignment(seed_cluster, roles, pending_roles) objects.Node.set_netgroups_ids(node, netgroups_id_mapping) if not seed_cluster.network_template: orig_manager.set_nic_assignment_netgroups_ids( node, netgroups_id_mapping) orig_manager.set_bond_assignment_netgroups_ids( node, netgroups_id_mapping) node.add_pending_change(consts.CLUSTER_CHANGES.interfaces) @classmethod def get_netgroups_id_mapping(self, orig_cluster, seed_cluster): orig_ng = orig_cluster.get_network_groups() seed_ng = seed_cluster.get_network_groups() seed_ng_dict = dict((ng.name, ng.id) for ng in seed_ng) mapping = dict((ng.id, seed_ng_dict[ng.name]) for ng in orig_ng) mapping[orig_cluster.get_admin_network_group().id] = \ seed_cluster.get_admin_network_group().id return mapping