# Copyright 2020 Red Hat, Inc. # All Rights Reserved. # # 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. """Test the derive and get_vcpus_per_osd methods of the HCI module""" import yaml from tripleo_ansible.ansible_plugins.modules import tripleo_derive_hci_parameters as derive_params from tripleo_ansible.tests import base as tests_base class TestTripleoDeriveHciParameters(tests_base.TestCase): """Test the derive method of the HCI module""" def test_derive_positive(self): """Test the derive method with valid input and confirm expected result """ der = derive_params.derive(mem_gb=256, vcpus=4, osds=1, average_guest_memory_size_in_mb=2048, average_guest_cpu_utilization_percentage=20) self.assertFalse(der['failed']) self.assertEqual(der['nova_reserved_mem_mb'], 56320) self.assertEqual(der['cpu_allocation_ratio'], 3.75) def test_derive_negative(self): """Test the derive method with invalid input """ der = derive_params.derive(mem_gb=2, vcpus=1, osds=1, average_guest_memory_size_in_mb=0, average_guest_cpu_utilization_percentage=0) self.assertTrue(der['failed']) def test_vcpu_ratio(self): """Test the get_vcpus_per_osd method and confirm expected result """ def mock_ironic(): """Return a dictionary with partial disks section of introspection """ return {'data': {'inventory': {'disks': [ {'by_path': '/dev/disk/by-path/pci-0000:00:07.0-scsi-0:0:0:5', 'name': '/dev/sda', 'rotational': True, 'wwn': None}, {'by_path': '/dev/disk/by-path/pci-0000:00:07.0-scsi-0:0:0:4', 'name': '/dev/sdb', 'rotational': True, 'wwn': None}, {'by_path': '/dev/disk/by-path/pci-0000:00:07.0-scsi-0:0:0:3', 'name': '/dev/sdc', 'rotational': True, 'wwn': None}, {'by_path': '/dev/disk/by-path/pci-0000:00:07.0-scsi-0:0:0:2', 'name': '/dev/sdd', 'rotational': True, 'wwn': None}, {'by_path': '/dev/disk/by-path/pci-0000:00:01.1-ata-1', 'name': '/dev/sde', 'rotational': True, 'wwn': None} ] } } } def get_ironic(flavor='hdd'): """Returns a dictionary which mocks ironic introspection data. Uses mock introspection data as the source but then applies flavor variations to make it look like the system which was introspected has SSD or NVMe SSDs. """ ironic = mock_ironic() if flavor in 'ssd': for dev in ironic['data']['inventory']['disks']: dev['rotational'] = False if flavor in 'nvme': i = 1 for dev in ironic['data']['inventory']['disks']: nvm_name = "/dev/nvme0n%i" % i dev['name'] = nvm_name dev['rotational'] = False i += 1 return ironic def get_env_ceph_ansible(flavor='hdd', osds_per_device=1): """Returns a dictionary which mocks the content of the tripleo_environment_parameters CephAnsibleDisksConfig where the deployer requests four OSDs using device list within ceph-ansible of differing flavor types. The flavor may be set to one of hdd, ssd, by_path, or nvme and it is also possible to set the osds_per_device (usually used with NVMe). Uses mock introspection data in molecule to build the device list with flavor variations. """ ironic = mock_ironic() devices = [] i = 1 for dev in ironic['data']['inventory']['disks']: if flavor in ('hdd', 'ssd'): devices.append(dev['name']) elif flavor in 'by_path': devices.append(dev['by_path']) elif flavor in 'nvme': nvm_name = "/dev/nvme0n%i" % i devices.append(nvm_name) i += 1 if i > 4: break disks_config = { "osd_objectstore": "bluestore", "osd_scenario": "lvm", "devices": devices } if osds_per_device > 1: disks_config['osds_per_device'] = osds_per_device env = { "CephAnsibleDisksConfig": disks_config } return env def get_env_cephadm(flavor='hdd', osds_per_device=1): """Returns a dictionary which mocks the content of the tripleo_environment_parameters CephOsd{Count,Type,Spec} where the deployer requests a number of OSDs of differing flavor types. The flavor may be set to one of hdd, ssd, or nvme and it is also possible to set the osds_per_device (usually used with NVMe). """ if osds_per_device == 0: osds_per_device = 1 env_cephadm = { "CephHciOsdCount": 5, "CephHciOsdType": flavor, "CephOsdSpec": { "data_devices": { "all": True, }, "osds_per_device": osds_per_device } } return env_cephadm ratio_map = { 'hdd': 1, 'ssd': 4, 'by_path': 1, 'nvme': 3 } for flavor in ratio_map: envs = [] if flavor == 'nvme': osds_per_device = 4 else: osds_per_device = 0 envs.append(get_env_ceph_ansible(flavor, osds_per_device)) if flavor != 'by_path': envs.append(get_env_cephadm(flavor, osds_per_device)) ironic = get_ironic(flavor) for env in envs: if "CephHciOsdCount" in env and "CephHciOsdType" in env: vcpu_ratio, vcpu_msg = derive_params\ .get_vcpus_per_osd(env, env['CephHciOsdCount'], env['CephHciOsdType'], env['CephOsdSpec']) else: num_osds = derive_params.count_osds(env) vcpu_ratio, vcpu_msg = \ derive_params.get_vcpus_per_osd_from_ironic(ironic, env, num_osds) self.assertEqual(vcpu_ratio, ratio_map[flavor]) self.assertIsNotNone(vcpu_msg) def test_derive_without_workload(self): """Test the derive method without passing the expected average guest cpu and mem utilization and confirm expected result """ der = derive_params.derive(mem_gb=256, vcpus=56, osds=16) self.assertFalse(der['failed']) self.assertEqual(der['nova_reserved_mem_mb'], 81920) def test_count_memory(self): """Test that the count_memory method can the right number regardless of which value ironic might provide. """ mock_ironic_memory_mb = {'data': {'memory_mb': 262144}} mock_ironic_memory_bytes = {'data': {'memory_mb': 0, 'inventory': {'memory': {'total': 274877906944}}}} gb_from_mb = derive_params.count_memory(mock_ironic_memory_mb) gb_from_bytes = derive_params.count_memory(mock_ironic_memory_bytes) self.assertEqual(gb_from_mb, gb_from_bytes) def test_find_parameter(self): """Tests that the find_parameter method returns the expected output for particular inputs. """ env = {'CephHciOsdCount': 3, 'CephHciOsdType': 'ssd', 'ComputeHCIParameters': { 'CephHciOsdCount': 4 }, } value = derive_params.find_parameter(env, 'CephHciOsdCount', 'ComputeHCI') self.assertEqual(value, 4) value = derive_params.find_parameter(env, 'CephHciOsdCount') self.assertEqual(value, 3) value = derive_params.find_parameter(env, 'CephOsdSpec') self.assertEqual(value, 0) value = derive_params.find_parameter(env, 'CephHciOsdType') self.assertEqual(value, 'ssd')