1812 lines
66 KiB
Python
1812 lines
66 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 collections import defaultdict
|
|
from copy import deepcopy
|
|
from itertools import groupby
|
|
|
|
from netaddr import IPNetwork
|
|
from sqlalchemy import and_
|
|
from sqlalchemy import or_
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
import math
|
|
import six
|
|
|
|
from nailgun import objects
|
|
|
|
from nailgun import consts
|
|
from nailgun.db import db
|
|
from nailgun.db.sqlalchemy.models import NetworkGroup
|
|
from nailgun.db.sqlalchemy.models import Node
|
|
from nailgun.errors import errors
|
|
from nailgun.logger import logger
|
|
from nailgun.objects import Cluster
|
|
from nailgun.settings import settings
|
|
from nailgun.utils import dict_merge
|
|
from nailgun.utils import extract_env_version
|
|
from nailgun.volumes import manager as volume_manager
|
|
|
|
|
|
def get_nodes_not_for_deletion(cluster):
|
|
"""All clusters nodes except nodes for deletion."""
|
|
return db().query(Node).filter(
|
|
and_(Node.cluster == cluster,
|
|
False == Node.pending_deletion)).order_by(Node.id)
|
|
|
|
|
|
class VmwareDeploymentSerializerMixin(object):
|
|
|
|
def generate_vmware_data(self, role, node):
|
|
"""Extend serialize data with vmware attributes
|
|
"""
|
|
vmware_data = {}
|
|
use_vcenter = node.cluster.attributes.editable.get('common', {}) \
|
|
.get('use_vcenter', {}).get('value')
|
|
|
|
if (use_vcenter and
|
|
role in ['controller', 'primary-controller', 'cinder-vmware']):
|
|
compute_instances = []
|
|
cinder_instances = []
|
|
|
|
vmware_attributes = node.cluster.vmware_attributes.editable \
|
|
.get('value', {})
|
|
availability_zones = vmware_attributes \
|
|
.get('availability_zones', {})
|
|
glance_instance = vmware_attributes.get('glance', {})
|
|
network = vmware_attributes.get('network', {})
|
|
|
|
for zone in availability_zones:
|
|
for compute in zone.get('nova_computes', {}):
|
|
compute_item = {
|
|
'availability_zone_name': zone.get('az_name', ''),
|
|
'vc_host': zone.get('vcenter_host', ''),
|
|
'vc_user': zone.get('vcenter_username', ''),
|
|
'vc_password': zone.get('vcenter_password', ''),
|
|
'service_name': compute.get('service_name', ''),
|
|
'vc_cluster': compute.get('vsphere_cluster', ''),
|
|
'datastore_regex': compute.get('datastore_regex', '')
|
|
}
|
|
|
|
compute_instances.append(compute_item)
|
|
|
|
cinder_instance = zone.get('cinder', {})
|
|
if cinder_instance.get('enable'):
|
|
cinder_item = {
|
|
'availability_zone_name': zone.get('az_name', ''),
|
|
'vc_host': zone.get('vcenter_host', ''),
|
|
'vc_user': zone.get('vcenter_username', ''),
|
|
'vc_password': zone.get('vcenter_password', '')
|
|
}
|
|
|
|
cinder_instances.append(cinder_item)
|
|
|
|
vmware_data['use_vcenter'] = True
|
|
|
|
if compute_instances:
|
|
vmware_data['vcenter'] = {
|
|
'esxi_vlan_interface':
|
|
network.get('esxi_vlan_interface', ''),
|
|
'computes': compute_instances
|
|
}
|
|
|
|
if cinder_instances:
|
|
vmware_data['cinder'] = {
|
|
'instances': cinder_instances
|
|
}
|
|
|
|
if glance_instance:
|
|
vmware_data['glance'] = {
|
|
'vc_host': glance_instance.get('vcenter_host', ''),
|
|
'vc_user': glance_instance.get('vcenter_username', ''),
|
|
'vc_password': glance_instance.get('vcenter_password', '')
|
|
}
|
|
|
|
return vmware_data
|
|
|
|
|
|
class NetworkDeploymentSerializer(object):
|
|
|
|
@classmethod
|
|
def update_nodes_net_info(cls, cluster, nodes):
|
|
"""Adds information about networks to each node."""
|
|
for node in get_nodes_not_for_deletion(cluster):
|
|
netw_data = node.network_data
|
|
addresses = {}
|
|
for net in node.cluster.network_groups:
|
|
if net.name == 'public' and \
|
|
not objects.Node.should_have_public(node):
|
|
continue
|
|
if net.meta.get('render_addr_mask'):
|
|
addresses.update(cls.get_addr_mask(
|
|
netw_data,
|
|
net.name,
|
|
net.meta.get('render_addr_mask')))
|
|
[n.update(addresses) for n in nodes
|
|
if n['uid'] == str(node.uid)]
|
|
return nodes
|
|
|
|
@classmethod
|
|
def get_common_attrs(cls, cluster, attrs):
|
|
"""Cluster network attributes."""
|
|
common = cls.network_provider_cluster_attrs(cluster)
|
|
common.update(
|
|
cls.network_ranges(Cluster.get_default_group(cluster).id))
|
|
common.update({'master_ip': settings.MASTER_IP})
|
|
|
|
common['nodes'] = deepcopy(attrs['nodes'])
|
|
common['nodes'] = cls.update_nodes_net_info(cluster, common['nodes'])
|
|
|
|
return common
|
|
|
|
@classmethod
|
|
def get_node_attrs(cls, node):
|
|
"""Node network attributes."""
|
|
return cls.network_provider_node_attrs(node.cluster, node)
|
|
|
|
@classmethod
|
|
def network_provider_cluster_attrs(cls, cluster):
|
|
raise NotImplementedError()
|
|
|
|
@classmethod
|
|
def network_provider_node_attrs(cls, cluster, node):
|
|
raise NotImplementedError()
|
|
|
|
@classmethod
|
|
def network_ranges(cls, group_id):
|
|
"""Returns ranges for network groups
|
|
except range for public network for each node
|
|
"""
|
|
ng_db = db().query(NetworkGroup).filter_by(group_id=group_id).all()
|
|
attrs = {}
|
|
for net in ng_db:
|
|
net_name = net.name + '_network_range'
|
|
if net.meta.get("render_type") == 'ip_ranges':
|
|
attrs[net_name] = cls.get_ip_ranges_first_last(net)
|
|
elif net.meta.get("render_type") == 'cidr' and net.cidr:
|
|
attrs[net_name] = net.cidr
|
|
return attrs
|
|
|
|
@classmethod
|
|
def get_ip_ranges_first_last(cls, network_group):
|
|
"""Get all ip ranges in "10.0.0.0-10.0.0.255" format
|
|
"""
|
|
return [
|
|
"{0}-{1}".format(ip_range.first, ip_range.last)
|
|
for ip_range in network_group.ip_ranges
|
|
]
|
|
|
|
@classmethod
|
|
def get_addr_mask(cls, network_data, net_name, render_name):
|
|
"""Get addr for network by name
|
|
"""
|
|
nets = filter(
|
|
lambda net: net['name'] == net_name,
|
|
network_data)
|
|
|
|
if not nets or 'ip' not in nets[0]:
|
|
raise errors.CanNotFindNetworkForNode(
|
|
'Cannot find network with name: %s' % net_name)
|
|
|
|
net = nets[0]['ip']
|
|
return {
|
|
render_name + '_address': str(IPNetwork(net).ip),
|
|
render_name + '_netmask': str(IPNetwork(net).netmask)
|
|
}
|
|
|
|
@staticmethod
|
|
def get_admin_ip_w_prefix(node):
|
|
"""Getting admin ip and assign prefix from admin network."""
|
|
network_manager = objects.Node.get_network_manager(node)
|
|
admin_ip = network_manager.get_admin_ip_for_node(node.id)
|
|
admin_ip = IPNetwork(admin_ip)
|
|
|
|
# Assign prefix from admin network
|
|
admin_net = IPNetwork(
|
|
network_manager.get_admin_network_group(node.id).cidr
|
|
)
|
|
admin_ip.prefixlen = admin_net.prefixlen
|
|
|
|
return str(admin_ip)
|
|
|
|
@classmethod
|
|
def add_bridge(cls, name, provider=None):
|
|
"""Add bridge to schema
|
|
It will take global provider if it is omitted here
|
|
"""
|
|
bridge = {
|
|
'action': 'add-br',
|
|
'name': name
|
|
}
|
|
if provider:
|
|
bridge['provider'] = provider
|
|
return bridge
|
|
|
|
@classmethod
|
|
def add_port(cls, name, bridge, provider=None):
|
|
"""Add port to schema
|
|
Bridge name may be None, port will not be connected to any bridge then
|
|
It will take global provider if it is omitted here
|
|
Port name can be in form "XX" or "XX.YY", where XX - NIC name,
|
|
YY - vlan id. E.g. "eth0", "eth0.1021". This will create corresponding
|
|
interface if name includes vlan id.
|
|
"""
|
|
port = {
|
|
'action': 'add-port',
|
|
'name': name
|
|
}
|
|
if bridge:
|
|
port['bridge'] = bridge
|
|
if provider:
|
|
bridge['provider'] = provider
|
|
return port
|
|
|
|
@classmethod
|
|
def add_bond(cls, iface, parameters):
|
|
"""Add bond to schema
|
|
All required parameters should be inside parameters dict. (e.g.
|
|
bond_properties, interface_properties, provider, bridge).
|
|
bond_properties is obligatory, others are optional.
|
|
bridge should be set if bridge for untagged network is to be connected
|
|
to bond. Ports are to be created for tagged networks which should be
|
|
connected to bond (e.g. port "bond-X.212" for bridge "br-ex").
|
|
"""
|
|
bond = {
|
|
'action': 'add-bond',
|
|
'name': iface.name,
|
|
'interfaces': sorted(x['name'] for x in iface.slaves),
|
|
}
|
|
if parameters:
|
|
bond.update(parameters)
|
|
return bond
|
|
|
|
@classmethod
|
|
def add_patch(cls, bridges, provider=None):
|
|
"""Add patch to schema
|
|
Patch connects two bridges listed in 'bridges'.
|
|
OVS bridge must go first in 'bridges'.
|
|
It will take global provider if it is omitted here
|
|
"""
|
|
patch = {
|
|
'action': 'add-patch',
|
|
'bridges': bridges,
|
|
}
|
|
if provider:
|
|
patch['provider'] = provider
|
|
return patch
|
|
|
|
|
|
class NovaNetworkDeploymentSerializer(NetworkDeploymentSerializer):
|
|
|
|
@classmethod
|
|
def network_provider_cluster_attrs(cls, cluster):
|
|
return {
|
|
'novanetwork_parameters': cls.novanetwork_attrs(cluster),
|
|
'dns_nameservers': cluster.network_config.dns_nameservers,
|
|
'fixed_network_range': cluster.network_config.fixed_networks_cidr,
|
|
'floating_network_range': [
|
|
"{0}-{1}".format(ip_range[0], ip_range[1])
|
|
for ip_range in cluster.network_config.floating_ranges
|
|
]
|
|
}
|
|
|
|
@classmethod
|
|
def network_provider_node_attrs(cls, cluster, node):
|
|
network_data = node.network_data
|
|
interfaces = cls.configure_interfaces(node)
|
|
cls.__add_hw_interfaces(interfaces, node.meta['interfaces'])
|
|
|
|
# Interfaces assignment
|
|
attrs = {'network_data': interfaces}
|
|
attrs.update(cls.interfaces_list(network_data))
|
|
|
|
if cluster.network_config.net_manager == 'VlanManager':
|
|
attrs.update(cls.add_vlan_interfaces(node))
|
|
|
|
return attrs
|
|
|
|
@classmethod
|
|
def novanetwork_attrs(cls, cluster):
|
|
"""Network configuration
|
|
"""
|
|
attrs = {'network_manager': cluster.network_config.net_manager}
|
|
|
|
# network_size is required for all managers, otherwise
|
|
# puppet will use default (255)
|
|
if attrs['network_manager'] == consts.NOVA_NET_MANAGERS.VlanManager:
|
|
attrs['num_networks'] = \
|
|
cluster.network_config.fixed_networks_amount
|
|
attrs['vlan_start'] = \
|
|
cluster.network_config.fixed_networks_vlan_start
|
|
attrs['network_size'] = cluster.network_config.fixed_network_size
|
|
elif (attrs['network_manager'] ==
|
|
consts.NOVA_NET_MANAGERS.FlatDHCPManager):
|
|
# We need set maximum available size for specific mask for FlatDHCP
|
|
# because default 256 caused problem
|
|
net_cidr = IPNetwork(cluster.network_config.fixed_networks_cidr)
|
|
attrs['network_size'] = net_cidr.size
|
|
attrs['num_networks'] = 1
|
|
|
|
return attrs
|
|
|
|
@classmethod
|
|
def add_vlan_interfaces(cls, node):
|
|
"""Assign fixed_interfaces and vlan_interface.
|
|
They should be equal.
|
|
"""
|
|
net_manager = objects.Node.get_network_manager(node)
|
|
fixed_interface = net_manager._get_interface_by_network_name(
|
|
node.id, 'fixed')
|
|
|
|
attrs = {'fixed_interface': fixed_interface.name,
|
|
'vlan_interface': fixed_interface.name}
|
|
return attrs
|
|
|
|
@classmethod
|
|
def configure_interfaces(cls, node):
|
|
"""Configure interfaces
|
|
"""
|
|
network_data = node.network_data
|
|
interfaces = {}
|
|
|
|
for network in network_data:
|
|
network_name = network['name']
|
|
name = cls.__make_interface_name(network.get('dev'),
|
|
network.get('vlan'))
|
|
|
|
interfaces.setdefault(name, {'interface': name, 'ipaddr': []})
|
|
interface = interfaces[name]
|
|
if network.get('ip'):
|
|
interface['ipaddr'].append(network.get('ip'))
|
|
|
|
if network_name == 'fuelweb_admin':
|
|
admin_ip_addr = cls.get_admin_ip_w_prefix(node)
|
|
interface['ipaddr'].append(admin_ip_addr)
|
|
elif network_name == 'public' and network.get('gateway'):
|
|
interface['gateway'] = network['gateway']
|
|
interface['default_gateway'] = True
|
|
|
|
for if_name, if_data in interfaces.iteritems():
|
|
if len(if_data['ipaddr']) == 0:
|
|
if_data['ipaddr'] = 'none'
|
|
|
|
interfaces['lo'] = {'interface': 'lo', 'ipaddr': ['127.0.0.1/8']}
|
|
|
|
return interfaces
|
|
|
|
@classmethod
|
|
def __make_interface_name(cls, name, vlan):
|
|
"""Make interface name
|
|
"""
|
|
if name and vlan:
|
|
return '.'.join([name, str(vlan)])
|
|
return name
|
|
|
|
@classmethod
|
|
def __add_hw_interfaces(cls, interfaces, hw_interfaces):
|
|
"""Add interfaces which not represents in
|
|
interfaces list but they are represented on node
|
|
"""
|
|
for hw_interface in hw_interfaces:
|
|
if hw_interface['name'] not in interfaces:
|
|
interfaces[hw_interface['name']] = {
|
|
'interface': hw_interface['name'],
|
|
'ipaddr': "none"
|
|
}
|
|
|
|
@classmethod
|
|
def interfaces_list(cls, network_data):
|
|
"""Generate list of interfaces
|
|
"""
|
|
interfaces = {}
|
|
for network in network_data:
|
|
if_name = cls.__make_interface_name(
|
|
network.get('dev'),
|
|
network.get('vlan'))
|
|
interfaces['%s_interface' % network['name']] = if_name
|
|
if network['name'] == 'public':
|
|
interfaces['floating_interface'] = if_name
|
|
return interfaces
|
|
|
|
|
|
class NovaNetworkDeploymentSerializer61(
|
|
NovaNetworkDeploymentSerializer
|
|
):
|
|
|
|
@classmethod
|
|
def network_provider_node_attrs(cls, cluster, node):
|
|
return {'network_scheme': cls.generate_network_scheme(node)}
|
|
|
|
@classmethod
|
|
def subiface_name(cls, iface_name, net_descr):
|
|
if not net_descr['vlan_id']:
|
|
return iface_name
|
|
else:
|
|
return "{0}.{1}".format(iface_name, net_descr['vlan_id'])
|
|
|
|
@classmethod
|
|
def generate_transformations(cls, node, nm, nets_by_ifaces,
|
|
fixed_sub_iface):
|
|
iface_types = consts.NETWORK_INTERFACE_TYPES
|
|
brnames = ['br-fw-admin', 'br-storage', 'br-mgmt', 'br-ex']
|
|
transformations = []
|
|
|
|
# add bridges for network roles
|
|
for brname in brnames:
|
|
transformations.append(cls.add_bridge(brname))
|
|
|
|
# fill up ports and bonds
|
|
bonded_ifaces = [x for x in node.nic_interfaces if x.bond]
|
|
for iface in node.interfaces:
|
|
if iface.type == iface_types.ether:
|
|
if iface in bonded_ifaces:
|
|
# don't create anything for slave NICs
|
|
continue
|
|
# add ports for all networks on every unbonded NIC
|
|
if iface.name in nets_by_ifaces:
|
|
for net in nets_by_ifaces[iface.name]:
|
|
sub_iface = cls.subiface_name(iface.name, net)
|
|
transformations.append(
|
|
cls.add_port(sub_iface, net['br_name']))
|
|
# we should avoid adding the port twice in case of
|
|
# VlanManager
|
|
if fixed_sub_iface == sub_iface:
|
|
fixed_sub_iface = None
|
|
elif iface.type == iface_types.bond:
|
|
# Add bonds and connect untagged networks' bridges to them.
|
|
# There can be no more than one untagged network on each bond.
|
|
bond_params = {
|
|
'bond_properties': nm.get_lnx_bond_properties(iface),
|
|
'interface_properties': nm.get_iface_properties(iface)
|
|
}
|
|
bond_ports = []
|
|
if iface.name in nets_by_ifaces:
|
|
for net in nets_by_ifaces[iface.name]:
|
|
if net['vlan_id']:
|
|
bond_ports.append(cls.add_port(
|
|
cls.subiface_name(iface.name, net),
|
|
net['br_name']))
|
|
else:
|
|
bond_params['bridge'] = net['br_name']
|
|
transformations.append(cls.add_bond(iface, bond_params))
|
|
transformations.extend(bond_ports)
|
|
# add manager-related ports
|
|
if fixed_sub_iface:
|
|
transformations.append(cls.add_port(fixed_sub_iface, None))
|
|
return transformations
|
|
|
|
@classmethod
|
|
def generate_network_scheme(cls, node):
|
|
|
|
# create network scheme structure and fill it with static values
|
|
attrs = {
|
|
'version': '1.1',
|
|
'provider': 'lnx',
|
|
'interfaces': {},
|
|
'endpoints': {},
|
|
'roles': {
|
|
'fw-admin': 'br-fw-admin',
|
|
'storage': 'br-storage',
|
|
'management': 'br-mgmt',
|
|
'ex': 'br-ex',
|
|
},
|
|
}
|
|
|
|
netgroup_mapping = [
|
|
('fuelweb_admin', 'br-fw-admin'),
|
|
('storage', 'br-storage'),
|
|
('management', 'br-mgmt'),
|
|
('public', 'br-ex'),
|
|
('fixed', '') # will be determined in code below
|
|
]
|
|
|
|
nm = objects.Node.get_network_manager(node)
|
|
|
|
# populate IP address information to endpoints
|
|
netgroups = {}
|
|
nets_by_ifaces = defaultdict(list)
|
|
fixed_sub_iface = None
|
|
for ngname, brname in netgroup_mapping:
|
|
# Here we get a dict with network description for this particular
|
|
# node with its assigned IPs and device names for each network.
|
|
netgroup = nm.get_node_network_by_netname(node, ngname)
|
|
if ngname == 'fixed':
|
|
vlan_id = None
|
|
if node.cluster.network_config.net_manager == \
|
|
consts.NOVA_NET_MANAGERS.FlatDHCPManager:
|
|
vlan_id = \
|
|
node.cluster.network_config.fixed_networks_vlan_start
|
|
net = {'vlan_id': vlan_id}
|
|
fixed_sub_iface = cls.subiface_name(netgroup['dev'], net)
|
|
attrs['endpoints'][fixed_sub_iface] = {'IP': 'none'}
|
|
else:
|
|
nets_by_ifaces[netgroup['dev']].append({
|
|
'br_name': brname,
|
|
'vlan_id': netgroup['vlan']
|
|
})
|
|
if netgroup.get('ip'):
|
|
attrs['endpoints'][brname] = {'IP': [netgroup['ip']]}
|
|
netgroups[ngname] = netgroup
|
|
|
|
attrs['endpoints']['br-ex']['gateway'] = \
|
|
netgroups['public']['gateway']
|
|
|
|
# add manager-related roles
|
|
if node.cluster.network_config.net_manager == \
|
|
consts.NOVA_NET_MANAGERS.VlanManager:
|
|
attrs['roles']['novanetwork/vlan'] = fixed_sub_iface
|
|
else:
|
|
attrs['roles']['novanetwork/fixed'] = fixed_sub_iface
|
|
|
|
for iface in node.nic_interfaces:
|
|
if iface.bond:
|
|
attrs['interfaces'][iface.name] = {}
|
|
else:
|
|
attrs['interfaces'][iface.name] = \
|
|
nm.get_iface_properties(iface)
|
|
|
|
attrs['transformations'] = \
|
|
cls.generate_transformations(node, nm, nets_by_ifaces,
|
|
fixed_sub_iface)
|
|
|
|
return attrs
|
|
|
|
|
|
class NeutronNetworkDeploymentSerializer(NetworkDeploymentSerializer):
|
|
|
|
@classmethod
|
|
def network_provider_cluster_attrs(cls, cluster):
|
|
"""Cluster attributes."""
|
|
attrs = {'quantum': True,
|
|
'quantum_settings': cls.neutron_attrs(cluster)}
|
|
|
|
if cluster.mode == 'multinode':
|
|
for node in cluster.nodes:
|
|
if cls._node_has_role_by_name(node, 'controller'):
|
|
net_manager = objects.Node.get_network_manager(node)
|
|
mgmt_cidr = net_manager.get_node_network_by_netname(
|
|
node,
|
|
'management'
|
|
)['ip']
|
|
attrs['management_vip'] = mgmt_cidr.split('/')[0]
|
|
break
|
|
|
|
return attrs
|
|
|
|
@classmethod
|
|
def network_provider_node_attrs(cls, cluster, node):
|
|
"""Serialize node, then it will be
|
|
merged with common attributes
|
|
"""
|
|
node_attrs = {'network_scheme': cls.generate_network_scheme(node)}
|
|
node_attrs = cls.mellanox_settings(node_attrs, node)
|
|
return node_attrs
|
|
|
|
@classmethod
|
|
def mellanox_settings(cls, node_attrs, node):
|
|
"""Serialize mellanox node attrs, then it will be
|
|
merged with common attributes, if mellanox plugin or iSER storage
|
|
enabled.
|
|
"""
|
|
# Get Mellanox data
|
|
neutron_mellanox_data = \
|
|
Cluster.get_attributes(node.cluster).editable\
|
|
.get('neutron_mellanox', {})
|
|
|
|
# Get storage data
|
|
storage_data = \
|
|
Cluster.get_attributes(node.cluster).editable.get('storage', {})
|
|
|
|
# Get network manager
|
|
nm = objects.Node.get_network_manager(node)
|
|
|
|
# Init mellanox dict
|
|
node_attrs['neutron_mellanox'] = {}
|
|
|
|
# Find Physical port for VFs generation
|
|
if 'plugin' in neutron_mellanox_data and \
|
|
neutron_mellanox_data['plugin']['value'] == 'ethernet':
|
|
node_attrs = cls.set_mellanox_ml2_config(node_attrs, node, nm)
|
|
|
|
# Fix network scheme to have physical port for RDMA if iSER enabled
|
|
if 'iser' in storage_data and storage_data['iser']['value']:
|
|
node_attrs = cls.fix_iser_port(node_attrs, node, nm)
|
|
|
|
return node_attrs
|
|
|
|
@classmethod
|
|
def set_mellanox_ml2_config(cls, node_attrs, node, nm):
|
|
"""Change the yaml file to include the required configurations
|
|
for ml2 mellanox mechanism driver.
|
|
should be called only in case of mellanox SR-IOV plugin usage.
|
|
"""
|
|
# Set physical port for SR-IOV virtual functions
|
|
node_attrs['neutron_mellanox']['physical_port'] = \
|
|
nm.get_node_network_by_netname(node, 'private')['dev']
|
|
|
|
# Set ML2 eswitch section conf
|
|
ml2_eswitch = {}
|
|
ml2_eswitch['vnic_type'] = 'hostdev'
|
|
ml2_eswitch['apply_profile_patch'] = True
|
|
node_attrs['neutron_mellanox']['ml2_eswitch'] = ml2_eswitch
|
|
|
|
return node_attrs
|
|
|
|
@classmethod
|
|
def fix_iser_port(cls, node_attrs, node, nm):
|
|
"""Change the iser port to eth_iser probed (VF on the HV) interface
|
|
instead of br-storage. that change is made due to RDMA
|
|
(Remote Direct Memory Access) limitation of working with physical
|
|
interfaces.
|
|
"""
|
|
# Set a new unique name for iSER virtual port
|
|
iser_new_name = 'eth_iser0'
|
|
|
|
# Add iSER extra params to astute.yaml
|
|
node_attrs['neutron_mellanox']['storage_parent'] = \
|
|
nm.get_node_network_by_netname(node, 'storage')['dev']
|
|
node_attrs['neutron_mellanox']['iser_interface_name'] = iser_new_name
|
|
|
|
# Get VLAN if exists
|
|
storage_vlan = \
|
|
nm.get_node_network_by_netname(node, 'storage').get('vlan')
|
|
|
|
if storage_vlan:
|
|
vlan_name = "vlan{0}".format(storage_vlan)
|
|
|
|
# Set storage rule to iSER interface vlan interface
|
|
node_attrs['network_scheme']['roles']['storage'] = vlan_name
|
|
|
|
# Set iSER interface vlan interface
|
|
node_attrs['network_scheme']['interfaces'][vlan_name] = \
|
|
{'L2': {'vlan_splinters': 'off'}}
|
|
node_attrs['network_scheme']['endpoints'][vlan_name] = \
|
|
node_attrs['network_scheme']['endpoints'].pop('br-storage', {})
|
|
node_attrs['network_scheme']['endpoints'][vlan_name]['vlandev'] = \
|
|
iser_new_name
|
|
else:
|
|
|
|
# Set storage rule to iSER port
|
|
node_attrs['network_scheme']['roles']['storage'] = iser_new_name
|
|
|
|
# Set iSER endpoint with br-storage parameters
|
|
node_attrs['network_scheme']['endpoints'][iser_new_name] = \
|
|
node_attrs['network_scheme']['endpoints'].pop('br-storage', {})
|
|
node_attrs['network_scheme']['interfaces'][iser_new_name] = \
|
|
{'L2': {'vlan_splinters': 'off'}}
|
|
|
|
return node_attrs
|
|
|
|
@classmethod
|
|
def _node_has_role_by_name(cls, node, rolename):
|
|
if rolename in node.pending_roles or rolename in node.roles:
|
|
return True
|
|
return False
|
|
|
|
@classmethod
|
|
def neutron_attrs(cls, cluster):
|
|
"""Network configuration for Neutron
|
|
"""
|
|
attrs = {}
|
|
attrs['L3'] = cls.generate_l3(cluster)
|
|
attrs['L2'] = cls.generate_l2(cluster)
|
|
attrs['predefined_networks'] = \
|
|
cls.generate_predefined_networks(cluster)
|
|
|
|
if cluster.release.operating_system == 'RHEL':
|
|
attrs['amqp'] = {'provider': 'qpid-rh'}
|
|
|
|
cluster_attrs = Cluster.get_attributes(cluster).editable
|
|
if 'nsx_plugin' in cluster_attrs and \
|
|
cluster_attrs['nsx_plugin']['metadata']['enabled']:
|
|
attrs['L2']['provider'] = 'nsx'
|
|
|
|
return attrs
|
|
|
|
@classmethod
|
|
def generate_network_scheme(cls, node):
|
|
|
|
# Create a data structure and fill it with static values.
|
|
|
|
attrs = {
|
|
'version': '1.0',
|
|
'provider': 'ovs',
|
|
'interfaces': {}, # It's a list of physical interfaces.
|
|
'endpoints': {
|
|
'br-storage': {},
|
|
'br-mgmt': {},
|
|
'br-fw-admin': {},
|
|
},
|
|
'roles': {
|
|
'management': 'br-mgmt',
|
|
'storage': 'br-storage',
|
|
'fw-admin': 'br-fw-admin',
|
|
},
|
|
'transformations': []
|
|
}
|
|
|
|
if objects.Node.should_have_public(node):
|
|
attrs['endpoints']['br-ex'] = {}
|
|
attrs['roles']['ex'] = 'br-ex'
|
|
|
|
nm = objects.Node.get_network_manager(node)
|
|
iface_types = consts.NETWORK_INTERFACE_TYPES
|
|
|
|
# Add a dynamic data to a structure.
|
|
|
|
vlan_splinters_data = \
|
|
node.cluster.attributes.editable\
|
|
.get('vlan_splinters', {})\
|
|
|
|
# if vlan_splinters is enabled - use its value
|
|
use_vlan_splinters = 'disabled'
|
|
if vlan_splinters_data\
|
|
.get('metadata', {})\
|
|
.get('enabled'):
|
|
|
|
use_vlan_splinters = \
|
|
vlan_splinters_data\
|
|
.get('vswitch', {})\
|
|
.get('value', 'disabled')
|
|
|
|
# Fill up interfaces and add bridges for them.
|
|
bonded_ifaces = [x for x in node.nic_interfaces if x.bond]
|
|
for iface in node.interfaces:
|
|
# Handle vlan splinters.
|
|
if iface.type == iface_types.ether:
|
|
attrs['interfaces'][iface.name] = {
|
|
'L2': cls._get_vlan_splinters_desc(
|
|
use_vlan_splinters, iface, node.cluster
|
|
)
|
|
}
|
|
|
|
if iface in bonded_ifaces:
|
|
continue
|
|
attrs['transformations'].append({
|
|
'action': 'add-br',
|
|
'name': 'br-%s' % iface.name
|
|
})
|
|
if iface.type == iface_types.ether:
|
|
attrs['transformations'].append({
|
|
'action': 'add-port',
|
|
'bridge': 'br-%s' % iface.name,
|
|
'name': iface.name
|
|
})
|
|
elif iface.type == iface_types.bond:
|
|
attrs['transformations'].append({
|
|
'action': 'add-bond',
|
|
'bridge': 'br-%s' % iface.name,
|
|
'name': iface.name,
|
|
'interfaces': [x['name'] for x in iface.slaves],
|
|
'properties': nm.get_ovs_bond_properties(iface)
|
|
})
|
|
|
|
# Add bridges for networks.
|
|
# We have to add them after br-ethXX bridges because it is the way
|
|
# to provide a right ordering of ifdown/ifup operations with
|
|
# IP interfaces.
|
|
brnames = ['br-ex', 'br-mgmt', 'br-storage', 'br-fw-admin']
|
|
if not objects.Node.should_have_public(node):
|
|
brnames.pop(0)
|
|
|
|
for brname in brnames:
|
|
attrs['transformations'].append({
|
|
'action': 'add-br',
|
|
'name': brname
|
|
})
|
|
|
|
# Populate IP address information to endpoints.
|
|
netgroup_mapping = [
|
|
('storage', 'br-storage'),
|
|
('management', 'br-mgmt'),
|
|
('fuelweb_admin', 'br-fw-admin'),
|
|
]
|
|
if objects.Node.should_have_public(node):
|
|
netgroup_mapping.append(('public', 'br-ex'))
|
|
|
|
netgroups = {}
|
|
for ngname, brname in netgroup_mapping:
|
|
# Here we get a dict with network description for this particular
|
|
# node with its assigned IPs and device names for each network.
|
|
netgroup = nm.get_node_network_by_netname(node, ngname)
|
|
if netgroup.get('ip'):
|
|
attrs['endpoints'][brname]['IP'] = [netgroup['ip']]
|
|
netgroups[ngname] = netgroup
|
|
|
|
if objects.Node.should_have_public(node):
|
|
attrs['endpoints']['br-ex']['gateway'] = \
|
|
netgroups['public']['gateway']
|
|
else:
|
|
attrs['endpoints']['br-fw-admin']['gateway'] = settings.MASTER_IP
|
|
|
|
# Connect interface bridges to network bridges.
|
|
for ngname, brname in netgroup_mapping:
|
|
netgroup = nm.get_node_network_by_netname(node, ngname)
|
|
if not netgroup['vlan']:
|
|
# Untagged network.
|
|
attrs['transformations'].append({
|
|
'action': 'add-patch',
|
|
'bridges': ['br-%s' % netgroup['dev'], brname],
|
|
'trunks': [0]
|
|
})
|
|
elif netgroup['vlan'] > 1:
|
|
# Tagged network.
|
|
attrs['transformations'].append({
|
|
'action': 'add-patch',
|
|
'bridges': ['br-%s' % netgroup['dev'], brname],
|
|
'tags': [netgroup['vlan'], 0]
|
|
})
|
|
else:
|
|
# FIXME! Should raise some exception I think.
|
|
logger.error('Invalid vlan for network: %s' % str(netgroup))
|
|
|
|
# Dance around Neutron segmentation type.
|
|
if node.cluster.network_config.segmentation_type == 'vlan':
|
|
attrs['endpoints']['br-prv'] = {'IP': 'none'}
|
|
attrs['roles']['private'] = 'br-prv'
|
|
|
|
attrs['transformations'].append({
|
|
'action': 'add-br',
|
|
'name': 'br-prv',
|
|
})
|
|
|
|
attrs['transformations'].append({
|
|
'action': 'add-patch',
|
|
'bridges': [
|
|
'br-%s' % nm.get_node_interface_by_netname(
|
|
node.id,
|
|
'private'
|
|
).name,
|
|
'br-prv'
|
|
]
|
|
})
|
|
elif node.cluster.network_config.segmentation_type == 'gre':
|
|
attrs['roles']['mesh'] = 'br-mgmt'
|
|
|
|
return attrs
|
|
|
|
@classmethod
|
|
def _get_vlan_splinters_desc(cls, use_vlan_splinters, iface,
|
|
cluster):
|
|
iface_attrs = {}
|
|
if use_vlan_splinters in ('disabled', 'kernel_lt'):
|
|
iface_attrs['vlan_splinters'] = 'off'
|
|
return iface_attrs
|
|
iface_attrs['vlan_splinters'] = 'auto'
|
|
trunks = [0]
|
|
|
|
if use_vlan_splinters == 'hard':
|
|
for ng in iface.assigned_networks_list:
|
|
if ng.name == 'private':
|
|
vlan_range = cluster.network_config.vlan_range
|
|
trunks.extend(xrange(*vlan_range))
|
|
trunks.append(vlan_range[1])
|
|
else:
|
|
if ng.vlan_start in (0, None):
|
|
continue
|
|
trunks.append(ng.vlan_start)
|
|
elif use_vlan_splinters == 'soft':
|
|
pass
|
|
else:
|
|
logger.warn('Invalid vlan_splinters value: %s', use_vlan_splinters)
|
|
return {}
|
|
|
|
iface_attrs['trunks'] = trunks
|
|
|
|
return iface_attrs
|
|
|
|
@classmethod
|
|
def _generate_external_network(cls, cluster):
|
|
public_cidr, public_gw = db().query(
|
|
NetworkGroup.cidr,
|
|
NetworkGroup.gateway
|
|
).filter_by(
|
|
group_id=Cluster.get_default_group(cluster).id,
|
|
name='public'
|
|
).first()
|
|
join_range = lambda r: (":".join(map(str, r)) if r else None)
|
|
return {
|
|
"L3": {
|
|
"subnet": public_cidr,
|
|
"gateway": public_gw,
|
|
"nameservers": [],
|
|
"floating": join_range(
|
|
cluster.network_config.floating_ranges[0]),
|
|
"enable_dhcp": False
|
|
},
|
|
"L2": {
|
|
"network_type": "flat",
|
|
"segment_id": None,
|
|
"router_ext": True,
|
|
"physnet": "physnet1"
|
|
},
|
|
"tenant": objects.Cluster.get_creds(cluster)['tenant']['value'],
|
|
"shared": False
|
|
}
|
|
|
|
@classmethod
|
|
def _generate_internal_network(cls, cluster):
|
|
return {
|
|
"L3": {
|
|
"subnet": cluster.network_config.internal_cidr,
|
|
"gateway": cluster.network_config.internal_gateway,
|
|
"nameservers": cluster.network_config.dns_nameservers,
|
|
"floating": None,
|
|
"enable_dhcp": True
|
|
},
|
|
"L2": {
|
|
"network_type": cluster.network_config.segmentation_type,
|
|
"segment_id": None,
|
|
"router_ext": False,
|
|
"physnet": "physnet2"
|
|
if cluster.network_config.segmentation_type == "vlan" else None
|
|
},
|
|
"tenant": objects.Cluster.get_creds(cluster)['tenant']['value'],
|
|
"shared": False
|
|
}
|
|
|
|
@classmethod
|
|
def generate_predefined_networks(cls, cluster):
|
|
return {
|
|
"net04_ext": cls._generate_external_network(cluster),
|
|
"net04": cls._generate_internal_network(cluster)
|
|
}
|
|
|
|
@classmethod
|
|
def generate_l2(cls, cluster):
|
|
join_range = lambda r: (":".join(map(str, r)) if r else None)
|
|
res = {
|
|
"base_mac": cluster.network_config.base_mac,
|
|
"segmentation_type": cluster.network_config.segmentation_type,
|
|
"phys_nets": {
|
|
"physnet1": {
|
|
"bridge": "br-ex",
|
|
"vlan_range": None
|
|
}
|
|
}
|
|
}
|
|
if cluster.network_config.segmentation_type == 'gre':
|
|
res["tunnel_id_ranges"] = join_range(
|
|
cluster.network_config.gre_id_range)
|
|
elif cluster.network_config.segmentation_type == 'vlan':
|
|
res["phys_nets"]["physnet2"] = {
|
|
"bridge": "br-prv",
|
|
"vlan_range": join_range(cluster.network_config.vlan_range)
|
|
}
|
|
|
|
# Set non-default ml2 configurations
|
|
attrs = Cluster.get_attributes(cluster).editable
|
|
if 'neutron_mellanox' in attrs and \
|
|
attrs['neutron_mellanox']['plugin']['value'] == 'ethernet':
|
|
res['mechanism_drivers'] = 'mlnx,openvswitch'
|
|
seg_type = cluster.network_config.segmentation_type
|
|
res['tenant_network_types'] = seg_type
|
|
res['type_drivers'] = '{0},flat,local'.format(seg_type)
|
|
|
|
return res
|
|
|
|
@classmethod
|
|
def generate_l3(cls, cluster):
|
|
l3 = {
|
|
"use_namespaces": True
|
|
}
|
|
attrs = Cluster.get_attributes(cluster).editable
|
|
if 'nsx_plugin' in attrs and \
|
|
attrs['nsx_plugin']['metadata']['enabled']:
|
|
dhcp_attrs = l3.setdefault('dhcp_agent', {})
|
|
dhcp_attrs['enable_isolated_metadata'] = True
|
|
dhcp_attrs['enable_metadata_network'] = True
|
|
|
|
return l3
|
|
|
|
|
|
class NeutronNetworkDeploymentSerializer51(NeutronNetworkDeploymentSerializer):
|
|
|
|
@classmethod
|
|
def _generate_external_network(cls, cluster):
|
|
ext_netw = super(NeutronNetworkDeploymentSerializer51, cls).\
|
|
_generate_external_network(cluster)
|
|
ext_netw["L2"] = {
|
|
"network_type": "local",
|
|
"segment_id": None,
|
|
"router_ext": True,
|
|
"physnet": None
|
|
}
|
|
return ext_netw
|
|
|
|
@classmethod
|
|
def generate_l2(cls, cluster):
|
|
l2 = super(NeutronNetworkDeploymentSerializer51, cls).\
|
|
generate_l2(cluster)
|
|
l2["phys_nets"].pop("physnet1")
|
|
return l2
|
|
|
|
|
|
class NeutronNetworkDeploymentSerializer60(
|
|
NeutronNetworkDeploymentSerializer51
|
|
):
|
|
|
|
@classmethod
|
|
def generate_network_scheme(cls, node):
|
|
attrs = super(NeutronNetworkDeploymentSerializer60, cls). \
|
|
generate_network_scheme(node)
|
|
|
|
for item in attrs.get('transformations', ()):
|
|
if 'tags' in item:
|
|
item['vlan_ids'] = item['tags']
|
|
|
|
# Include information about all subnets that don't belong to this node.
|
|
# This is used during deployment to configure routes to all other
|
|
# networks in the environment.
|
|
nm = objects.Node.get_network_manager(node)
|
|
other_nets = nm.get_networks_not_on_node(node)
|
|
|
|
netgroup_mapping = [
|
|
('storage', 'br-storage'),
|
|
('management', 'br-mgmt'),
|
|
('fuelweb_admin', 'br-fw-admin'),
|
|
]
|
|
if objects.Node.should_have_public(node):
|
|
netgroup_mapping.append(('public', 'br-ex'))
|
|
|
|
for ngname, brname in netgroup_mapping:
|
|
netgroup = nm.get_node_network_by_netname(node, ngname)
|
|
if netgroup.get('gateway'):
|
|
attrs['endpoints'][brname]['gateway'] = netgroup['gateway']
|
|
attrs['endpoints'][brname]['other_nets'] = \
|
|
other_nets.get(ngname, [])
|
|
|
|
if objects.Node.should_have_public(node):
|
|
attrs['endpoints']['br-ex']['default_gateway'] = True
|
|
else:
|
|
gw = nm.get_default_gateway(node.id)
|
|
attrs['endpoints']['br-fw-admin']['gateway'] = gw
|
|
attrs['endpoints']['br-fw-admin']['default_gateway'] = True
|
|
|
|
return attrs
|
|
|
|
|
|
class NeutronNetworkDeploymentSerializer61(
|
|
NeutronNetworkDeploymentSerializer60
|
|
):
|
|
|
|
@classmethod
|
|
def subiface_name(cls, iface_name, net_descr):
|
|
if not net_descr['vlan_id']:
|
|
return iface_name
|
|
else:
|
|
return "{0}.{1}".format(iface_name, net_descr['vlan_id'])
|
|
|
|
@classmethod
|
|
def generate_routes(cls, node, attrs, nm, netgroup_mapping, netgroups):
|
|
other_nets = nm.get_networks_not_on_node(node)
|
|
|
|
for ngname, brname in netgroup_mapping:
|
|
netgroup = netgroups[ngname]
|
|
if netgroup.get('gateway'):
|
|
via = netgroup['gateway']
|
|
attrs['endpoints'][brname]['routes'] = []
|
|
for cidr in other_nets.get(ngname, []):
|
|
attrs['endpoints'][brname]['routes'].append({
|
|
'net': cidr,
|
|
'via': via
|
|
})
|
|
|
|
@classmethod
|
|
def generate_transformations(cls, node, nm, nets_by_ifaces, is_public,
|
|
prv_base_ep):
|
|
transformations = []
|
|
|
|
iface_types = consts.NETWORK_INTERFACE_TYPES
|
|
brnames = ['br-fw-admin', 'br-mgmt', 'br-storage']
|
|
if is_public:
|
|
brnames.append('br-ex')
|
|
|
|
# Add bridges for networks.
|
|
for brname in brnames:
|
|
transformations.append(cls.add_bridge(brname))
|
|
|
|
if is_public:
|
|
# br-floating is an OVS bridge and it's always connected with br-ex
|
|
transformations.append(
|
|
cls.add_bridge('br-floating', provider='ovs'))
|
|
transformations.append(cls.add_patch(
|
|
bridges=['br-floating', 'br-ex'],
|
|
provider='ovs'))
|
|
|
|
# Dance around Neutron segmentation type.
|
|
if node.cluster.network_config.segmentation_type == 'vlan':
|
|
transformations.append(
|
|
cls.add_bridge('br-prv', provider='ovs'))
|
|
if prv_base_ep:
|
|
transformations.append(cls.add_patch(
|
|
bridges=['br-prv', prv_base_ep],
|
|
provider='ovs'))
|
|
else:
|
|
transformations.append(cls.add_bridge('br-aux'))
|
|
transformations.append(cls.add_patch(
|
|
bridges=['br-prv', 'br-aux'],
|
|
provider='ovs'))
|
|
|
|
# Add ports and bonds.
|
|
bonded_ifaces = [x for x in node.nic_interfaces if x.bond]
|
|
for iface in node.interfaces:
|
|
if iface.type == iface_types.ether:
|
|
if iface in bonded_ifaces:
|
|
# Don't create anything for slave NICs.
|
|
continue
|
|
# Add ports for all networks on every unbonded NIC.
|
|
if iface.name in nets_by_ifaces:
|
|
for net in nets_by_ifaces[iface.name]:
|
|
transformations.append(cls.add_port(
|
|
cls.subiface_name(iface.name, net),
|
|
net['br_name']))
|
|
elif iface.type == iface_types.bond:
|
|
# Add bonds and connect untagged networks' bridges to them.
|
|
# There can be no more than one untagged network on each bond.
|
|
bond_params = {
|
|
'bond_properties': nm.get_lnx_bond_properties(iface),
|
|
'interface_properties': nm.get_iface_properties(iface)
|
|
}
|
|
bond_ports = []
|
|
if iface.name in nets_by_ifaces:
|
|
for net in nets_by_ifaces[iface.name]:
|
|
if net['vlan_id']:
|
|
bond_ports.append(cls.add_port(
|
|
cls.subiface_name(iface.name, net),
|
|
net['br_name']))
|
|
else:
|
|
bond_params['bridge'] = net['br_name']
|
|
transformations.append(cls.add_bond(iface, bond_params))
|
|
transformations.extend(bond_ports)
|
|
|
|
return transformations
|
|
|
|
@classmethod
|
|
def generate_network_scheme(cls, node):
|
|
|
|
# Create a data structure and fill it with static values.
|
|
attrs = {
|
|
'version': '1.1',
|
|
'provider': 'lnx',
|
|
'interfaces': {}, # It's a list of physical interfaces.
|
|
'endpoints': {},
|
|
'roles': {
|
|
'management': 'br-mgmt',
|
|
'storage': 'br-storage',
|
|
'fw-admin': 'br-fw-admin',
|
|
},
|
|
}
|
|
|
|
is_public = objects.Node.should_have_public(node)
|
|
if is_public:
|
|
attrs['endpoints']['br-ex'] = {}
|
|
attrs['endpoints']['br-floating'] = {'IP': 'none'}
|
|
attrs['roles']['ex'] = 'br-ex'
|
|
attrs['roles']['neutron/floating'] = 'br-floating'
|
|
|
|
nm = objects.Node.get_network_manager(node)
|
|
|
|
# Populate IP and GW information to endpoints.
|
|
netgroup_mapping = [
|
|
('storage', 'br-storage'),
|
|
('management', 'br-mgmt'),
|
|
('fuelweb_admin', 'br-fw-admin'),
|
|
]
|
|
if is_public:
|
|
netgroup_mapping.append(('public', 'br-ex'))
|
|
|
|
netgroups = {}
|
|
nets_by_ifaces = defaultdict(list)
|
|
for ngname, brname in netgroup_mapping:
|
|
# Here we get a dict with network description for this particular
|
|
# node with its assigned IPs and device names for each network.
|
|
netgroup = nm.get_node_network_by_netname(node, ngname)
|
|
if netgroup.get('ip'):
|
|
attrs['endpoints'][brname] = {'IP': [netgroup['ip']]}
|
|
netgroups[ngname] = netgroup
|
|
nets_by_ifaces[netgroup['dev']].append({
|
|
'br_name': brname,
|
|
'vlan_id': netgroup['vlan']
|
|
})
|
|
|
|
# Add gateway.
|
|
if is_public:
|
|
attrs['endpoints']['br-ex']['gateway'] = \
|
|
netgroups['public']['gateway']
|
|
else:
|
|
attrs['endpoints']['br-fw-admin']['gateway'] = \
|
|
nm.get_default_gateway(node.id)
|
|
|
|
# Fill up interfaces.
|
|
for iface in node.nic_interfaces:
|
|
if iface.bond:
|
|
attrs['interfaces'][iface.name] = {}
|
|
else:
|
|
attrs['interfaces'][iface.name] = \
|
|
nm.get_iface_properties(iface)
|
|
|
|
# Dance around Neutron segmentation type.
|
|
prv_base_ep = None
|
|
if node.cluster.network_config.segmentation_type == 'vlan':
|
|
attrs['endpoints']['br-prv'] = {'IP': 'none'}
|
|
attrs['roles']['neutron/private'] = 'br-prv'
|
|
|
|
netgroup = nm.get_node_network_by_netname(node, 'private')
|
|
# create br-aux if there is no untagged network (endpoint) on the
|
|
# same interface.
|
|
if netgroup['dev'] in nets_by_ifaces:
|
|
for ep in nets_by_ifaces[netgroup['dev']]:
|
|
if not ep['vlan_id']:
|
|
prv_base_ep = ep['br_name']
|
|
if not prv_base_ep:
|
|
nets_by_ifaces[netgroup['dev']].append({
|
|
'br_name': 'br-aux',
|
|
'vlan_id': None
|
|
})
|
|
elif node.cluster.network_config.segmentation_type == 'gre':
|
|
attrs['roles']['neutron/mesh'] = 'br-mgmt'
|
|
|
|
attrs['transformations'] = cls.generate_transformations(
|
|
node, nm, nets_by_ifaces, is_public, prv_base_ep)
|
|
|
|
if objects.NodeGroupCollection.get_by_cluster_id(
|
|
node.cluster.id).count() > 1:
|
|
cls.generate_routes(node, attrs, nm, netgroup_mapping, netgroups)
|
|
|
|
attrs = cls.generate_driver_information(node, attrs, nm)
|
|
|
|
return attrs
|
|
|
|
@classmethod
|
|
def generate_driver_information(cls, node, network_scheme, nm):
|
|
|
|
network_mapping = network_scheme.get('roles', {})
|
|
endpoints = network_scheme.get('endpoints', {})
|
|
bonds_map = dict((b.name, b) for b in node.bond_interfaces)
|
|
net_name_mapping = {'ex': 'public'}
|
|
managed_networks = ['public', 'storage', 'management']
|
|
|
|
# Add interfaces drivers data
|
|
for iface in node.nic_interfaces:
|
|
if iface.driver or iface.bus_info:
|
|
iface_dict = network_scheme['interfaces'][iface.name]
|
|
if 'vendor_specific' not in iface_dict:
|
|
iface_dict['vendor_specific'] = {}
|
|
if iface.driver:
|
|
iface_dict['vendor_specific']['driver'] = iface.driver
|
|
if iface.bus_info:
|
|
iface_dict['vendor_specific']['bus_info'] = iface.bus_info
|
|
|
|
# Add physical allocation data
|
|
for ngname, brname in six.iteritems(network_mapping):
|
|
if ngname in net_name_mapping:
|
|
ngname = net_name_mapping[ngname]
|
|
if ngname not in managed_networks:
|
|
continue
|
|
if 'vendor_specific' not in endpoints[brname]:
|
|
endpoints[brname]['vendor_specific'] = {}
|
|
netgroup = nm.get_node_network_by_netname(node, ngname)
|
|
ep_dict = endpoints[brname]['vendor_specific']
|
|
ep_dict['phy_interfaces'] = \
|
|
cls.get_phy_interfaces(bonds_map, netgroup)
|
|
if netgroup['vlan'] > 1:
|
|
ep_dict['vlans'] = netgroup['vlan']
|
|
|
|
if node.cluster.network_config.segmentation_type == 'vlan':
|
|
private_ep = endpoints[network_mapping['neutron/private']]
|
|
join_range = lambda r: (":".join(map(str, r)) if r else None)
|
|
netgroup = nm.get_node_network_by_netname(node, 'private')
|
|
phys = cls.get_phy_interfaces(bonds_map, netgroup)
|
|
if 'vendor_specific' not in private_ep:
|
|
private_ep['vendor_specific'] = {}
|
|
private_ep['vendor_specific']['phy_interfaces'] = phys
|
|
private_ep['vendor_specific']['vlans'] = \
|
|
join_range(node.cluster.network_config.vlan_range)
|
|
|
|
return network_scheme
|
|
|
|
@classmethod
|
|
def get_phy_interfaces(cls, bonds_map, netgroup):
|
|
if netgroup['dev'] in bonds_map.keys():
|
|
phys = [s['name'] for s in bonds_map[netgroup['dev']].slaves]
|
|
else:
|
|
phys = [netgroup['dev']]
|
|
return phys
|
|
|
|
|
|
class GraphBasedSerializer(object):
|
|
|
|
def __init__(self, graph):
|
|
self.graph = graph
|
|
|
|
def set_deployment_priorities(self, nodes):
|
|
self.graph.add_priorities(nodes)
|
|
|
|
def set_tasks(self, nodes):
|
|
for node in nodes:
|
|
node['tasks'] = self.graph.deploy_task_serialize(node)
|
|
|
|
|
|
class DeploymentMultinodeSerializer(GraphBasedSerializer):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer
|
|
|
|
critical_roles = ['controller', 'ceph-osd', 'primary-mongo']
|
|
|
|
def serialize(self, cluster, nodes, ignore_customized=False):
|
|
"""Method generates facts which
|
|
through an orchestrator passes to puppet
|
|
"""
|
|
serialized_nodes = []
|
|
keyfunc = lambda node: bool(node.replaced_deployment_info)
|
|
for customized, node_group in groupby(nodes, keyfunc):
|
|
if customized and not ignore_customized:
|
|
serialized_nodes.extend(
|
|
self.serialize_customized(cluster, node_group))
|
|
else:
|
|
serialized_nodes.extend(self.serialize_generated(
|
|
cluster, node_group))
|
|
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)
|
|
self.set_critical_nodes(nodes)
|
|
self.set_tasks(nodes)
|
|
return [dict_merge(node, common_attrs) for node in nodes]
|
|
|
|
def serialize_customized(self, cluster, nodes):
|
|
serialized = []
|
|
for node in nodes:
|
|
for role_data in node.replaced_deployment_info:
|
|
serialized.append(role_data)
|
|
return serialized
|
|
|
|
def get_common_attrs(self, cluster):
|
|
"""Cluster attributes."""
|
|
attrs = objects.Attributes.merged_attrs_values(cluster.attributes)
|
|
release = self.current_release(cluster)
|
|
|
|
attrs['deployment_mode'] = cluster.mode
|
|
attrs['deployment_id'] = cluster.id
|
|
attrs['openstack_version_prev'] = getattr(
|
|
self.previous_release(cluster), 'version', None)
|
|
attrs['openstack_version'] = release.version
|
|
attrs['fuel_version'] = cluster.fuel_version
|
|
attrs['nodes'] = self.node_list(get_nodes_not_for_deletion(cluster))
|
|
|
|
for node in attrs['nodes']:
|
|
if node['role'] in 'cinder':
|
|
attrs['use_cinder'] = True
|
|
|
|
self.set_storage_parameters(cluster, attrs)
|
|
self.set_primary_mongo(attrs['nodes'])
|
|
|
|
net_serializer = self.get_net_provider_serializer(cluster)
|
|
net_common_attrs = net_serializer.get_common_attrs(cluster, attrs)
|
|
attrs = dict_merge(attrs, net_common_attrs)
|
|
|
|
return attrs
|
|
|
|
def current_release(self, cluster):
|
|
"""Actual cluster release."""
|
|
return objects.Release.get_by_uid(cluster.pending_release_id) \
|
|
if cluster.status == consts.CLUSTER_STATUSES.update \
|
|
else cluster.release
|
|
|
|
def previous_release(self, cluster):
|
|
"""Returns previous release.
|
|
|
|
:param cluster: a ``Cluster`` instance to retrieve release from
|
|
:returns: a ``Release`` instance of previous release or ``None``
|
|
in case there's no previous release (fresh deployment).
|
|
"""
|
|
if cluster.status == consts.CLUSTER_STATUSES.update:
|
|
return cluster.release
|
|
return None
|
|
|
|
def set_storage_parameters(self, cluster, attrs):
|
|
"""Generate pg_num as the number of OSDs across the cluster
|
|
multiplied by 100, divided by Ceph replication factor, and
|
|
rounded up to the nearest power of 2.
|
|
"""
|
|
osd_num = 0
|
|
nodes = db().query(Node). \
|
|
filter(or_(
|
|
Node.role_list.any(name='ceph-osd'),
|
|
Node.pending_role_list.any(name='ceph-osd'))). \
|
|
filter(Node.cluster == cluster). \
|
|
options(joinedload('attributes'))
|
|
for node in nodes:
|
|
for disk in node.attributes.volumes:
|
|
for part in disk.get('volumes', []):
|
|
if part.get('name') == 'ceph' and part.get('size', 0) > 0:
|
|
osd_num += 1
|
|
if osd_num > 0:
|
|
repl = int(attrs['storage']['osd_pool_size'])
|
|
pg_num = 2 ** int(math.ceil(math.log(osd_num * 100.0 / repl, 2)))
|
|
else:
|
|
pg_num = 128
|
|
attrs['storage']['pg_num'] = pg_num
|
|
|
|
@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': node.fqdn,
|
|
'name': objects.Node.make_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 set_critical_nodes(self, nodes):
|
|
"""Set behavior on nodes deployment error
|
|
during deployment process.
|
|
"""
|
|
for n in nodes:
|
|
n['fail_if_error'] = n['role'] in self.critical_roles
|
|
|
|
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))
|
|
self.set_primary_mongo(serialized_nodes)
|
|
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': node.fqdn,
|
|
'status': node.status,
|
|
'role': role,
|
|
# 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.get_image_cache_max_size(node))
|
|
node_attrs.update(self.generate_test_vm_image_data(node))
|
|
|
|
return node_attrs
|
|
|
|
def get_image_cache_max_size(self, node):
|
|
images_ceph = (node.cluster.attributes['editable']['storage']
|
|
['images_ceph']['value'])
|
|
if images_ceph:
|
|
image_cache_max_size = '0'
|
|
else:
|
|
image_cache_max_size = volume_manager.calc_glance_cache_size(
|
|
node.attributes.volumes)
|
|
return {'glance': {'image_cache_max_size': image_cache_max_size}}
|
|
|
|
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': '',
|
|
}
|
|
# 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)
|
|
# Add default Glance property for Murano.
|
|
glance_properties = [
|
|
"""--property murano_image_info="""
|
|
"""'{"title": "Murano Demo", "type": "cirros.demo"}'"""
|
|
]
|
|
|
|
# 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),
|
|
})
|
|
glance_properties.append('--property vmware_disktype=sparse')
|
|
glance_properties.append('--property vmware_adaptertype=lsilogic')
|
|
glance_properties.append('--property hypervisor_type=vmware')
|
|
|
|
image_data['glance_properties'] = ' '.join(glance_properties)
|
|
|
|
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 set_primary_node(self, nodes, role, primary_node_index):
|
|
"""Set primary node for role if it not set yet.
|
|
primary_node_index defines primary node position in nodes list
|
|
"""
|
|
sorted_nodes = sorted(
|
|
nodes, key=lambda node: int(node['uid']))
|
|
|
|
primary_role = 'primary-{0}'.format(role)
|
|
primary_node = self.filter_by_roles(
|
|
sorted_nodes, [primary_role])
|
|
if primary_node:
|
|
return
|
|
|
|
result_nodes = self.filter_by_roles(
|
|
sorted_nodes, [role])
|
|
if result_nodes:
|
|
result_nodes[primary_node_index]['role'] = primary_role
|
|
|
|
def set_primary_mongo(self, nodes):
|
|
"""Set primary mongo for the last mongo node
|
|
node if it not set yet
|
|
"""
|
|
self.set_primary_node(nodes, 'mongo', 0)
|
|
|
|
def filter_by_roles(self, nodes, roles):
|
|
return filter(
|
|
lambda node: node['role'] in roles, nodes)
|
|
|
|
|
|
class DeploymentHASerializer(DeploymentMultinodeSerializer):
|
|
"""Serializer for ha mode."""
|
|
|
|
critical_roles = ['primary-controller',
|
|
'primary-mongo',
|
|
'primary-swift-proxy',
|
|
'ceph-osd']
|
|
|
|
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)
|
|
|
|
net_manager = objects.Cluster.get_network_manager(cluster)
|
|
|
|
common_attrs.update(net_manager.assign_vip_for_groups(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
|
|
|
|
|
|
class DeploymentMultinodeSerializer51(DeploymentMultinodeSerializer):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer51
|
|
|
|
|
|
class DeploymentHASerializer51(DeploymentHASerializer):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer51
|
|
|
|
|
|
class DeploymentMultinodeSerializer60(DeploymentMultinodeSerializer):
|
|
|
|
nova_network_serializer = NovaNetworkDeploymentSerializer
|
|
neutron_network_serializer = NeutronNetworkDeploymentSerializer60
|
|
|
|
|
|
class DeploymentHASerializer60(DeploymentHASerializer):
|
|
|
|
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(role, 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(role, 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,
|
|
})
|
|
image_vmdk_data['glance_properties'] = ' '.join([
|
|
'--property vmware_disktype=sparse',
|
|
'--property vmware_adaptertype=lsilogic',
|
|
'--property hypervisor_type=vmware'])
|
|
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
|
|
|
|
|
|
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': DeploymentMultinodeSerializer,
|
|
'ha': DeploymentHASerializer,
|
|
},
|
|
'5.1': {
|
|
'multinode': DeploymentMultinodeSerializer51,
|
|
'ha': DeploymentHASerializer51,
|
|
},
|
|
'6.0': {
|
|
'multinode': DeploymentMultinodeSerializer60,
|
|
'ha': DeploymentHASerializer60,
|
|
},
|
|
'6.1': {
|
|
'multinode': DeploymentMultinodeSerializer61,
|
|
'ha': DeploymentHASerializer61,
|
|
},
|
|
}
|
|
|
|
env_version = extract_env_version(cluster.release.version)
|
|
env_mode = 'ha' if cluster.is_ha_mode else 'multinode'
|
|
for version, serializers in six.iteritems(serializers_map):
|
|
if env_version.startswith(version):
|
|
return serializers[env_mode]
|
|
|
|
raise errors.UnsupportedSerializer()
|
|
|
|
|
|
def serialize(orchestrator_graph, cluster, nodes, ignore_customized=False):
|
|
"""Serialization depends on deployment mode
|
|
"""
|
|
objects.Cluster.set_primary_roles(cluster, nodes)
|
|
objects.NodeCollection.prepare_for_deployment(cluster.nodes)
|
|
serializer = get_serializer_for_cluster(cluster)(orchestrator_graph)
|
|
|
|
return serializer.serialize(
|
|
cluster, nodes, ignore_customized=ignore_customized)
|