diff --git a/doc/source/modules/modules-check_cpus_aligned_with_dpdk_nics.rst b/doc/source/modules/modules-check_cpus_aligned_with_dpdk_nics.rst new file mode 100644 index 000000000..96dac3d1a --- /dev/null +++ b/doc/source/modules/modules-check_cpus_aligned_with_dpdk_nics.rst @@ -0,0 +1,15 @@ +========================================== +Module - check_cpus_aligned_with_dpdk_nics +========================================== + + +This module provides for the following ansible plugin: + + * check_cpus_aligned_with_dpdk_nics + + +.. ansibleautoplugin:: + :module: library/check_cpus_aligned_with_dpdk_nics.py + :documentation: true + :examples: true + diff --git a/doc/source/modules/modules-check_other_processes_pmd_usage.rst b/doc/source/modules/modules-check_other_processes_pmd_usage.rst new file mode 100644 index 000000000..203374567 --- /dev/null +++ b/doc/source/modules/modules-check_other_processes_pmd_usage.rst @@ -0,0 +1,15 @@ +======================================== +Module - check_other_processes_pmd_usage +======================================== + + +This module provides for the following ansible plugin: + + * check_other_processes_pmd_usage + + +.. ansibleautoplugin:: + :module: library/check_other_processes_pmd_usage.py + :documentation: true + :examples: true + diff --git a/doc/source/modules/modules-convert_range_to_numbers_list.rst b/doc/source/modules/modules-convert_range_to_numbers_list.rst new file mode 100644 index 000000000..c0752f4fd --- /dev/null +++ b/doc/source/modules/modules-convert_range_to_numbers_list.rst @@ -0,0 +1,15 @@ +====================================== +Module - convert_range_to_numbers_list +====================================== + + +This module provides for the following ansible plugin: + + * convert_range_to_numbers_list + + +.. ansibleautoplugin:: + :module: library/convert_range_to_numbers_list.py + :documentation: true + :examples: true + diff --git a/doc/source/modules/modules-get_dpdk_nics_numa_info.rst b/doc/source/modules/modules-get_dpdk_nics_numa_info.rst new file mode 100644 index 000000000..44c880159 --- /dev/null +++ b/doc/source/modules/modules-get_dpdk_nics_numa_info.rst @@ -0,0 +1,15 @@ +================================ +Module - get_dpdk_nics_numa_info +================================ + + +This module provides for the following ansible plugin: + + * get_dpdk_nics_numa_info + + +.. ansibleautoplugin:: + :module: library/get_dpdk_nics_numa_info.py + :documentation: true + :examples: true + diff --git a/doc/source/modules/modules-pmd_threads_siblings_check.rst b/doc/source/modules/modules-pmd_threads_siblings_check.rst new file mode 100644 index 000000000..d63e30fe8 --- /dev/null +++ b/doc/source/modules/modules-pmd_threads_siblings_check.rst @@ -0,0 +1,15 @@ +=================================== +Module - pmd_threads_siblings_check +=================================== + + +This module provides for the following ansible plugin: + + * pmd_threads_siblings_check + + +.. ansibleautoplugin:: + :module: library/pmd_threads_siblings_check.py + :documentation: true + :examples: true + diff --git a/doc/source/roles/role-check_nfv_ovsdpdk_zero_packet_loss.rst b/doc/source/roles/role-check_nfv_ovsdpdk_zero_packet_loss.rst new file mode 100644 index 000000000..544492b1d --- /dev/null +++ b/doc/source/roles/role-check_nfv_ovsdpdk_zero_packet_loss.rst @@ -0,0 +1,39 @@ +================================== +check_nfv_ovsdpdk_zero_packet_loss +================================== +-------------- +About the role +-------------- +This role validates the NFV OvS DPDK zero packet loss rules on OvS DPDK Compute nodes to find out the issues with NFV OvS Dpdk configuration. +Requirements +============ +- Validates PMD threads configuration. +- Validates PMD threads included as part of isolcpus. +- Checks any interrupts on Isolated CPU's. +- Validates all the data paths are same on the server if ovs user bridge is used. +- Validates bandwidth of the PCI slots. +- Validates hugepages, CPU pinning, emulatorpin threads and libvirt queue size configuration on NFV instances. +Dependencies +============ +- Expects all the configuration files that are passed. +Example Playbook +================ +.. code-block:: yaml + + - hosts: servers + roles: + + - { role: check_nfv_ovsdpdk_zero_packet_loss } + +License +======= +Apache +Author Information +================= +**Red Hat TripleO DFG:NFV Integration** +---------------- +Full Description +---------------- + +.. ansibleautoplugin:: + :role: roles/check_nfv_ovsdpdk_zero_packet_loss diff --git a/library/check_cpus_aligned_with_dpdk_nics.py b/library/check_cpus_aligned_with_dpdk_nics.py new file mode 100644 index 000000000..4b08c9a27 --- /dev/null +++ b/library/check_cpus_aligned_with_dpdk_nics.py @@ -0,0 +1,140 @@ +#!/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. +"""check_cpus_aligned_with_dpdk_nics module +Used by the `check_nfv_ovsdpdk_zero_packet_loss` role. +""" + +from ansible.module_utils.basic import AnsibleModule +from yaml import safe_load as yaml_safe_load + +import json +import yaml + +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 + - Owned by the DFG:NFV Integration +options: + cpus: + required: true + description: + - The CPU's list + type: str + numa_node: + required: true + description: + - The NUMA node + type: int + dpdk_nics_numa_info: + required: true + description: + - The DPDK NIC's NUMA details + type: list +author: "Jaganathan Palanisamy" +''' + +EXAMPLES = ''' +# Call this module from TripleO Ansible Validations + +- name: Check CPU's aligned with DPDK NIC's NUMA + become: true + check_cpus_aligned_with_dpdk_nics: + cpus: "2,3,4,5" + numa_node: "0" + dpdk_nics_numa_info: [{"numa_node": 0, "mac": "mac1", "pci": "pci1"}, + {"numa_node": 0, "mac": "mac2", "pci": "pci2"}] + register: valid_cpus +''' + + +def get_nodes_cpus_info(module): + """Gets the logical cpus info for all numa nodes.""" + + dict_cpus = {} + # Gets numa node and cpu details + cmd = "lscpu -p=NODE,CPU" + result = module.run_command(cmd) + if (not result or (result[0] != 0) or not (str(result[1]).strip(' '))): + err = "Unable to determine NUMA cpus" + module.fail_json(msg=err) + else: + output = str(result[1]) + try: + for line in output.split('\n'): + if line and '#' not in line: + cpu_info = line.split(',') + node = int(cpu_info[0]) + thread = int(cpu_info[1]) + if node in dict_cpus: + if thread not in dict_cpus[node]: + dict_cpus[node].append(thread) + else: + dict_cpus[node] = [thread] + except (IndexError, ValueError): + err = "Unable to determine NUMA cpus" + module.fail_json(msg=err) + return dict_cpus + + +def check_cpus_aligned_with_dpdk_nics(module, cpus, numa_node, dpdk_nics_numa_info): + """Checks cpus aligned with NUMA with DPDK NIC's.""" + + result = dict( + changed=False, + valid_cpus=False, + message='' + ) + nodes = [] + valid_numa = False + invalid_cpus = [] + nodes_cpus = get_nodes_cpus_info(module) + for dpdk_nics_numa in dpdk_nics_numa_info: + if (dpdk_nics_numa['numa_node'] == numa_node): + valid_numa = True + break + if not valid_numa: + err = "NFV instance is not aligned with DPDK NIC's NUMA." + module.fail_json(msg=err) + for cpu in cpus.split(','): + if not int(cpu) in nodes_cpus[numa_node]: + invalid_cpus.append(cpu) + if invalid_cpus: + err = "CPU's are not aligned with DPDK NIC's NUMA, Invalid CPU's: "+','.join(invalid_cpus) + result['message'] = err + result['valid_cpus'] = False + module.fail_json(msg=err) + else: + result['message'] = "CPU's configured correctly: " + cpus + result['valid_cpus'] = True + module.exit_json(**result) + + +def main(): + module = AnsibleModule( + argument_spec=yaml_safe_load(DOCUMENTATION)['options'] + ) + + check_cpus_aligned_with_dpdk_nics(module, + module.params.get('cpus'), + module.params.get('numa_node'), + module.params.get('dpdk_nics_numa_info')) + + +if __name__ == '__main__': + main() diff --git a/library/check_other_processes_pmd_usage.py b/library/check_other_processes_pmd_usage.py new file mode 100644 index 000000000..06e7352ce --- /dev/null +++ b/library/check_other_processes_pmd_usage.py @@ -0,0 +1,162 @@ +#!/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. +"""check_other_processes_pmd_usage module +Used by the `check_nfv_ovsdpdk_zero_packet_loss` role. +""" + +from ansible.module_utils.basic import AnsibleModule +from yaml import safe_load as yaml_safe_load + +DOCUMENTATION = ''' +--- +module: Check OVS DPDK PMD threads used by other processes or not +short_description: Run PMD threads used by other processes or not check +description: + - Run PMD threads used by other processes or not check + - Owned by the DFG:NFV Integration +options: + pmd_cpus: + required: true + description: + - The pmd cpus list + type: list + exclude_processes_pid: + required: false + description: + - The processes pid list which need to be excluded. + - This option is optional. + default: [] + type: list + +author: "Jaganathan Palanisamy" +''' + +EXAMPLES = ''' +# Call this module from TripleO Ansible Validations + +- name: Run PMD threads used by other processes or not check + become: true + check_other_processes_pmd_usage: + pmd_cpus: [6, 7, 9, 11] + register: pmd_interrupts + +- name: Run PMD threads used by other processes or not with exclude processes + become: true + check_other_processes_pmd_usage: + pmd_cpus: [6, 7, 9, 11] + exclude_processes_pid: ['24', '26'] + register: pmd_interrupts +''' + + +def check_current_process_pmd_usage(module, pmd_cpus, process_id, range_list): + """Check pmd usage in current process cpus range list.""" + + messages = [] + num_list = [] + exclude_num_list = [] + threads_used = [] + try: + for val in range_list.split(','): + val = val.strip(' ') + if '^' in val: + exclude_num_list.append(int(val[1:])) + elif '-' in val: + split_list = val.split("-") + range_min = int(split_list[0]) + range_max = int(split_list[1]) + num_list.extend(range(range_min, (range_max + 1))) + else: + num_list.append(int(val)) + except ValueError as exc: + err = ("Invalid number in input param " + "'range_list': %s" % exc) + module.fail_json(msg=err) + # here, num_list is a list of integers + threads_list = [str(num) for num in num_list if num not in exclude_num_list] + for thread in threads_list: + if thread in pmd_cpus: + if threads_used: + threads_used.append(thread) + else: + threads_used = [thread] + if threads_used: + messages.append("pmd threads: " + ','.join(threads_used) + " used in process: " + process_id) + return list(messages) + + +def check_other_processes_pmd_usage(module, pmd_cpus, exclude_processes_pid): + """Checks PMD threads used in any other process or not""" + + output = dict( + pmd_interrupts=False, + messages=[] + ) + messages = [] + threads_used = {} + current_processes = [] + + # Gets all the processes and corresponding threads usage + # except processes mentioned in exclude_processes_pid list + # processes pid and threads information + cmd = ("find -L /proc/[0-9]*/exe ! -type l | cut -d / -f3 | " + "xargs -l -i sh -c 'ps -p {} -o comm=; taskset -acp {}' | " + "grep -vE '" + '|'.join(exclude_processes_pid) + "' | " + "awk '{printf \"%s %s\\n\", $2, $6}'") + result = module.run_command(cmd, use_unsafe_shell=True) + if (not result or (result[0] != 0) or not (str(result[1]).strip(' '))): + err = "Unable to determine current processes" + module.fail_json(msg=err) + else: + current_processes = str(result[1]).split('\n') + + pmd_threads_processes = [] + # Gets processes associated to PMD and corresponding threads usage + # proceses pid and threads information + cmd = ("ps -T -o spid,comm -p $(pidof ovs-vswitchd) |grep '\- + "{{ container_cli }}" exec -u root nova_libvirt virsh dumpxml "{{ vm_id }}" + register: instance_xml + +- name: Set instance {{ vm_id }} xml data + set_fact: + instance_xml_data: "{{ instance_xml.stdout }}" + +- name: Get interfaces from xml string + xml: + xmlstring: "{{ instance_xml_data }}" + xpath: /domain/devices/interface + content: attribute + attribute: type + register: xmlinterfacetype + +- name: Set default no valid nfv instance {{ vm_id }} + set_fact: + valid_nfv_instance: false + +- name: Check whether valid nfv instance {{ vm_id }} + set_fact: + valid_nfv_instance: true + when: "{{ interface_type.interface.type == 'vhostuser' }}" + loop: "{{ xmlinterfacetype.matches}}" + loop_control: + loop_var: interface_type + +- name: Validate NFV instance + vars: + instance_id: "{{ vm_id }}" + import_tasks: validate_instance.yml + when: valid_nfv_instance diff --git a/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/check_nfv_pci_address.yml b/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/check_nfv_pci_address.yml new file mode 100644 index 000000000..468d2bfff --- /dev/null +++ b/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/check_nfv_pci_address.yml @@ -0,0 +1,26 @@ +--- +- name: Initialize PCI bandwidth default value + set_fact: + pci_bw: 1 + +- name: Check pci addresses bandwidth + become: true + shell: "lspci -s {{ pci }} -vvnn | grep -i width" + register: pci_bandwidth + +- name: Compute bandwidth on pci address + set_fact: + pci_bw: "{{ pci_bw | int * ((bw_param.split(' ')[1] | replace('x', '')) | replace('GT/s', '') | int) }}" + when: "{{ 'Speed' in bw_param or 'Width' in bw_param }}" + loop: "{{ pci_bandwidth.stdout.split('\n')[0].split(', ') }}" + loop_control: + loop_var: bw_param + +- name: Get interface bandwidth + vars: + dpdk_pci_bw: "{{ pci_bw }}" + dpdk_port_name: "{{ dpdk_port }}" + include_tasks: validate_dpdk_port_bandwidth.yml + loop: "{{ dpdk_ports_list }}" + loop_control: + loop_var: dpdk_port diff --git a/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/main.yml b/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/main.yml new file mode 100644 index 000000000..e30582608 --- /dev/null +++ b/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/main.yml @@ -0,0 +1,217 @@ +--- +- name: Initialize validation message list + set_fact: + validation_msg: [] + pmd_isolated: true + +# Gets applied PMD cpus list +- name: Get OVS DPDK PMD cores mask value + become: true + register: pmd_cpu_mask + command: ovs-vsctl --no-wait get Open_vSwitch . other_config:pmd-cpu-mask + changed_when: false + +- name: Check OVS DPDK PMD cores thread siblings + become: true + pmd_threads_siblings_check: + pmd_cpu_mask: "{{ pmd_cpu_mask.stdout }}" + register: pmd_cpus_list + +- name: Set PMD cpus + set_fact: + pmd_cpus: "{{ pmd_cpus_list.pmd_cpus_list }}" + when: pmd_cpus_list.pmd_cpus_list is defined + +# Validates datapath's are mixed or not. +- name: Verify system and netdev datapath's are not mixed on compute node + block: + - name: Check bridge datapath type + become: true + shell: "ovs-vsctl list bridge | grep datapath_type" + register: bridge_data_path_type + + - name: Check if any datapath type is netdev + set_fact: + datapath_type_list: "{{ bridge_data_path_type.stdout.split('\n') }}" + when: "{{ 'netdev' in bridge_data_path_type.stdout }}" + + - name: Check if all the datapath type netdev + set_fact: + validation_msg: "{{ validation_msg }} + {{ ['Mixed system and netdev datapath's on the same compute node.'] }}" + when: "{{ not 'netdev' in datapath }}" + loop: "{{ datapath_type_list }}" + loop_control: + loop_var: datapath + +- name: Get DPDK NIC's NUMA info + become: true + get_dpdk_nics_numa_info: + dpdk_mapping_file: /var/lib/os-net-config/dpdk_mapping.yaml + register: dpdk_nics_numa + +- name: Set DPDK NIC's NUMA info + set_fact: + dpdk_nics_numa_info: "{{ dpdk_nics_numa.dpdk_nics_numa_info }}" + when: + - dpdk_nics_numa is defined + +- name: Get nova_libvirt_launcher Process + become: true + shell: |- + ps -Leaf | grep nova_libvirt_launcher.sh | grep -v pts | awk '{printf "%s", $2}' + register: nova_libvirt_launcher + +- name: Get nova libvirt namespace processes + become: true + shell: |- + pgrep --ns {{ nova_libvirt_launcher.stdout }} + register: nova_libvirt_proceses + +- name: Update nova libvirt namespace processes pid + set_fact: + nova_libvirt_proceses_pid: "{{ nova_libvirt_proceses.stdout.split('\n') | join('|') }}" + +- name: Get nova libvirt launch processes id + become: true + shell: |- + ps -Leaf | grep -E '{{ nova_libvirt_proceses_pid }}' | grep -v pts | awk '{printf "%s\n", $4}' + register: nova_libvirt_launch_pids + +- name: Update nova libvirt launch processes pid + set_fact: + nova_lib_launch_proceses_pid: "{{ nova_libvirt_launch_pids.stdout.split('\n') }}" + +- name: Check pmd cpus used in any other processes + become: true + check_other_processes_pmd_usage: + pmd_cpus: "{{ pmd_cpus | list }}" + exclude_processes_pid: "{{ nova_lib_launch_proceses_pid | list }}" + register: pmd_threads_usage + +- name: Update validation message if any PMD threads usage by other processes message + set_fact: + validation_msg: "{{ validation_msg }} + {{ pmd_threads_usage.messages }}" + when: + - pmd_threads_usage.pmd_interrupts + +# Validates PMD cpus are isolated or not. +- name: Check PMD cores should be isolated + become: true + shell: "cat /proc/cmdline" + register: kernel_args + +- name: Get isolcpus using kernel args + set_fact: + isol_cpus: "{{ kernel_arg.split('=')[1] }}" + when: "{{ 'isolcpus' == kernel_arg.split('=')[0] }}" + loop: "{{ kernel_args.stdout.split(' ') }}" + loop_control: + loop_var: kernel_arg + +- name: Convert isolcpus range list into number list + convert_range_to_numbers_list: + range_list: "{{ isol_cpus }}" + register: isol_cpus_list + +- name: check PMD threads isolated or not + set_fact: + pmd_isolated: false + when: "{{ not pmd_thread | int in isol_cpus_list.number_list }}" + loop: "{{ pmd_cpus }}" + loop_control: + loop_var: pmd_thread + +- name: Set message if pmd threads are not isolated + set_fact: + validation_msg: "{{ validation_msg }} + ['PMD threads are not isolated.']" + when: + - not pmd_isolated + +# Validates any interuppts happened on isolcpus list. +- name: Set isol cpus required columns + set_fact: + cpu_columns_format: "{{ (cpu_columns_format | default('')) + '%s,' }}" + cpu_columns: "{{ (cpu_columns | default('')) + '$'+ ((cpu | int + 2) | string) + ',' }}" + loop: "{{ isol_cpus_list.number_list | list }}" + loop_control: + loop_var: cpu + +- name: Update cpu columns in required format + set_fact: + cpu_columns_format: "{{ cpu_columns_format | regex_replace(',$', '') }}" + cpu_columns: "{{ cpu_columns | regex_replace(',$', '') }}" + +- name: Check interrupts on isolcpus list + become: true + shell: |- + cat /proc/interrupts | awk '{printf "%s-{{ cpu_columns_format }}\n", $1,{{ cpu_columns }} }' | grep -v [A-Za-z] + register: isolcpus_interrupts + +- name: Isol CPU's interrupts + set_fact: + validation_msg: "{{ validation_msg }} + {{ ['Interrupts exist in Isol cpus ' + ( isol_cpus_list.number_list | join(',')) +': ' + interrupts_line ] }}" + when: "{{ 'CPU' not in interrupts_line and interrupts_line.split('-')[1].replace('0', '').split(',') is any }}" + loop: "{{ isolcpus_interrupts.stdout.split('\n') }}" + loop_control: + loop_var: interrupts_line + +- name: Get list of dpdk ports + become: true + shell: "ovs-appctl dpctl/show | grep 'dpdk: configured'" + register: ovs_dpdk_ports + +- name: Get list of dpdk ports name + set_fact: + dpdk_ports: "{{ dpdk_ports | default([]) }} + {{ [dpdk_port_line.split(': ')[1].split(' ')[0]] }}" + loop: "{{ ovs_dpdk_ports.stdout.split('\n') }}" + loop_control: + loop_var: dpdk_port_line + +- name: Get DPDK NIC's PCI addresses + set_fact: + dpdk_pci_list: |- + {{ (dpdk_pci_list | default([])) }} + {{ [dpdk_nic_info.pci] }} + loop: "{{ dpdk_nics_numa_info }}" + loop_control: + loop_var: dpdk_nic_info + +- name: Check pci addresses bandwidth + vars: + pci: "{{ dpdk_pci }}" + dpdk_ports_list: "{{ dpdk_ports }}" + include_tasks: check_nfv_pci_address.yml + loop: "{{ dpdk_pci_list }}" + loop_control: + loop_var: dpdk_pci + +# validates the NFV instances on OvS DPDK node. +- name: Set container_cli fact from the inventory + set_fact: + container_cli: "{{ hostvars[inventory_hostname].container_cli | default('podman', true) }}" + when: container_cli is not defined + +- name: Get instances list on node + become: true + shell: >- + "{{ container_cli }}" exec -u root nova_libvirt virsh list --all | awk 'NR > 2 { printf $1 "\n"}' + register: instances_list + +- name: Get instances id list + set_fact: + vm_list: "{{ instances_list.stdout.split('\n') }}" + +# Validate all the instances using instances xml +- name: Loop on all instances and validate xml + include_tasks: check_nfv_instances.yml + when: "{{ vm_id }}" + loop: "{{ vm_list }}" + loop_control: + loop_var: vm_id + +# Prints all the validation errors if found. +- name: Validation errors + fail: + msg: "Failed NFV zero packet loss rules:\n{{ validation_msg | join('\n') }}" + when: + - validation_msg is defined + - validation_msg | length > 0 diff --git a/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/validate_dpdk_port_bandwidth.yml b/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/validate_dpdk_port_bandwidth.yml new file mode 100644 index 000000000..85dbb6961 --- /dev/null +++ b/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/validate_dpdk_port_bandwidth.yml @@ -0,0 +1,21 @@ +--- +- name: Get DPDK port bandwidth + become: true + shell: "ovs-vsctl list interface {{ dpdk_port_name }} | grep -e link_speed= -e dpdk-devargs=" + register: dpdk_port_bw + +- name: Check dpdk port matching pci or not + set_fact: + inf_bw: "{{ dpdk_port_param.split('=')[1] | replace('Gbps', '') }}" + when: "{{ (dpdk_port_bw is defined) and (pci in dpdk_port_bw.stdout) and ('link_speed=' in dpdk_port_param) }}" + loop: "{{ dpdk_port_bw.stdout.split(', ') }}" + loop_control: + loop_var: dpdk_port_param + +- name: Update invalid bandwidth validation message + set_fact: + validation_msg: "{{ validation_msg }} + {{ ['PCI bandwidth configured less than interface link speed.'] }}" + when: + - dpdk_port_bw is defined + - pci in dpdk_port_bw.stdout + - inf_bw | int > dpdk_pci_bw | int diff --git a/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/validate_instance.yml b/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/validate_instance.yml new file mode 100644 index 000000000..ebb53609b --- /dev/null +++ b/roles/check_nfv_ovsdpdk_zero_packet_loss/tasks/validate_instance.yml @@ -0,0 +1,91 @@ +--- +- name: Get instance numa node from xml string + xml: + xmlstring: "{{ instance_xml_data }}" + xpath: /domain/numatune/memory + content: attribute + attribute: nodeset + register: xml_instance_node + +- name: Get instance associated numa nodes + set_fact: + instance_numa: "{{ xml_instance_node.matches[0].memory.nodeset }}" + +# Validates the instance vcpus list. +- name: Get vcpu list from xml string + xml: + xmlstring: "{{ instance_xml_data }}" + xpath: /domain/cputune/vcpupin + content: attribute + attribute: cpuset + register: xml_vcpus + +- name: Get instance vcpus list + set_fact: + vcpus_list: |- + {{ (vcpus_list | default([])) }} + {{ [vcpu.vcpupin.cpuset] }} + loop: "{{ xml_vcpus.matches }}" + loop_control: + loop_var: vcpu + +- name: Check vcpu's aligned with DPDK NIC's NUMA + become: true + check_cpus_aligned_with_dpdk_nics: + cpus: "{{ vcpus_list | join(',') }}" + numa_node: "{{ instance_numa | int }}" + dpdk_nics_numa_info: "{{ dpdk_nics_numa_info }}" + register: valid_cpus + +- name: Check vcpu's valid or not + set_fact: + validation_msg: "{{ validation_msg }} + {{ [valid_cpus.message] }}" + when: + - not valid_cpus.valid_cpus + +# Validates instance emulatorpin threads +- name: Get emulatorpin list from xml string + xml: + xmlstring: "{{ instance_xml_data }}" + xpath: /domain/cputune/emulatorpin + content: attribute + attribute: cpuset + register: xml_emulatorpin + +- name: Check emulatorpin valid or not + set_fact: + validation_msg: "{{ validation_msg }} + {{ ['Invalid emulatorpin configured for instance ' \ + + instance_id + ': ' + emulatorpin.emulatorpin.cpuset] }}" + when: "{{ not emulatorpin.emulatorpin.cpuset in vcpus_list | list }}" + loop: "{{ xml_emulatorpin.matches }}" + loop_control: + loop_var: emulatorpin + +# Validates instance huge page size length is greater than or equal to 6. +- name: Get hugepages from xml string + xml: + xmlstring: "{{ instance_xml_data }}" + xpath: /domain/memoryBacking/hugepages/page + content: attribute + attribute: size + register: xmlhugepages + +- name: Set instance {{ vm_id }} hugepages details + set_fact: + msg: |- + Huge page size '{{ xmlhugepages.matches[0].page.size }}' + when: "{{ xmlhugepages.matches[0].page.size | length >= 6 }}" + +# Validates instance tx rx queue sizes and should be greater than or equal to 1024. +- name: Get {{ vm_id }} libvirt tx | rx queue sizes from xml string + xml: + xmlstring: "{{ instance_xml_data }}" + xpath: /domain/devices/interface/driver + content: attribute + register: xmlqueues + +- name: Set instance {{ vm_id }} devices tx and rx queue size details + set_fact: + validation_msg: "{{ validation_msg }} + {{ ['Invalid tx/rx queues configured for instance ' + + instance_id + ', tx queue size: ' + xmlqueues.matches[0].driver.tx_queue_size + ' \ + & rx queue size: ' + xmlqueues.matches[0].driver.rx_queue_size] }}" + when: "{{ xmlqueues.matches[0].driver.tx_queue_size | int < 1024 and xmlqueues.matches[0].driver.rx_queue_size | int < 1024 }}" diff --git a/tripleo_validations/tests/library/test_check_cpus_aligned_with_dpdk_nics.py b/tripleo_validations/tests/library/test_check_cpus_aligned_with_dpdk_nics.py new file mode 100644 index 000000000..9409c4057 --- /dev/null +++ b/tripleo_validations/tests/library/test_check_cpus_aligned_with_dpdk_nics.py @@ -0,0 +1,67 @@ +# 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 unittest.mock import MagicMock +from unittest.mock import patch + +import library.check_cpus_aligned_with_dpdk_nics as validation +from tripleo_validations.tests import base + + +class TestCpusAlignedWithDpdkNicsCheck(base.TestCase): + + def setUp(self): + super(TestCpusAlignedWithDpdkNicsCheck, self).setUp() + self.module = MagicMock() + + @patch('library.check_cpus_aligned_with_dpdk_nics.' + 'get_nodes_cpus_info') + def test_validate_valid_cpus_aligned_with_dpdk_nics_numa(self, mock_nodes_cpus): + mock_nodes_cpus.return_value = {0: [0, 1, 2, 3], 1: [4, 5, 6, 7]} + dpdk_nics_numa_info = [{"numa_node": 0, "mac": "mac1", "pci": "pci1"}] + cpus = "2,3" + numa_node = 0 + validation.check_cpus_aligned_with_dpdk_nics(self.module, cpus, + numa_node, dpdk_nics_numa_info) + self.module.exit_json.assert_called_with( + changed=False, + message="CPU's configured correctly: 2,3", + valid_cpus=True) + + @patch('library.check_cpus_aligned_with_dpdk_nics.' + 'get_nodes_cpus_info') + def test_validate_invalid_cpus_aligned_with_dpdk_nics_numa(self, mock_nodes_cpus): + mock_nodes_cpus.return_value = {0: [0, 1, 2, 3], 1: [4, 5, 6, 7]} + dpdk_nics_numa_info = [{"numa_node": 0, "mac": "mac1", "pci": "pci1"}] + cpus = "2,3,4,5" + numa_node = 0 + validation.check_cpus_aligned_with_dpdk_nics(self.module, cpus, + numa_node, dpdk_nics_numa_info) + self.module.fail_json.assert_called_with( + msg="CPU's are not aligned with DPDK NIC's NUMA, Invalid CPU's: 4,5") + + def test_valid_get_nodes_cpus_info(self): + lines = "# format\n0,0\n 0,2\n1,1\n1,3" + self.module.run_command.return_value = [0, lines, ""] + expected_value = {0: [0, 2], 1: [1, 3]} + result = validation.get_nodes_cpus_info(self.module) + self.assertEqual(result, expected_value) + + def test_invalid_missing_val_get_nodes_cpus_info(self): + lines = "# format\n,0\n0,2\n1,1\n1,3" + self.module.run_command.return_value = [0, lines, ""] + validation.get_nodes_cpus_info(self.module) + self.module.fail_json.assert_called_with( + msg="Unable to determine NUMA cpus") diff --git a/tripleo_validations/tests/library/test_check_other_processes_pmd_usage.py b/tripleo_validations/tests/library/test_check_other_processes_pmd_usage.py new file mode 100644 index 000000000..c69752c59 --- /dev/null +++ b/tripleo_validations/tests/library/test_check_other_processes_pmd_usage.py @@ -0,0 +1,116 @@ +# 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 unittest.mock import MagicMock +from unittest.mock import patch + +import library.check_other_processes_pmd_usage as validation +from tripleo_validations.tests import base + + +class TestOtherProcessesPmdusageCheck(base.TestCase): + + def setUp(self): + super(TestOtherProcessesPmdusageCheck, self).setUp() + self.module = MagicMock() + + @patch('library.check_other_processes_pmd_usage.' + 'check_current_process_pmd_usage') + def test_validate_no_other_processes_pmd_usage(self, mock_number_list): + mock_number_list.side_effect = [[], []] + pmd_cpus = ["2", "3"] + current_processes = "21's 4-7\n22's 4-9\n24's 4-5" + pmd_processes = "22's 4-9" + self.module.run_command.side_effect = [[0, current_processes, ""], + [0, pmd_processes, ""]] + exclude_processes_pid = ["24"] + validation.check_other_processes_pmd_usage(self.module, pmd_cpus, + exclude_processes_pid) + self.module.exit_json.assert_called_with( + messages=[], + pmd_interrupts=False) + + @patch('library.check_other_processes_pmd_usage.' + 'check_current_process_pmd_usage') + def test_validate_with_no_current_processes(self, mock_number_list): + mock_number_list.side_effect = [[], []] + pmd_cpus = ["2", "3"] + current_processes = "" + pmd_processes = "22's 4-9" + self.module.run_command.side_effect = [None, + [0, pmd_processes, ""]] + exclude_processes_pid = ["24"] + validation.check_other_processes_pmd_usage(self.module, pmd_cpus, + exclude_processes_pid) + self.module.fail_json.assert_called_with( + msg="Unable to determine current processes") + + @patch('library.check_other_processes_pmd_usage.' + 'check_current_process_pmd_usage') + def test_validate_with_no_pmd_processes(self, mock_number_list): + mock_number_list.return_value = [] + pmd_cpus = ["2", "3"] + current_processes = "21's 2-5\n22's 4-9\n24's 4-5" + pmd_processes = "" + self.module.run_command.side_effect = [[0, current_processes, ""], + None] + exclude_processes_pid = ["24"] + validation.check_other_processes_pmd_usage(self.module, pmd_cpus, + exclude_processes_pid) + self.module.fail_json.assert_called_with( + msg="Unable to determine PMD threads processes") + + @patch('library.check_other_processes_pmd_usage.' + 'check_current_process_pmd_usage') + def test_validate_other_processes_pmd_usage(self, mock_number_list): + mock_number_list.side_effect = [["pmd threads: 2,3 used in process: 21"], []] + pmd_cpus = ["2", "3"] + current_processes = "21's 2-5\n22's 4-9\n24's 4-5" + pmd_processes = "22's 4-9" + self.module.run_command.side_effect = [[0, current_processes, ""], + [0, pmd_processes, ""]] + exclude_processes_pid = ["24"] + validation.check_other_processes_pmd_usage(self.module, pmd_cpus, + exclude_processes_pid) + self.module.exit_json.assert_called_with( + messages=["pmd threads: 2,3 used in process: 21"], + pmd_interrupts=True) + + def test_check_current_process_pmd_usage(self): + pmd_cpus = ["2", "3"] + process_id = "21" + range_list = "2-5,8-11" + expected_value = ["pmd threads: 2,3 used in process: 21"] + result = validation.check_current_process_pmd_usage(self.module, pmd_cpus, + process_id, range_list) + self.assertEqual(result, expected_value) + + def test_check_current_process_pmd_usage_with_exclude_value(self): + pmd_cpus = ["2", "3"] + process_id = "21" + range_list = "2-5,8-11,^8" + expected_value = ["pmd threads: 2,3 used in process: 21"] + result = validation.check_current_process_pmd_usage(self.module, pmd_cpus, + process_id, range_list) + self.assertEqual(result, expected_value) + + def test_check_current_process_pmd_usage_with_invalid_range(self): + pmd_cpus = ["2", "3"] + process_id = "21" + range_list = "2-5,-" + result = validation.check_current_process_pmd_usage(self.module, pmd_cpus, + process_id, range_list) + self.module.fail_json.assert_called_with( + msg="Invalid number in input param 'range_list': invalid literal for int() with base 10: ''") diff --git a/tripleo_validations/tests/library/test_convert_range_to_numbers_list.py b/tripleo_validations/tests/library/test_convert_range_to_numbers_list.py new file mode 100644 index 000000000..0027412e1 --- /dev/null +++ b/tripleo_validations/tests/library/test_convert_range_to_numbers_list.py @@ -0,0 +1,45 @@ +# 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 unittest.mock import MagicMock +from unittest.mock import patch + +import library.convert_range_to_numbers_list as validation +from tripleo_validations.tests import base + + +class TestConvertRangeToNumbersList(base.TestCase): + + def setUp(self): + super(TestConvertRangeToNumbersList, self).setUp() + self.module = MagicMock() + + def test_valid_convert_range_to_numbers_list(self): + range_list = "2-5,8-11" + expected_value = [2, 3, 4, 5, 8, 9, 10, 11] + result = validation.convert_range_to_numbers_list(self.module, range_list) + self.assertEqual(result, expected_value) + + def test_valid_convert_range_to_numbers_list_with_exclude_value(self): + range_list = "2-5,8-11,^8" + expected_value = [2, 3, 4, 5, 9, 10, 11] + result = validation.convert_range_to_numbers_list(self.module, range_list) + self.assertEqual(result, expected_value) + + def test_invalid_convert_range_to_numbers_list(self): + range_list = "2-5,-" + validation.convert_range_to_numbers_list(self.module, range_list) + self.module.fail_json.assert_called_with( + msg="Invalid number in input param 'range_list': invalid literal for int() with base 10: ''") diff --git a/tripleo_validations/tests/library/test_get_dpdk_nics_numa_info.py b/tripleo_validations/tests/library/test_get_dpdk_nics_numa_info.py new file mode 100644 index 000000000..8566651bc --- /dev/null +++ b/tripleo_validations/tests/library/test_get_dpdk_nics_numa_info.py @@ -0,0 +1,48 @@ +# 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 unittest.mock import MagicMock +from unittest.mock import patch + +import library.get_dpdk_nics_numa_info as validation +from tripleo_validations.tests import base + + +class TestGetDpdkNicsNumaInfo(base.TestCase): + + def setUp(self): + super(TestGetDpdkNicsNumaInfo, self).setUp() + self.module = MagicMock() + + @patch('library.get_dpdk_nics_numa_info.' + 'get_dpdk_nics_info') + def test_get_dpdk_nics_numa_info(self, mock_dpdk_nics_info): + dpdk_nics_numa_info = [{"numa_node": 0, "mac": "mac1", "pci": "pci1"}] + mock_dpdk_nics_info.return_value = dpdk_nics_numa_info + dpdk_mapping_file = "/var/lib/os-net-config/dpdk_mapping.yaml" + validation.get_dpdk_nics_numa_info(self.module, dpdk_mapping_file) + self.module.exit_json.assert_called_with( + changed=False, + message="DPDK NIC's NUMA info", + dpdk_nics_numa_info=dpdk_nics_numa_info) + + @patch('library.get_dpdk_nics_numa_info.' + 'get_dpdk_nics_info') + def test_no_dpdk_nics_numa_info(self, mock_dpdk_nics_info): + mock_dpdk_nics_info.return_value = [] + dpdk_mapping_file = "/var/lib/os-net-config/dpdk_mapping.yaml" + validation.get_dpdk_nics_numa_info(self.module, dpdk_mapping_file) + self.module.fail_json.assert_called_with( + msg="Unable to determine DPDK NIC's NUMA info") diff --git a/tripleo_validations/tests/library/test_pmd_threads_siblings_check.py b/tripleo_validations/tests/library/test_pmd_threads_siblings_check.py new file mode 100644 index 000000000..26280e3a1 --- /dev/null +++ b/tripleo_validations/tests/library/test_pmd_threads_siblings_check.py @@ -0,0 +1,120 @@ +# Copyright 2021 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 unittest.mock import MagicMock +from unittest.mock import patch + +import library.pmd_threads_siblings_check as validation +from tripleo_validations.tests import base + + +class TestPmdThreadsSiblingsCheck(base.TestCase): + + def setUp(self): + super(TestPmdThreadsSiblingsCheck, self).setUp() + self.module = MagicMock() + + @patch('library.pmd_threads_siblings_check.' + 'get_cpus_list_from_mask_value') + @patch('library.pmd_threads_siblings_check.' + 'get_nodes_cores_info') + @patch('library.pmd_threads_siblings_check.' + 'get_thread_siblings') + def test_validate_valid_pmd_cpus(self, mock_pmd_cpus, mock_cpus, + mock_threads_siblings): + mock_pmd_cpus.return_value = ['0', '2'] + mock_cpus.return_value = ( + [0], + [{'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}]) + mock_threads_siblings.return_value = ['0', '2'] + + validation.validate_pmd_cpus(self.module, '"3"') + self.module.exit_json.assert_called_with( + changed=False, + message="PMD CPU's configured correctly: 0,2", + pmd_cpus_list=['0', '2']) + + @patch('library.pmd_threads_siblings_check.' + 'get_cpus_list_from_mask_value') + @patch('library.pmd_threads_siblings_check.' + 'get_nodes_cores_info') + @patch('library.pmd_threads_siblings_check.' + 'get_thread_siblings') + def test_validate_invalid_pmd_cpus(self, mock_pmd_cpus, mock_cpus, + mock_threads_siblings): + mock_pmd_cpus.return_value = ['0', '1'] + mock_cpus.return_value = ( + [0], + [{'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}]) + mock_threads_siblings.return_value = ['0', '2'] + + validation.validate_pmd_cpus(self.module, '"5"') + self.module.fail_json.assert_called_with( + msg="Invalid PMD CPU's, thread siblings missed") + + 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.")