# # Copyright (c) 2018 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__) SCHEDULER_FILTERS_COMMON = [ 'RetryFilter', 'ComputeFilter', 'BaremetalFilter', 'AvailabilityZoneFilter', 'AggregateInstanceExtraSpecsFilter', 'ComputeCapabilitiesFilter', 'ImagePropertiesFilter', 'VCpuModelFilter', 'NUMATopologyFilter', 'ServerGroupAffinityFilter', 'ServerGroupAntiAffinityFilter', 'PciPassthroughFilter', 'DiskFilter', ] class NovaHelm(openstack.OpenstackBaseHelm): """Class to encapsulate helm operations for the nova chart""" CHART = constants.HELM_CHART_NOVA SUPPORTED_NAMESPACES = [ common.HELM_NS_OPENSTACK ] SERVICE_NAME = 'nova' AUTH_USERS = ['nova', 'placement'] SERVICE_USERS = ['neutron', 'ironic'] def get_namespaces(self): return self.SUPPORTED_NAMESPACES def get_overrides(self, namespace=None): scheduler_filters = SCHEDULER_FILTERS_COMMON overrides = { common.HELM_NS_OPENSTACK: { 'pod': { 'user': { 'nova': { 'uid': 0 } } }, 'manifests': { 'cron_job_cell_setup': False, 'cron_job_service_cleaner': False }, 'conf': { 'ceph': { 'enabled': False }, 'nova': { 'DEFAULT': { 'default_mempages_size': 2048, 'reserved_host_memory_mb': 0, 'compute_monitors': 'cpu.virt_driver', 'running_deleted_instance_poll_interval': 60, 'mkisofs_cmd': '/usr/bin/genisoimage', 'network_allocate_retries': 2, 'force_raw_images': False, 'concurrent_disk_operations': 2, # Set number of block device allocate retries and interval # for volume create when VM boots and creates a new volume. # The total block allocate retries time is set to 2 hours # to satisfy the volume allocation time on slow RPM disks # which may take 1 hour and a half per volume when several # volumes are created in parallel. 'block_device_allocate_retries_interval': 3, 'block_device_allocate_retries': 2400, 'disk_allocation_ratio': 1.0, 'cpu_allocation_ratio': 16.0, 'ram_allocation_ratio': 1.0, 'remove_unused_original_minimum_age_seconds': 3600, 'enable_new_services': False, 'map_new_hosts': False }, 'libvirt': { 'virt_type': self._get_virt_type(), 'cpu_mode': 'none', 'live_migration_completion_timeout': 180, 'live_migration_permit_auto_converge': True, 'mem_stats_period_seconds': 0, 'rbd_secret_uuid': None, 'rbd_user': None, # Allow up to 1 day for resize confirm 'remove_unused_resized_minimum_age_seconds': 86400 }, 'database': { 'max_overflow': 64, 'idle_timeout': 60, 'max_pool_size': 1 }, 'api_database': { 'max_overflow': 64, 'idle_timeout': 60, 'max_pool_size': 1 }, 'cell0_database': { 'max_overflow': 64, 'idle_timeout': 60, 'max_pool_size': 1 }, 'placement': { 'os_interface': 'internal' }, 'neutron': { 'default_floating_pool': 'public' }, 'notifications': { 'notification_format': 'unversioned' }, 'filter_scheduler': { 'enabled_filters': scheduler_filters, 'ram_weight_multiplier': 0.0, 'disk_weight_multiplier': 0.0, 'io_ops_weight_multiplier': -5.0, 'pci_weight_multiplier': 0.0, 'soft_affinity_weight_multiplier': 0.0, 'soft_anti_affinity_weight_multiplier': 0.0 }, 'scheduler': { 'periodic_task_interval': -1, 'discover_hosts_in_cells_interval': 30 }, 'metrics': { 'required': False, 'weight_setting_multi': 'vswitch.multi_avail=100.0', 'weight_setting': 'vswitch.max_avail=100.0' }, 'vnc': { 'novncproxy_base_url': self._get_novncproxy_base_url(), }, 'upgrade_levels': 'None' }, 'overrides': { 'nova_compute': { 'hosts': self._get_per_host_overrides() } } }, 'endpoints': self._get_endpoints_overrides(), 'images': self._get_images_overrides(), } } 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): pvs = self.dbapi.ipv_get_by_ihost(host.id) instance_backing = constants.LVG_NOVA_BACKING_IMAGE concurrent_disk_operations = constants.LVG_NOVA_PARAM_DISK_OPS_DEFAULT rbd_pool = constants.CEPH_POOL_EPHEMERAL_NAME rbd_ceph_conf = os.path.join(constants.CEPH_CONF_PATH, constants.SB_TYPE_CEPH_CONF_FILENAME) nova_lvg_uuid = None for pv in pvs: if (pv.lvm_vg_name == constants.LVG_NOVA_LOCAL and pv.pv_state != constants.PV_ERR): nova_lvg_uuid = pv.ilvg_uuid if nova_lvg_uuid: lvg = self.dbapi.ilvg_get(nova_lvg_uuid) instance_backing = lvg.capabilities.get( constants.LVG_NOVA_PARAM_BACKING) concurrent_disk_operations = lvg.capabilities.get( constants.LVG_NOVA_PARAM_DISK_OPS) default_config.update({'concurrent_disk_operations': concurrent_disk_operations}) # 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 instance_backing == constants.LVG_NOVA_BACKING_IMAGE: libvirt_config.update({'images_type': 'default'}) elif instance_backing == constants.LVG_NOVA_BACKING_REMOTE: libvirt_config.update({'images_type': 'rbd', 'images_rbd_pool': rbd_pool, 'images_rbd_ceph_conf': rbd_ceph_conf}) 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 _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 == constants.PROVISIONED): 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)