434 lines
16 KiB
Python
434 lines
16 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2015 Mirantis, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""Base classes of deployment serializers for orchestrator"""
|
|
|
|
from copy import deepcopy
|
|
from netaddr import IPNetwork
|
|
|
|
from nailgun.db import db
|
|
from nailgun.db.sqlalchemy.models import NetworkGroup
|
|
from nailgun.errors import errors
|
|
from nailgun import objects
|
|
from nailgun.settings import settings
|
|
|
|
|
|
class MellanoxMixin(object):
|
|
|
|
@classmethod
|
|
def inject_mellanox_settings_for_deployment(
|
|
cls, node_attrs, cluster, networks):
|
|
"""Mellanox settings for deployment
|
|
|
|
Serialize mellanox node attrs then it will be merged with common
|
|
attributes, if mellanox plugin or iSER storage enabled.
|
|
|
|
:param node_attrs: attributes for specific node
|
|
:type node_attrs: dict
|
|
:param cluster: A cluster instance
|
|
:type cluster: Cluster model
|
|
:param networks: networks related for specific node
|
|
:type networks: list
|
|
:returns: None
|
|
"""
|
|
|
|
cluster_attrs = objects.Cluster.get_editable_attributes(cluster)
|
|
neutron_mellanox_data = cluster_attrs.get('neutron_mellanox', {})
|
|
# We need to support mellanox for releases < 8.0. So if mellanox
|
|
# attributes exist (primary in old releases) then merge them with
|
|
# common node attributes.
|
|
if neutron_mellanox_data:
|
|
storage_data = cluster_attrs.get('storage', {})
|
|
nm = objects.Cluster.get_network_manager(cluster)
|
|
node_attrs['neutron_mellanox'] = {}
|
|
|
|
# Find Physical port for VFs generation
|
|
if 'plugin' in neutron_mellanox_data and \
|
|
neutron_mellanox_data['plugin']['value'] == 'ethernet':
|
|
# Set config for ml2 mellanox mechanism driver
|
|
node_attrs['neutron_mellanox'].update({
|
|
'physical_port': nm.get_network_by_netname(
|
|
'private', networks)['dev'],
|
|
'ml2_eswitch': {
|
|
'vnic_type': 'hostdev',
|
|
'apply_profile_patch': True}
|
|
})
|
|
|
|
# Fix network scheme to have physical port for RDMA if iSER enabled
|
|
if 'iser' in storage_data and storage_data['iser']['value']:
|
|
iser_new_name = 'eth_iser0'
|
|
node_attrs['neutron_mellanox'].update({
|
|
'storage_parent': nm.get_network_by_netname(
|
|
'storage', networks)['dev'],
|
|
'iser_interface_name': iser_new_name
|
|
})
|
|
|
|
storage_vlan = \
|
|
nm.get_network_by_netname('storage', networks).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
|
|
node_attrs['network_scheme']['interfaces'][
|
|
iser_new_name] = {'L2': {'vlan_splinters': 'off'}}
|
|
node_attrs['network_scheme']['endpoints'][
|
|
iser_new_name] = node_attrs['network_scheme'][
|
|
'endpoints'].pop('br-storage', {})
|
|
|
|
@classmethod
|
|
def inject_mellanox_settings_for_provisioning(
|
|
cls, cluster_attrs, serialized_node):
|
|
"""Mellanox settings for provisioning
|
|
|
|
Serialize mellanox node attrs then it will be merged with common
|
|
node attributes
|
|
|
|
:param cluster_attrs: cluster attributes
|
|
:type cluster_attrs: dict
|
|
:param serialized_node: node attributes data for provisioning
|
|
:type serialized_node: dict
|
|
:returns: None
|
|
"""
|
|
mellanox_data = cluster_attrs.get('neutron_mellanox')
|
|
# We need to support mellanox for releases < 8.0. So if mellanox
|
|
# attributes exist (primary in old releases) then merge them with
|
|
# common node attributes.
|
|
if mellanox_data:
|
|
serialized_node['ks_meta'].update({
|
|
'mlnx_vf_num': mellanox_data.get('vf_num'),
|
|
'mlnx_plugin_mode': mellanox_data.get('plugin'),
|
|
'mlnx_iser_enabled': cluster_attrs.get(
|
|
'storage', {}).get('iser')
|
|
})
|
|
# Add relevant kernel parameter when using Mellanox SR-IOV
|
|
# and/or iSER (which works on top of a probed virtual function)
|
|
# unless it was explicitly added by the user
|
|
pm_data = serialized_node['ks_meta']['pm_data']
|
|
if ((mellanox_data['plugin'] == 'ethernet' or
|
|
cluster_attrs['storage']['iser'] is True) and
|
|
'intel_iommu=' not in pm_data['kernel_params']):
|
|
pm_data['kernel_params'] += ' intel_iommu=on'
|
|
|
|
|
|
class MuranoMetadataSerializerMixin(object):
|
|
|
|
def generate_test_vm_image_data(self, node):
|
|
return self.inject_murano_settings(super(
|
|
MuranoMetadataSerializerMixin,
|
|
self).generate_test_vm_image_data(node))
|
|
|
|
def inject_murano_settings(self, image_data):
|
|
"""Adds murano metadata to the test image"""
|
|
test_vm_image = image_data['test_vm_image']
|
|
existing_properties = test_vm_image['glance_properties']
|
|
murano_data = ' '.join(["""--property murano_image_info='{"title":"""
|
|
""" "Murano Demo", "type": "cirros.demo"}'"""])
|
|
test_vm_image['glance_properties'] = existing_properties + murano_data
|
|
return {'test_vm_image': test_vm_image}
|
|
|
|
|
|
class VmwareDeploymentSerializerMixin(object):
|
|
|
|
def generate_vmware_data(self, node):
|
|
"""Extend serialize data with vmware attributes"""
|
|
vmware_data = {}
|
|
allowed_roles = [
|
|
'controller',
|
|
'primary-controller',
|
|
'compute-vmware',
|
|
'cinder-vmware'
|
|
]
|
|
|
|
all_roles = objects.Node.all_roles(node)
|
|
use_vcenter = node.cluster.attributes.editable.get('common', {}) \
|
|
.get('use_vcenter', {}).get('value')
|
|
|
|
if (use_vcenter and any(role in allowed_roles for role in all_roles)):
|
|
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:
|
|
|
|
vc_user = self.escape_dollar(zone.get('vcenter_username', ''))
|
|
vc_password = self.escape_dollar(zone.get('vcenter_password',
|
|
''))
|
|
|
|
for compute in zone.get('nova_computes', {}):
|
|
datastore_regex = \
|
|
self.escape_dollar(compute.get('datastore_regex', ''))
|
|
|
|
compute_item = {
|
|
'availability_zone_name': zone.get('az_name', ''),
|
|
'vc_host': zone.get('vcenter_host', ''),
|
|
'vc_user': vc_user,
|
|
'vc_password': vc_password,
|
|
'service_name': compute.get('service_name', ''),
|
|
'vc_cluster': compute.get('vsphere_cluster', ''),
|
|
'datastore_regex': datastore_regex,
|
|
'target_node': compute.get('target_node', {}).get(
|
|
'current', {}).get('id', 'controllers')
|
|
}
|
|
|
|
compute_instances.append(compute_item)
|
|
|
|
cinder_item = {
|
|
'availability_zone_name': zone.get('az_name', ''),
|
|
'vc_host': zone.get('vcenter_host', ''),
|
|
'vc_user': vc_user,
|
|
'vc_password': vc_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:
|
|
glance_username = \
|
|
self.escape_dollar(glance_instance
|
|
.get('vcenter_username', ''))
|
|
glance_password = \
|
|
self.escape_dollar(glance_instance
|
|
.get('vcenter_password', ''))
|
|
|
|
vmware_data['glance'] = {
|
|
'vc_host': glance_instance.get('vcenter_host', ''),
|
|
'vc_user': glance_username,
|
|
'vc_password': glance_password,
|
|
'vc_datacenter': glance_instance.get('datacenter', ''),
|
|
'vc_datastore': glance_instance.get('datastore', '')
|
|
}
|
|
|
|
return vmware_data
|
|
|
|
@staticmethod
|
|
def escape_dollar(data):
|
|
"""Escape dollar symbol
|
|
|
|
In order to disable variable interpolation in
|
|
values that we write to configuration files during
|
|
deployment we must replace all $ (dollar sign) occurrences.
|
|
"""
|
|
return data.replace('$', '$$')
|
|
|
|
|
|
class NetworkDeploymentSerializer(object):
|
|
|
|
@classmethod
|
|
def update_nodes_net_info(cls, cluster, nodes):
|
|
"""Adds information about networks to each node."""
|
|
for node in objects.Cluster.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_with_ip(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(objects.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.Cluster.get_network_manager(node.cluster)
|
|
admin_ip = network_manager.get_admin_ip_for_node(node.id)
|
|
admin_ip = IPNetwork(admin_ip)
|
|
|
|
# Assign prefix from admin network
|
|
admin_net = IPNetwork(
|
|
objects.NetworkGroup.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:
|
|
port['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 iface.interface_properties.get('mtu'):
|
|
bond['mtu'] = iface.interface_properties['mtu']
|
|
if parameters:
|
|
bond.update(parameters)
|
|
return bond
|
|
|
|
@classmethod
|
|
def add_patch(cls, bridges, provider=None, mtu=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
|
|
if mtu:
|
|
patch['mtu'] = mtu
|
|
return patch
|