# -*- 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 copy import collections import six from nailgun import consts from nailgun.db import db 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 from .transformations import vip from .transformations import volumes as volumes_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 get_net_key(net): group_name = None if net["group_id"]: group_name = objects.NodeGroup.get_by_uid(net["group_id"]).name return (net["name"], group_name) def merge_nets(a, b): new_settings = copy.deepcopy(b) source_networks = dict((get_net_key(net), net) for net in a["networks"]) for net in new_settings["networks"]: net_key = get_net_key(net) if net_key not in source_networks: continue source_net = source_networks[net_key] 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) vip_transformations = transformations.Lazy(vip.Manager) volumes_transformations = transformations.Lazy(volumes_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_node_groups(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) cls.sync_network_groups(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 sync_network_groups(cls, orig_cluster, new_cluster): cls.remove_network_groups(new_cluster) nodegroups_id_maping = cls.get_nodegroups_id_mapping(orig_cluster, new_cluster) release = new_cluster.release.id cls.copy_network_groups(orig_cluster, nodegroups_id_maping, release) @classmethod def remove_network_groups(cls, cluster): seed_ng = cluster.get_network_groups() for ng in seed_ng: if ng.name == 'fuelweb_admin': continue objects.NetworkGroup.delete(ng.network_group) @classmethod def copy_network_groups(cls, orig_cluster, nodegroups_id_maping, release): nets_serializer = cls.network_serializers[orig_cluster.net_provider] orig_net = nets_serializer.serialize_for_cluster(orig_cluster.cluster) for ng in orig_net['networks']: if ng['name'] == 'fuelweb_admin': continue meta = ng['meta'] metadata = { 'notation': 'cidr', 'render_type': None, 'map_priority': 2, 'configurable': True, 'use_gateway': False, 'name': ng['name'], 'vlan_start': ng['vlan_start'] } metadata.update(meta) if metadata['notation'] == 'ip_ranges': metadata['ip_range'] = ng['ip_ranges'][0] metadata['cidr'] = ng['cidr'] data = { 'name': ng['name'], 'release': release, 'vlan_start': ng['vlan_start'], 'cidr': ng['cidr'], 'gateway': ng['gateway'], 'group_id': nodegroups_id_maping[ng['group_id']], 'meta': metadata } objects.NetworkGroup.create(data) db().commit() @classmethod def copy_node_groups(cls, orig_cluster, new_cluster): for ng in orig_cluster.node_groups: if getattr(ng, 'is_default', False) or ng.name == 'default': continue data = { 'name': ng.name, 'cluster_id': new_cluster.id } objects.NodeGroup.create(data) @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 = orig_net_manager.get_assigned_vips( network_names=(consts.NETWORKS.public, consts.NETWORKS.management)) netgroups_id_mapping = cls.get_netgroups_id_mapping(orig_cluster, new_cluster) new_vips = cls.reassociate_vips(vips, netgroups_id_mapping) id_name_vips_mapping = cls.get_id_name_vips_mapping(new_vips) new_vips, id_name_vips_mapping = cls.vip_transformations.apply( orig_cluster.release.environment_version, new_cluster.release.environment_version, (new_vips, id_name_vips_mapping), ) new_net_manager.assign_given_vips_for_net_groups(new_vips) new_net_manager.assign_vips_for_net_groups() @classmethod def reassociate_vips(cls, vips, netgroups_id_mapping): new_vips = collections.defaultdict(dict) for orig_net_id, net_vips in vips.items(): new_net_id = netgroups_id_mapping[orig_net_id] new_vips[new_net_id] = net_vips return new_vips @classmethod def get_id_name_vips_mapping(self, vips): mapping = {} for vip_id in vips: mapping[vip_id] = \ adapters.NailgunNetworkGroupAdapter.get_by_uid(vip_id).name return mapping @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) volumes = cls.volumes_transformations.apply( orig_cluster.release.environment_version, seed_cluster.release.environment_version, node.get_volumes(), ) node.set_volumes(volumes) 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.nodegroup.name), ng.id) for ng in seed_ng) mapping = dict((ng.id, seed_ng_dict[(ng.name, ng.nodegroup.name)]) for ng in orig_ng) mapping[orig_cluster.get_admin_network_group().id] = \ seed_cluster.get_admin_network_group().id return mapping @classmethod def get_nodegroups_id_mapping(cls, orig_cluster, seed_cluster): orig_ng = orig_cluster.node_groups seed_ng = seed_cluster.node_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) return mapping @classmethod def validate_network_roles(cls, orig_cluster, seed_cluster): if not orig_cluster.network_template: return cls._compare_releases_roles(orig_cluster, seed_cluster) else: # TODO network template case return True @classmethod def _compare_releases_roles(cls, orig_cluster, seed_cluster): orig_roles = orig_cluster.get_network_roles() new_roles = seed_cluster.get_network_roles() orig_mapping = cls._get_release_mapping(orig_roles) new_mapping = cls._get_release_mapping(new_roles) return orig_mapping.issubset(new_mapping) @staticmethod def _get_release_mapping(roles): return {(role['id'], role['default_mapping']) for role in roles}