From 0a54d6de24f0e27ceb67d8a4c0f388acc5c28003 Mon Sep 17 00:00:00 2001 From: Jaganathan Palanisamy Date: Thu, 5 Apr 2018 16:37:42 -0400 Subject: [PATCH] Validation for no PMD cores on a NUMA node Add a validation to fail the deployment when 0 PMD cores on a NUMA node. When there is no PMD cores assigned to a NUMA node on the DPDK compute node, the deployment will succeed, but will face issues after the guest VM is created. Change-Id: I19ff82c7737283da942649936f74fa041ee8c8ae Closes-Bug: #1747616 (cherry picked from commit c847f0632fb94cea9b04905253af71bd3834d382) --- .../library/test_ovs_dpdk_pmd_cpus_check.py | 111 ++++++++++++++ .../library/ovs_dpdk_pmd_cpus_check.py | 137 ++++++++++++++++++ validations/ovs-dpdk-pmd-cpus-check.yaml | 23 +++ 3 files changed, 271 insertions(+) create mode 100644 tripleo_validations/tests/library/test_ovs_dpdk_pmd_cpus_check.py create mode 100644 validations/library/ovs_dpdk_pmd_cpus_check.py create mode 100644 validations/ovs-dpdk-pmd-cpus-check.yaml diff --git a/tripleo_validations/tests/library/test_ovs_dpdk_pmd_cpus_check.py b/tripleo_validations/tests/library/test_ovs_dpdk_pmd_cpus_check.py new file mode 100644 index 000000000..6e8295905 --- /dev/null +++ b/tripleo_validations/tests/library/test_ovs_dpdk_pmd_cpus_check.py @@ -0,0 +1,111 @@ +# Copyright 2016 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. + + +from mock import MagicMock +from mock import patch + +from tripleo_validations.tests import base +import validations.library.ovs_dpdk_pmd_cpus_check as validation + + +class TestOvsDpdkPmdCpusCheck(base.TestCase): + + def setUp(self): + super(TestOvsDpdkPmdCpusCheck, self).setUp() + self.module = MagicMock() + + @patch('validations.library.ovs_dpdk_pmd_cpus_check.' + 'get_nodes_cores_info') + @patch('validations.library.ovs_dpdk_pmd_cpus_check.' + 'get_cpus_list_from_mask_value') + def test_validate_valid_pmd_cpus(self, mock_pmd_cpus, mock_cpus): + mock_pmd_cpus.return_value = '0,1' + mock_cpus.return_value = ( + [0, 1], + [{'numa_node': 0, 'thread_siblings': [0, 2], 'cpu': 0}, + {'numa_node': 0, 'thread_siblings': [4, 6], 'cpu': 4}, + {'numa_node': 0, 'thread_siblings': [8, 10], 'cpu': 8}, + {'numa_node': 1, 'thread_siblings': [1, 3], 'cpu': 1}, + {'numa_node': 1, 'thread_siblings': [5, 7], 'cpu': 5}, + {'numa_node': 1, 'thread_siblings': [9, 11], 'cpu': 9}]) + + validation.validate_pmd_cpus(self.module, '"3"') + self.module.exit_json.assert_called_with( + msg="PMD CPU's configured correctly.") + + @patch('validations.library.ovs_dpdk_pmd_cpus_check.' + 'get_nodes_cores_info') + @patch('validations.library.ovs_dpdk_pmd_cpus_check.' + 'get_cpus_list_from_mask_value') + def test_validate_invalid_pmd_cpus(self, mock_pmd_cpus, mock_cpus): + mock_pmd_cpus.return_value = '0,2' + mock_cpus.return_value = ( + [0, 1], + [{'numa_node': 0, 'thread_siblings': [0, 2], 'cpu': 0}, + {'numa_node': 0, 'thread_siblings': [4, 6], 'cpu': 4}, + {'numa_node': 0, 'thread_siblings': [8, 10], 'cpu': 8}, + {'numa_node': 1, 'thread_siblings': [1, 3], 'cpu': 1}, + {'numa_node': 1, 'thread_siblings': [5, 7], 'cpu': 5}, + {'numa_node': 1, 'thread_siblings': [9, 11], 'cpu': 9}]) + + validation.validate_pmd_cpus(self.module, '"5"') + self.module.fail_json.assert_called_with( + msg="Invalid PMD CPU's, cpu is not used from NUMA node(s): 1.") + + def test_get_cpus_list_from_mask_value(self): + cpu_mask_val = '"3"' + expected_value = "0,1" + result = validation.get_cpus_list_from_mask_value(cpu_mask_val) + self.assertEqual(result, expected_value) + + def test_valid_get_nodes_cores_info(self): + lines = "# format\n0,0,0\n 0,0,2\n1,1,1\n1,1,3" + self.module.run_command.return_value = [0, lines, ""] + + expected_value = ( + [0, 1], + [{'numa_node': 0, 'thread_siblings': [0, 2], 'cpu': 0}, + {'numa_node': 1, 'thread_siblings': [1, 3], 'cpu': 1}]) + result = validation.get_nodes_cores_info(self.module) + self.assertListEqual(result[0], expected_value[0]) + self.assertListEqual(result[1], expected_value[1]) + + def test_invalid_missing_val_get_nodes_cores_info(self): + lines = "# format\n,0,0\n 0,0,2\n1,1,1\n1,1,3" + self.module.run_command.return_value = [0, lines, ""] + validation.get_nodes_cores_info(self.module) + self.module.fail_json.assert_called_with( + msg="Unable to determine physical and logical cpus.") + + def test_invalid_missing_field_get_nodes_cores_info(self): + lines = "# format\n0,0\n 0,0,2\n1,1,1\n1,1,3" + self.module.run_command.return_value = [0, lines, ""] + validation.get_nodes_cores_info(self.module) + self.module.fail_json.assert_called_with( + msg="Unable to determine physical and logical cpus.") + + def test_invalid_incorrect_value_get_nodes_cores_info(self): + lines = "# format\nab,0,0\n0,0,2\n1,1,1\n1,1,3" + self.module.run_command.return_value = [0, lines, ""] + validation.get_nodes_cores_info(self.module) + self.module.fail_json.assert_called_with( + msg="Unable to determine physical and logical cpus.") + + def test_invalid_command_result_get_nodes_cores_info(self): + self.module.run_command.return_value = [] + validation.get_nodes_cores_info(self.module) + self.module.fail_json.assert_called_with( + msg="Unable to determine physical and logical cpus.") diff --git a/validations/library/ovs_dpdk_pmd_cpus_check.py b/validations/library/ovs_dpdk_pmd_cpus_check.py new file mode 100644 index 000000000..bcdf75ccd --- /dev/null +++ b/validations/library/ovs_dpdk_pmd_cpus_check.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python + +# -*- coding: utf-8 -*- +# 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 ansible.module_utils.basic import AnsibleModule + +DOCUMENTATION = ''' +--- +module: OVS DPDK PMD CPU's check +short_description: Run PMD CPU's from all the NUMA nodes check +description: + - Run PMD CPU's from all the NUMA nodes check +options: + pmd_cpu_mask: + required: true + description: + - The pmd cpu mask value + type: str +author: "Jaganathan Palanisamy" +''' + +EXAMPLES = ''' +- hosts: ComputeOvsDpdk + vars: + pmd_cpu_mask: "1010010000000001" + tasks: + - name: Run PMD CPU's check + become: true + ovs_dpdk_pmd_cpus_check: pmd_cpu_mask={{ pmad_cpu_mask }} +''' + + +def get_cpus_list_from_mask_value(mask_val): + """Gets CPU's list from the mask value + + :return: comma separated CPU's list + """ + mask_val = mask_val.strip('\\"') + cpus_list = [] + int_mask_val = int(mask_val, 16) + bin_mask_val = bin(int_mask_val) + bin_mask_val = str(bin_mask_val).replace('0b', '') + rev_bin_mask_val = bin_mask_val[::-1] + thread = 0 + for bin_val in rev_bin_mask_val: + if bin_val == '1': + cpus_list.append(thread) + thread += 1 + return ','.join([str(cpu) for cpu in cpus_list]) + + +# Gets the distinct numa nodes, physical and logical cpus info +# for all numa nodes. +def get_nodes_cores_info(module): + dict_cpus = {} + numa_nodes = [] + cmd = "sudo lscpu -p=NODE,CORE,CPU" + result = module.run_command(cmd) + if (not result or (result[0] != 0) or not (str(result[1]).strip(' '))): + err = "Unable to determine physical and logical cpus." + module.fail_json(msg=err) + else: + for line in str(result[1]).split('\n'): + if (line.strip(' ') and not line.strip(' ').startswith('#')): + cpu_info = line.strip(' ').split(',') + try: + node = int(cpu_info[0]) + cpu = int(cpu_info[1]) + thread = int(cpu_info[2]) + if node not in numa_nodes: + numa_nodes.append(node) + # CPU and NUMA node together forms a unique value, + # as cpu is specific to a NUMA node + # NUMA node id and cpu id tuple is used for unique key + key = node, cpu + if key in dict_cpus: + if thread not in dict_cpus[key]['thread_siblings']: + dict_cpus[key]['thread_siblings'].append(thread) + else: + cpu_item = {} + cpu_item['thread_siblings'] = [thread] + cpu_item['cpu'] = cpu + cpu_item['numa_node'] = node + dict_cpus[key] = cpu_item + except (IndexError, ValueError): + err = "Unable to determine physical and logical cpus." + module.fail_json(msg=err) + return (numa_nodes, list(dict_cpus.values())) + + +def validate_pmd_cpus(module, pmd_cpu_mask): + pmd_cpus = get_cpus_list_from_mask_value(pmd_cpu_mask) + pmd_cpu_list = pmd_cpus.split(',') + cpus = [] + numa_nodes = [] + numa_nodes, cpus = get_nodes_cores_info(module) + valid_numa_nodes = {} + for numa_node in numa_nodes: + valid_numa_nodes[str(numa_node)] = False + for cpu in cpus: + if cpu['numa_node'] == numa_node: + if True in [int(pmd_cpu) in cpu['thread_siblings'] + for pmd_cpu in pmd_cpu_list]: + valid_numa_nodes[str(numa_node)] = True + invalid_numa_nodes = [node for node, val in valid_numa_nodes.items() + if not val] + if invalid_numa_nodes: + failed_nodes = ','.join(invalid_numa_nodes) + err = ("Invalid PMD CPU's, cpu is not used from " + "NUMA node(s): %(node)s." % {'node': failed_nodes}) + module.fail_json(msg=err) + else: + module.exit_json(msg="PMD CPU's configured correctly.") + + +def main(): + module = AnsibleModule(argument_spec=dict( + pmd_cpu_mask=dict(required=True, type='str'), + )) + validate_pmd_cpus(module, + module.params.get('pmd_cpu_mask')) + + +if __name__ == '__main__': + main() diff --git a/validations/ovs-dpdk-pmd-cpus-check.yaml b/validations/ovs-dpdk-pmd-cpus-check.yaml new file mode 100644 index 000000000..ca258f919 --- /dev/null +++ b/validations/ovs-dpdk-pmd-cpus-check.yaml @@ -0,0 +1,23 @@ +--- +- hosts: ComputeOvsDpdk + vars: + metadata: + name: Validates OVS DPDK PMD cores from all NUMA nodes. + description: > + OVS DPDK PMD cpus must be provided from all NUMA nodes. + + A failed status post-deployment indicates PMD CPU list is not + configured correctly. + groups: + - post-deployment + become: true + + tasks: + - name: Get OVS DPDK PMD cores mask value + become_method: sudo + register: pmd_cpu_mask + command: ovs-vsctl --no-wait get Open_vSwitch . other_config:pmd-cpu-mask + changed_when: False + + - name: Run OVS DPDK PMD cores check + ovs_dpdk_pmd_cpus_check: pmd_cpu_mask={{ pmd_cpu_mask.stdout }}