552 lines
18 KiB
Python
552 lines
18 KiB
Python
# Copyright 2015-2016 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.
|
|
|
|
from __future__ import division
|
|
|
|
import os
|
|
|
|
from netaddr import IPNetwork
|
|
import yaml
|
|
|
|
from devops.error import DevopsError
|
|
|
|
|
|
def yaml_template_load(config_file):
|
|
def yaml_include(loader, node):
|
|
file_name = os.path.join(os.path.dirname(loader.name), node.value)
|
|
if not os.path.isfile(file_name):
|
|
raise ValueError(
|
|
"Cannot load the environment template {0} : include file {1} "
|
|
"doesn't exist.".format(config_file, file_name))
|
|
with open(file_name) as inputfile:
|
|
return yaml.load(inputfile)
|
|
|
|
def yaml_get_env_variable(loader, node):
|
|
if not node.value.strip():
|
|
raise ValueError("Environment variable is required after {tag} in "
|
|
"{filename}".format(tag=node.tag,
|
|
filename=loader.name))
|
|
node_value = node.value.split(',', 1)
|
|
# Get the name of environment variable
|
|
env_variable = node_value[0].strip()
|
|
|
|
# Get the default value for environment variable if it exists in config
|
|
if len(node_value) > 1:
|
|
default_val = node_value[1].strip()
|
|
else:
|
|
default_val = None
|
|
|
|
value = os.environ.get(env_variable, default_val)
|
|
if value is None:
|
|
raise ValueError("Environment variable {var} is not set from shell"
|
|
" environment! No default value provided in file "
|
|
"{filename}".format(var=env_variable,
|
|
filename=loader.name))
|
|
|
|
return yaml.load(value)
|
|
|
|
if not os.path.isfile(config_file):
|
|
raise ValueError(
|
|
"Cannot load the environment template {0} : file "
|
|
"doesn't exist.".format(config_file))
|
|
|
|
yaml.add_constructor("!include", yaml_include)
|
|
yaml.add_constructor("!os_env", yaml_get_env_variable)
|
|
|
|
return yaml.load(open(config_file))
|
|
|
|
|
|
def get_devops_config(filename):
|
|
"""Read the YAML file and create a 'full_config' object.
|
|
|
|
:param filename: path to a file that contains a YAML template
|
|
:rtype: dict
|
|
"""
|
|
import devops
|
|
config_file = os.path.join(os.path.dirname(devops.__file__),
|
|
'templates', filename)
|
|
return yaml_template_load(config_file)
|
|
|
|
|
|
def create_admin_config(admin_vcpu, admin_memory, admin_sysvolume_capacity,
|
|
admin_iso_path, boot_from, interfaceorder,
|
|
numa_nodes,
|
|
networks_bonding=None,
|
|
networks_bondinginterfaces=None):
|
|
|
|
if networks_bonding:
|
|
# DEPRECATED. Use YAML template for test cases with bonding.
|
|
# For fuel-qa bonding cases, 'network_config' is hardcoded in the tests
|
|
# (see self.INTERFACES in fuel-qa 'test_bonding_base.py')
|
|
# Translate a dict of lists {net_name: [eth0, eth1],} into a new dict:
|
|
# {eth0: net_name, eth1: net_name,}
|
|
ifaces = {
|
|
label: iname
|
|
for iname in networks_bondinginterfaces
|
|
for label in networks_bondinginterfaces[iname]
|
|
}
|
|
admin_interfaces = [
|
|
{
|
|
'label': label,
|
|
'l2_network_device': ifaces[label],
|
|
'interface_model': 'e1000',
|
|
} for label in sorted(ifaces.keys())
|
|
]
|
|
# Please use YAML templates instead of old-style tests to make new
|
|
# tests for bonds.
|
|
else:
|
|
admin_interfaces = [
|
|
{
|
|
'label': 'iface' + str(n),
|
|
'l2_network_device': iname,
|
|
'interface_model': 'e1000',
|
|
} for n, iname in enumerate(interfaceorder)
|
|
]
|
|
|
|
# network_config is for storing OpenStack networks mapping on interfaces
|
|
# based on 'interfaceorder' object.
|
|
# Resulting object will be (by default):
|
|
# network_config:
|
|
# iface0:
|
|
# networks:
|
|
# - fuelweb_admin
|
|
# iface1:
|
|
# networks:
|
|
# - public
|
|
# iface2:
|
|
# networks:
|
|
# - storage
|
|
# iface3:
|
|
# networks:
|
|
# - management
|
|
# iface4:
|
|
# networks:
|
|
# - private
|
|
network_config = {
|
|
iface['label']: {
|
|
'networks': [
|
|
iface['l2_network_device']
|
|
if iface['l2_network_device'] != 'admin'
|
|
else 'fuelweb_admin',
|
|
]
|
|
} for iface in admin_interfaces
|
|
}
|
|
|
|
if boot_from == 'usb':
|
|
boot_device_order = ['hd']
|
|
iso_device = 'disk'
|
|
iso_bus = 'usb'
|
|
bootmenu_timeout = 3000
|
|
else: # boot_from == 'cdrom':
|
|
boot_device_order = ['hd', 'cdrom']
|
|
iso_device = 'cdrom'
|
|
iso_bus = 'ide'
|
|
bootmenu_timeout = 0
|
|
|
|
numa = _calculate_numa(
|
|
numa_nodes=numa_nodes,
|
|
vcpu=admin_vcpu,
|
|
memory=admin_memory,
|
|
name='admin')
|
|
|
|
admin_config = {
|
|
'name': 'admin', # Custom name of VM for Fuel admin node
|
|
'role': 'fuel_master', # Fixed role for (Fuel admin) node properties
|
|
'params': {
|
|
'vcpu': admin_vcpu,
|
|
'memory': admin_memory,
|
|
'boot': boot_device_order,
|
|
'bootmenu_timeout': bootmenu_timeout,
|
|
'numa': numa,
|
|
'volumes': [
|
|
{
|
|
'name': 'system',
|
|
'capacity': admin_sysvolume_capacity,
|
|
'format': 'qcow2',
|
|
},
|
|
{
|
|
'name': 'iso',
|
|
'source_image': admin_iso_path,
|
|
'format': 'raw',
|
|
'device': iso_device,
|
|
'bus': iso_bus,
|
|
},
|
|
],
|
|
'interfaces': admin_interfaces,
|
|
'network_config': network_config,
|
|
},
|
|
}
|
|
return admin_config
|
|
|
|
|
|
def create_slave_config(slave_name, slave_role, slave_vcpu, slave_memory,
|
|
slave_volume_capacity,
|
|
interfaceorder,
|
|
numa_nodes,
|
|
second_volume_capacity=None,
|
|
third_volume_capacity=None,
|
|
use_all_disks=False,
|
|
multipath_count=0,
|
|
networks_multiplenetworks=None,
|
|
networks_nodegroups=None,
|
|
networks_bonding=None,
|
|
networks_bondinginterfaces=None):
|
|
|
|
if networks_multiplenetworks:
|
|
nodegroups_idx = 1 - int(slave_name[-2:]) % 2
|
|
slave_interfaces = [
|
|
{
|
|
'label': 'iface' + str(n),
|
|
'l2_network_device': iname,
|
|
'interface_model': 'e1000',
|
|
} for n, iname in enumerate(
|
|
networks_nodegroups[nodegroups_idx]['pools'])
|
|
]
|
|
elif networks_bonding:
|
|
# DEPRECATED. Use YAML template for test cases with bonding.
|
|
# For fuel-qa bonding cases, 'network_config' is hardcoded in the tests
|
|
# (see self.INTERFACES in fuel-qa 'test_bonding_base.py')
|
|
# Translate a dict of lists {net_name: [eth0, eth1],} into a new dict:
|
|
# {eth0: net_name, eth1: net_name,}
|
|
ifaces = {
|
|
label: iname
|
|
for iname in networks_bondinginterfaces
|
|
for label in networks_bondinginterfaces[iname]
|
|
}
|
|
slave_interfaces = [
|
|
{
|
|
'label': label,
|
|
'l2_network_device': ifaces[label],
|
|
'interface_model': 'e1000',
|
|
} for label in sorted(ifaces.keys())
|
|
]
|
|
else:
|
|
slave_interfaces = [
|
|
{
|
|
'label': 'iface' + str(n),
|
|
'l2_network_device': iname,
|
|
'interface_model': 'e1000',
|
|
} for n, iname in enumerate(interfaceorder)
|
|
]
|
|
|
|
# network_config is for storing OpenStack networks mapping on interfaces
|
|
# based on 'interfaceorder' object.
|
|
# Resulting object will be (by default):
|
|
# network_config:
|
|
# iface0:
|
|
# networks:
|
|
# - fuelweb_admin
|
|
# iface1:
|
|
# networks:
|
|
# - public
|
|
# iface2:
|
|
# networks:
|
|
# - storage
|
|
# iface3:
|
|
# networks:
|
|
# - management
|
|
# iface4:
|
|
# networks:
|
|
# - private
|
|
network_config = {
|
|
iface['label']: {
|
|
'networks': [
|
|
iface['l2_network_device']
|
|
if iface['l2_network_device'] != 'admin'
|
|
else 'fuelweb_admin',
|
|
]
|
|
} for iface in slave_interfaces
|
|
}
|
|
|
|
volumes = [
|
|
{
|
|
'name': 'system',
|
|
'capacity': slave_volume_capacity,
|
|
'multipath_count': multipath_count,
|
|
}
|
|
]
|
|
if use_all_disks:
|
|
volumes.extend([
|
|
{
|
|
'name': 'cinder',
|
|
'capacity': second_volume_capacity or slave_volume_capacity,
|
|
'multipath_count': multipath_count,
|
|
},
|
|
{
|
|
'name': 'swift',
|
|
'capacity': third_volume_capacity or slave_volume_capacity,
|
|
}
|
|
])
|
|
else:
|
|
if second_volume_capacity:
|
|
volumes.append(
|
|
{
|
|
'name': 'cinder',
|
|
'capacity': second_volume_capacity,
|
|
'multipath_count': multipath_count,
|
|
}
|
|
)
|
|
if third_volume_capacity:
|
|
volumes.append(
|
|
{
|
|
'name': 'swift',
|
|
'capacity': third_volume_capacity,
|
|
}
|
|
)
|
|
|
|
numa = _calculate_numa(
|
|
numa_nodes=numa_nodes,
|
|
vcpu=slave_vcpu,
|
|
memory=slave_memory,
|
|
name=slave_name)
|
|
|
|
slave_config = {
|
|
'name': slave_name,
|
|
'role': slave_role,
|
|
'params': {
|
|
'vcpu': slave_vcpu,
|
|
'memory': slave_memory,
|
|
'boot': ['network', 'hd'],
|
|
'numa': numa,
|
|
'volumes': volumes,
|
|
'interfaces': slave_interfaces,
|
|
'network_config': network_config,
|
|
},
|
|
}
|
|
return slave_config
|
|
|
|
|
|
def create_netpools(interfaceorder):
|
|
netpool = {}
|
|
for iname in interfaceorder:
|
|
if iname == 'admin':
|
|
netname = 'fuelweb_admin'
|
|
else:
|
|
netname = iname
|
|
netpool[netname] = iname
|
|
return netpool
|
|
|
|
|
|
def create_address_pools(interfaceorder, networks_pools):
|
|
address_pools = {
|
|
iname: {
|
|
'net': ':'.join(networks_pools[iname]),
|
|
'params': {
|
|
'ip_reserved': {
|
|
# Gateway will be used for configure OpenStack networks
|
|
'gateway': 1,
|
|
# l2_network_device will be used for configure local bridge
|
|
'l2_network_device': 1,
|
|
},
|
|
'ip_ranges': {
|
|
'default': [2, -2],
|
|
},
|
|
},
|
|
} for iname in interfaceorder
|
|
}
|
|
|
|
if 'public' in interfaceorder:
|
|
# Put floating IP range for public network
|
|
default_pool_name = 'default'
|
|
floating_pool_name = 'floating'
|
|
|
|
# Take a first subnet with necessary size and calculate the size
|
|
net = IPNetwork(networks_pools['public'][0])
|
|
new_prefix = int(networks_pools['public'][1])
|
|
subnet = next(net.subnet(prefixlen=new_prefix))
|
|
network_size = subnet.size
|
|
|
|
address_pools['public']['params']['ip_ranges'][default_pool_name] = [
|
|
2, network_size // 2 - 1]
|
|
address_pools['public']['params']['ip_ranges'][floating_pool_name] = [
|
|
network_size // 2, -2]
|
|
|
|
return address_pools
|
|
|
|
|
|
def create_l2_network_devices(interfaceorder,
|
|
networks_dhcp,
|
|
networks_forwarding):
|
|
l2_network_devices = {
|
|
iname: {
|
|
'address_pool': iname,
|
|
'dhcp': networks_dhcp[iname],
|
|
'forward': {
|
|
'mode': networks_forwarding[iname],
|
|
}
|
|
} for iname in interfaceorder
|
|
}
|
|
return l2_network_devices
|
|
|
|
|
|
def _calculate_numa(numa_nodes, vcpu, memory, name):
|
|
numa = []
|
|
if numa_nodes:
|
|
cpus_per_numa = vcpu // numa_nodes
|
|
if cpus_per_numa * numa_nodes != vcpu:
|
|
raise DevopsError(
|
|
"NUMA_NODES={0} is not a multiple of the number of CPU={1}"
|
|
" for node '{2}'".format(numa_nodes, vcpu, name))
|
|
memory_per_numa = memory // numa_nodes
|
|
if memory_per_numa * numa_nodes != memory:
|
|
raise DevopsError(
|
|
"NUMA_NODES={0} is not a multiple of the amount of "
|
|
"MEMORY={1} for node '{2}'".format(numa_nodes,
|
|
memory,
|
|
name))
|
|
for x in range(numa_nodes):
|
|
# List of cpu IDs for the numa node
|
|
cpus = range(x * cpus_per_numa, (x + 1) * cpus_per_numa)
|
|
cell = {
|
|
'cpus': ','.join(map(str, cpus)),
|
|
'memory': memory_per_numa,
|
|
}
|
|
numa.append(cell)
|
|
|
|
return numa
|
|
|
|
|
|
def create_devops_config(boot_from,
|
|
env_name,
|
|
admin_vcpu,
|
|
admin_memory,
|
|
admin_sysvolume_capacity,
|
|
admin_iso_path,
|
|
nodes_count,
|
|
numa_nodes,
|
|
slave_vcpu,
|
|
slave_memory,
|
|
slave_volume_capacity,
|
|
second_volume_capacity,
|
|
third_volume_capacity,
|
|
use_all_disks,
|
|
multipath_count,
|
|
ironic_nodes_count,
|
|
networks_bonding,
|
|
networks_bondinginterfaces,
|
|
networks_multiplenetworks,
|
|
networks_nodegroups,
|
|
networks_interfaceorder,
|
|
networks_pools,
|
|
networks_forwarding,
|
|
networks_dhcp,
|
|
driver_enable_acpi):
|
|
"""Creates devops config object
|
|
|
|
This method is used for backward compatibility with old-style
|
|
environment creation, where most of environment parameters were
|
|
passed with shell environment variables.
|
|
|
|
See models/environment.py and settings.py for details about
|
|
input parameters structure.
|
|
"""
|
|
|
|
# If bonding enabled, then a different interfaces order is provided.
|
|
if networks_bonding:
|
|
interfaceorder = networks_bondinginterfaces.keys()
|
|
else:
|
|
interfaceorder = networks_interfaceorder
|
|
|
|
# Create address pools object
|
|
address_pools = create_address_pools(interfaceorder, networks_pools)
|
|
netpools = create_netpools(interfaceorder)
|
|
|
|
# Create network devices object
|
|
l2_network_devices = create_l2_network_devices(interfaceorder,
|
|
networks_dhcp,
|
|
networks_forwarding)
|
|
# Create admin and slave nodes
|
|
config_nodes = []
|
|
|
|
admin_config = create_admin_config(
|
|
admin_vcpu=admin_vcpu,
|
|
admin_memory=admin_memory,
|
|
admin_sysvolume_capacity=admin_sysvolume_capacity,
|
|
admin_iso_path=admin_iso_path,
|
|
boot_from=boot_from,
|
|
numa_nodes=numa_nodes,
|
|
interfaceorder=interfaceorder,
|
|
networks_bonding=networks_bonding,
|
|
networks_bondinginterfaces=networks_bondinginterfaces)
|
|
|
|
config_nodes.append(admin_config)
|
|
|
|
for slave_n in range(1, nodes_count):
|
|
slave_name = 'slave-{0:0>2}'.format(slave_n)
|
|
|
|
slave_config = create_slave_config(
|
|
slave_name=slave_name,
|
|
slave_role='fuel_slave',
|
|
slave_vcpu=slave_vcpu,
|
|
slave_memory=slave_memory,
|
|
slave_volume_capacity=slave_volume_capacity,
|
|
second_volume_capacity=second_volume_capacity,
|
|
third_volume_capacity=third_volume_capacity,
|
|
interfaceorder=interfaceorder,
|
|
numa_nodes=numa_nodes,
|
|
use_all_disks=use_all_disks,
|
|
multipath_count=multipath_count,
|
|
networks_multiplenetworks=networks_multiplenetworks,
|
|
networks_nodegroups=networks_nodegroups,
|
|
networks_bonding=networks_bonding,
|
|
networks_bondinginterfaces=networks_bondinginterfaces)
|
|
|
|
config_nodes.append(slave_config)
|
|
|
|
for ironic_n in range(1, ironic_nodes_count + 1):
|
|
ironic_name = 'ironic-slave-{0:0>2}'.format(ironic_n)
|
|
|
|
ironic_config = create_slave_config(
|
|
slave_name=ironic_name,
|
|
slave_role='ironic_slave',
|
|
slave_vcpu=slave_vcpu,
|
|
slave_memory=slave_memory,
|
|
slave_volume_capacity=slave_volume_capacity,
|
|
interfaceorder=['ironic'],
|
|
numa_nodes=numa_nodes,
|
|
use_all_disks=False)
|
|
|
|
config_nodes.append(ironic_config)
|
|
|
|
config = {
|
|
'template': {
|
|
'devops_settings': {
|
|
'env_name': env_name,
|
|
'address_pools': address_pools,
|
|
'groups': [
|
|
{
|
|
'driver': {
|
|
'name':
|
|
'devops.driver.libvirt',
|
|
'params': {
|
|
'connection_string': 'qemu:///system',
|
|
'storage_pool_name': 'default',
|
|
'stp': True,
|
|
'hpet': False,
|
|
'use_host_cpu': True,
|
|
'enable_acpi': driver_enable_acpi,
|
|
},
|
|
},
|
|
'name': 'default',
|
|
'network_pools': netpools,
|
|
'l2_network_devices': l2_network_devices,
|
|
'nodes': config_nodes,
|
|
},
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
return config
|