config/sysinv/sysinv/sysinv/sysinv/helm/nova.py

330 lines
13 KiB
Python

#
# Copyright (c) 2018-2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import copy
import os
from sysinv.common import constants
from sysinv.common import exception
from sysinv.common import utils
from sysinv.openstack.common import log as logging
from sysinv.helm import common
from sysinv.helm import openstack
LOG = logging.getLogger(__name__)
class NovaHelm(openstack.OpenstackBaseHelm):
"""Class to encapsulate helm operations for the nova chart"""
CHART = constants.HELM_CHART_NOVA
SERVICE_NAME = 'nova'
AUTH_USERS = ['nova', 'placement']
SERVICE_USERS = ['neutron', 'ironic']
def get_overrides(self, namespace=None):
ssh_privatekey, ssh_publickey = \
self._get_or_generate_ssh_keys(self.SERVICE_NAME, common.HELM_NS_OPENSTACK)
overrides = {
common.HELM_NS_OPENSTACK: {
'pod': {
'replicas': {
'api_metadata': self._num_controllers(),
'placement': self._num_controllers(),
'osapi': self._num_controllers(),
'conductor': self._num_controllers(),
'consoleauth': self._num_controllers(),
'scheduler': self._num_controllers(),
# set replicas for novncproxy once it's validated.
}
},
'conf': {
'nova': {
'libvirt': {
'virt_type': self._get_virt_type(),
},
'vnc': {
'novncproxy_base_url': self._get_novncproxy_base_url(),
}
},
'overrides': {
'nova_compute': {
'hosts': self._get_per_host_overrides()
}
},
'ssh_private': ssh_privatekey,
'ssh_public': ssh_publickey,
},
'endpoints': self._get_endpoints_overrides(),
'images': self._get_images_overrides(),
'network': {
'sshd': {
'from_subnet': self._get_ssh_subnet(),
}
}
}
}
if namespace in self.SUPPORTED_NAMESPACES:
return overrides[namespace]
elif namespace:
raise exception.InvalidHelmNamespace(chart=self.CHART,
namespace=namespace)
else:
return overrides
def _get_images_overrides(self):
heat_image = self._operator.chart_operators[
constants.HELM_CHART_HEAT].docker_image
return {
'tags': {
'bootstrap': heat_image,
'db_drop': heat_image,
'db_init': heat_image,
'ks_user': heat_image,
'ks_service': heat_image,
'ks_endpoints': heat_image,
'nova_api': self.docker_image,
'nova_cell_setup': self.docker_image,
'nova_cell_setup_init': heat_image,
'nova_compute': self.docker_image,
'nova_compute_ironic': self.docker_image,
'nova_compute_ssh': self.docker_image,
'nova_conductor': self.docker_image,
'nova_consoleauth': self.docker_image,
'nova_db_sync': self.docker_image,
'nova_novncproxy': self.docker_image,
'nova_placement': self.docker_image,
'nova_scheduler': self.docker_image,
'nova_spiceproxy': self.docker_image,
'nova_spiceproxy_assets': self.docker_image
}
}
def _get_endpoints_overrides(self):
overrides = {
'identity': {
'name': 'keystone',
'auth': self._get_endpoints_identity_overrides(
self.SERVICE_NAME, self.AUTH_USERS),
},
'oslo_cache': {
'auth': {
'memcached_secret_key':
self._get_common_password('auth_memcache_key')
}
},
'oslo_messaging': {
'auth': self._get_endpoints_oslo_messaging_overrides(
self.SERVICE_NAME, [self.SERVICE_NAME])
},
}
db_passwords = {'auth': self._get_endpoints_oslo_db_overrides(
self.SERVICE_NAME, [self.SERVICE_NAME])}
overrides.update({
'oslo_db': db_passwords,
'oslo_db_api': copy.deepcopy(db_passwords),
'oslo_db_cell0': copy.deepcopy(db_passwords),
})
# Service user passwords already exist in other chart overrides
for user in self.SERVICE_USERS:
overrides['identity']['auth'].update({
user: {
'region_name': self._region_name(),
'password': self._get_or_generate_password(
user, common.HELM_NS_OPENSTACK, user)
}
})
return overrides
def _get_novncproxy_base_url(self):
oam_addr = self._get_oam_address(),
url = "http://%s:6080/vnc_auto.html" % oam_addr
return url
def _get_virt_type(self):
if utils.is_virtual():
return 'qemu'
else:
return 'kvm'
def _get_host_cpu_list(self, host, function=None, threads=False):
"""
Retreive a list of CPUs for the host, filtered by function and thread
siblings (if supplied)
"""
cpus = []
for c in self.dbapi.icpu_get_by_ihost(host.id):
if c.thread != 0 and not threads:
continue
if c.allocated_function == function or not function:
cpus.append(c)
return cpus
def _update_host_cpu_maps(self, host, default_config):
host_cpus = self._get_host_cpu_list(host, threads=True)
if host_cpus:
vm_cpus = self._get_host_cpu_list(
host, function=constants.APPLICATION_FUNCTION, threads=True)
vm_cpu_list = [c.cpu for c in vm_cpus]
vm_cpu_fmt = "\"%s\"" % utils.format_range_set(vm_cpu_list)
default_config.update({'vcpu_pin_set': vm_cpu_fmt})
shared_cpus = self._get_host_cpu_list(
host, function=constants.SHARED_FUNCTION, threads=True)
shared_cpu_map = {c.numa_node: c.cpu for c in shared_cpus}
shared_cpu_fmt = "\"%s\"" % ','.join(
"%r:%r" % (node, cpu) for node, cpu in shared_cpu_map.items())
default_config.update({'shared_pcpu_map': shared_cpu_fmt})
def _update_host_storage(self, host, default_config, libvirt_config):
remote_storage = False
labels = self.dbapi.label_get_all(host.id)
for label in labels:
if (label.label_key == common.LABEL_REMOTE_STORAGE and
label.label_value == common.LABEL_VALUE_ENABLED):
remote_storage = True
break
rbd_pool = constants.CEPH_POOL_EPHEMERAL_NAME
rbd_ceph_conf = os.path.join(constants.CEPH_CONF_PATH,
constants.SB_TYPE_CEPH_CONF_FILENAME)
# If NOVA is a service on a ceph-external backend, use the ephemeral_pool
# and ceph_conf file that are stored in that DB entry.
# If NOVA is not on any ceph-external backend, it must be on the internal
# ceph backend with default "ephemeral" pool and default "/etc/ceph/ceph.conf"
# config file
sb_list = self.dbapi.storage_backend_get_list_by_type(
backend_type=constants.SB_TYPE_CEPH_EXTERNAL)
if sb_list:
for sb in sb_list:
if constants.SB_SVC_NOVA in sb.services:
ceph_ext_obj = self.dbapi.storage_ceph_external_get(sb.id)
rbd_pool = sb.capabilities.get('ephemeral_pool')
rbd_ceph_conf = \
constants.CEPH_CONF_PATH + os.path.basename(ceph_ext_obj.ceph_conf)
if remote_storage:
libvirt_config.update({'images_type': 'rbd',
'images_rbd_pool': rbd_pool,
'images_rbd_ceph_conf': rbd_ceph_conf})
else:
libvirt_config.update({'images_type': 'default'})
def _update_host_addresses(self, host, default_config, vnc_config, libvirt_config):
interfaces = self.dbapi.iinterface_get_by_ihost(host.id)
addresses = self.dbapi.addresses_get_by_host(host.id)
cluster_host_network = self.dbapi.network_get_by_type(
constants.NETWORK_TYPE_CLUSTER_HOST)
cluster_host_iface = None
for iface in interfaces:
interface_network = {'interface_id': iface.id,
'network_id': cluster_host_network.id}
try:
self.dbapi.interface_network_query(interface_network)
cluster_host_iface = iface
except exception.InterfaceNetworkNotFoundByHostInterfaceNetwork:
pass
if cluster_host_iface is None:
return
cluster_host_ip = None
ip_family = None
for addr in addresses:
if addr.interface_uuid == cluster_host_iface.uuid:
cluster_host_ip = addr.address
ip_family = addr.family
default_config.update({'my_ip': cluster_host_ip})
if ip_family == 4:
vnc_config.update({'vncserver_listen': '0.0.0.0'})
elif ip_family == 6:
vnc_config.update({'vncserver_listen': '::0'})
libvirt_config.update({'live_migration_inbound_addr': cluster_host_ip})
vnc_config.update({'vncserver_proxyclient_address': cluster_host_ip})
def _get_ssh_subnet(self):
cluster_host_network = self.dbapi.network_get_by_type(
constants.NETWORK_TYPE_CLUSTER_HOST)
address_pool = self.dbapi.address_pool_get(cluster_host_network.pool_uuid)
return '%s/%s' % (str(address_pool.network), str(address_pool.prefix))
def _update_host_memory(self, host, default_config):
vswitch_2M_pages = []
vswitch_1G_pages = []
vm_4K_pages = []
# The retrieved information is not necessarily ordered by numa node.
host_memory = self.dbapi.imemory_get_by_ihost(host.id)
# This makes it ordered by numa node.
memory_numa_list = utils.get_numa_index_list(host_memory)
# Process them in order of numa node.
for node, memory_list in memory_numa_list.items():
memory = memory_list[0]
# first the 4K memory
vm_hugepages_nr_4K = memory.vm_hugepages_nr_4K if (
memory.vm_hugepages_nr_4K is not None) else 0
vm_4K_pages.append(vm_hugepages_nr_4K)
# Now the vswitch memory of each hugepage size.
vswitch_2M_page = 0
vswitch_1G_page = 0
if memory.vswitch_hugepages_size_mib == constants.MIB_2M:
vswitch_2M_page = memory.vswitch_hugepages_nr
elif memory.vswitch_hugepages_size_mib == constants.MIB_1G:
vswitch_1G_page = memory.vswitch_hugepages_nr
vswitch_2M_pages.append(vswitch_2M_page)
vswitch_1G_pages.append(vswitch_1G_page)
# Build up the config values.
vswitch_2M = "\"%s\"" % ','.join([str(i) for i in vswitch_2M_pages])
vswitch_1G = "\"%s\"" % ','.join([str(i) for i in vswitch_1G_pages])
vm_4K = "\"%s\"" % ','.join([str(i) for i in vm_4K_pages])
# Add the new entries to the DEFAULT config section.
default_config.update({
'compute_vm_4K_pages': vm_4K,
'compute_vswitch_2M_pages': vswitch_2M,
'compute_vswitch_1G_pages': vswitch_1G,
})
def _get_per_host_overrides(self):
host_list = []
hosts = self.dbapi.ihost_get_list()
for host in hosts:
if (host.invprovision in [constants.PROVISIONED,
constants.PROVISIONING]):
if constants.WORKER in utils.get_personalities(host):
hostname = str(host.hostname)
default_config = {}
vnc_config = {}
libvirt_config = {}
self._update_host_cpu_maps(host, default_config)
self._update_host_storage(host, default_config, libvirt_config)
self._update_host_addresses(host, default_config, vnc_config,
libvirt_config)
self._update_host_memory(host, default_config)
host_nova = {
'name': hostname,
'conf': {
'nova': {
'DEFAULT': default_config,
'vnc': vnc_config,
'libvirt': libvirt_config,
}
}
}
host_list.append(host_nova)
return host_list
def get_region_name(self):
return self._get_service_region_name(self.SERVICE_NAME)