aboutsummaryrefslogtreecommitdiffstats
path: root/sdv/docker/sdvstate/internal/validator/airship/compute_check.py
diff options
context:
space:
mode:
Diffstat (limited to 'sdv/docker/sdvstate/internal/validator/airship/compute_check.py')
-rw-r--r--sdv/docker/sdvstate/internal/validator/airship/compute_check.py661
1 files changed, 661 insertions, 0 deletions
diff --git a/sdv/docker/sdvstate/internal/validator/airship/compute_check.py b/sdv/docker/sdvstate/internal/validator/airship/compute_check.py
new file mode 100644
index 0000000..a602471
--- /dev/null
+++ b/sdv/docker/sdvstate/internal/validator/airship/compute_check.py
@@ -0,0 +1,661 @@
+# Copyright 2020 University Of Delhi.
+#
+# 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.
+
+"""
+Compute Related Checks
+"""
+
+import configparser
+import json
+import re
+
+from tools.kube_utils import kube_exec, get_pod_with_labels
+from tools.conf import settings
+from .store_result import store_result
+
+
+###########
+# Checks
+###########
+
+def isolated_cores_check():
+ """
+ isolated_cores_check
+ """
+ traced_value = trace_isolated_cores()
+ required_value = required_isolated_cores()
+
+ result = {'category': 'compute',
+ 'case_name': 'isolated_cores_check',
+ 'details': {'traced_cores': traced_value,
+ 'required_cores': required_value
+ }
+ }
+
+ if is_ranges_equals(traced_value, required_value):
+ result['criteria'] = 'pass'
+ else:
+ result['criteria'] = 'fail'
+
+
+ store_result(result)
+ return result
+
+
+
+def reserved_vnf_cores_check():
+ """
+ reserved_vnf_cores_check
+ """
+ traced_value = trace_reserved_vnf_cores()
+ required_value = required_reserved_vnf_cores()
+
+ result = {'category': 'compute',
+ 'case_name': 'reserved_vnf_cores_check',
+ 'details': {'traced_cores': traced_value,
+ 'required_cores': required_value
+ }
+ }
+
+ if is_ranges_equals(traced_value, required_value):
+ result['criteria'] = 'pass'
+ else:
+ result['criteria'] = 'fail'
+
+
+ store_result(result)
+ return result
+
+
+
+def vswitch_pmd_cores_check():
+ """
+ vswitch_pmd_cores_check
+ """
+ traced_value = trace_vswitch_pmd_cores()
+ required_value = required_vswitch_pmd_cores()
+
+ result = {'category': 'compute',
+ 'case_name': 'vswitch_pmd_cores_check',
+ 'details': {'traced_cores': traced_value,
+ 'required_cores': required_value
+ }
+ }
+
+ if is_ranges_equals(traced_value, required_value):
+ result['criteria'] = 'pass'
+ else:
+ result['criteria'] = 'fail'
+
+
+ store_result(result)
+ return result
+
+
+
+def vswitch_dpdk_lcores_check():
+ """
+ vswitch_dpdk_lcores_check
+ """
+ traced_value = trace_vswitch_dpdk_lcores()
+ required_value = required_vswitch_dpdk_lcores()
+
+ result = {'category': 'compute',
+ 'case_name': 'vswitch_dpdk_lcores_check',
+ 'details': {'traced_cores': traced_value,
+ 'required_cores': required_value
+ }
+ }
+
+ if is_ranges_equals(traced_value, required_value):
+ result['criteria'] = 'pass'
+ else:
+ result['criteria'] = 'fail'
+
+
+ store_result(result)
+ return result
+
+
+
+def os_reserved_cores_check():
+ """
+ os_reserved_cores_check
+ """
+ traced_value = trace_os_reserved_cores()
+ required_value = required_os_reserved_cores()
+
+ result = {'category': 'compute',
+ 'case_name': 'os_reserved_cores_check',
+ 'details': {'traced_cores': traced_value,
+ 'required_cores': required_value
+ }
+ }
+
+ if is_ranges_equals(traced_value, required_value):
+ result['criteria'] = 'pass'
+ else:
+ result['criteria'] = 'fail'
+
+
+ store_result(result)
+ return result
+
+
+
+def nova_scheduler_filters_check():
+ """
+ nova_scheduler_filters_check
+ """
+ traced_value = trace_nova_scheduler_filters()
+ required_value = required_nova_scheduler_filters()
+
+ result = {'category': 'compute',
+ 'case_name': 'nova_scheduler_filters_check',
+ 'details': {'traced_filters': traced_value,
+ 'required_filters': required_value
+ }
+ }
+
+ if are_lists_equal(traced_value, required_value):
+ result['criteria'] = 'pass'
+ else:
+ result['criteria'] = 'fail'
+
+ store_result(result)
+ return result
+
+
+
+def cpu_allocation_ratio_check():
+ """
+ cpu_allocation_ratio_check
+ """
+ traced_value = trace_cpu_allocation_ratio()
+ required_value = required_cpu_allocation_ratio()
+
+ result = {'category': 'compute',
+ 'case_name': 'cpu_allocation_ratio_check',
+ 'details': {'traced_ratio': traced_value,
+ 'required_ratio': required_value
+ }
+ }
+
+ if traced_value == required_value:
+ result['criteria'] = 'pass'
+ else:
+ result['criteria'] = 'fail'
+
+ store_result(result)
+ return result
+
+
+
+
+
+
+
+
+###############
+# helper functions
+###############
+
+
+
+def trace_isolated_cores():
+ """
+ Trace isolated_cores from Airship deployment
+
+ :return: value traced from `isolcpus` key in `/proc/cmdline`
+ """
+ pod = get_pod_with_labels('application=nova,component=compute')
+
+ cmd = ['cat', '/proc/cmdline']
+ proc_cmd = kube_exec(pod, cmd)
+
+ for option in proc_cmd.split():
+ if 'isolcpus' in option:
+ _, isolcpus_value = split_key_value(option)
+ break
+
+ return isolcpus_value
+
+
+def required_isolated_cores():
+ """
+ Returns value of `isolated_cpus` from platform_profile used by
+ Role for worker nodes in PDF
+
+ :return: isolated_cores value expected by the PDF
+ """
+ worker_role = settings.getValue('WORKER_ROLE_NAME')
+ profile = get_platform_profile_by_role(worker_role)
+ return profile['isolated_cpus']
+
+
+
+
+
+
+def trace_reserved_vnf_cores():
+ """
+ Trace vnf_reserved_cores from Airship deployment
+
+ :return: value traced from `vcpu_pin_set` key in nova.conf
+ of actual deployment
+ """
+ try:
+ config = get_nova_conf()
+ vcpu_pin_set = config.get('DEFAULT', 'vcpu_pin_set')
+ except (configparser.NoOptionError, configparser.MissingSectionHeaderError):
+ vcpu_pin_set = ''
+
+ return vcpu_pin_set
+
+
+def required_reserved_vnf_cores():
+ """
+ Returns value of vnf_cores from platform_profile used by
+ Role for worker nodes in PDF
+
+ :return: vnf_reserverd_core value expected by the PDF
+ """
+ worker_role = settings.getValue('WORKER_ROLE_NAME')
+ profile = get_platform_profile_by_role(worker_role)
+ return profile['vnf_cores']
+
+
+
+
+
+
+def trace_vswitch_pmd_cores():
+ """
+ Trace vswitch_pmd_cores from Airship deployment
+
+ :return: value traced from `other_config:pmd-cpu-mask` in
+ openvswitchdb using ovs-vsctl
+ """
+ ovs_pod = get_pod_with_labels('application=openvswitch,component=openvswitch-vswitchd')
+
+ cmd = ['ovs-vsctl', '-t', '5', 'get', 'Open_vSwitch', '.', 'other_config']
+ response = kube_exec(ovs_pod, cmd)
+
+ # convert config str to json str
+ match = re.findall("[a-zA-Z0-9-]+=", response)
+ for key in match:
+ response = response.replace(key, '"' + key[:-1] + '":')
+ match = re.findall(":[a-zA-Z0-9-]+", response)
+ for key in match:
+ response = response.replace(key[1:], '"' + key[1:] + '"')
+
+ config = json.loads(response)
+
+ if 'pmd-cpu-mask' in config:
+ pmd_cores = hex_to_comma_list(config['pmd-cpu-mask'])
+ else:
+ pmd_cores = ''
+
+ return pmd_cores
+
+
+def required_vswitch_pmd_cores():
+ """
+ Returns value of vswitch_pmd_cores from platform_profile used by
+ Role for worker nodes in PDF
+
+ :return: vswitch_pmd_cores value expected by the PDF
+ """
+ worker_role = settings.getValue('WORKER_ROLE_NAME')
+ profile = get_platform_profile_by_role(worker_role)
+ return profile['vswitch_pmd_cores']
+
+
+
+
+
+
+def trace_vswitch_dpdk_lcores():
+ """
+ Trace vswitch_dpdk_lcores from Airship deployment
+
+ :return: value traced from `other_config:dpdk-lcore-mask` in
+ openvswitchdb using ovs-vsctl
+ """
+ ovs_pod = get_pod_with_labels('application=openvswitch,component=openvswitch-vswitchd')
+
+ cmd = ['ovs-vsctl', '-t', '5', 'get', 'Open_vSwitch', '.', 'other_config']
+ response = kube_exec(ovs_pod, cmd)
+
+ # convert config str to json str
+ match = re.findall("[a-zA-Z0-9-]+=", response)
+ for key in match:
+ response = response.replace(key, '"' + key[:-1] + '":')
+ match = re.findall(":[a-zA-Z0-9-]+", response)
+ for key in match:
+ response = response.replace(key[1:], '"' + key[1:] + '"')
+
+ config = json.loads(response)
+
+ if 'dpdk-lcore-mask' in config:
+ pmd_cores = hex_to_comma_list(config['dpdk-lcore-mask'])
+ else:
+ pmd_cores = ''
+
+ return pmd_cores
+
+
+def required_vswitch_dpdk_lcores():
+ """
+ Returns value of vswitch_dpdk_lcores from platform_profile used by
+ Role for worker nodes in PDF
+
+ :return: vswitch_dpdk_lcores value expected by the PDF
+ """
+ worker_role = settings.getValue('WORKER_ROLE_NAME')
+ profile = get_platform_profile_by_role(worker_role)
+ return profile['vswitch_dpdk_lcores']
+
+
+
+
+
+
+def trace_os_reserved_cores():
+ """
+ Trace os_reserved_cores from Airship deployment
+
+ os_reserved_cores = all_cores - (reserved_vnf_cores +
+ vswitch_pmd_cores +
+ vswitch_dpdk_lcores)
+ """
+ worker_role = settings.getValue('WORKER_ROLE_NAME')
+ all_cores = get_cores_by_role(worker_role)
+
+ reserved_vnf_cores = trace_reserved_vnf_cores()
+ vswitch_pmd_cores = trace_vswitch_pmd_cores()
+ vswitch_dpdk_lcores = trace_vswitch_dpdk_lcores()
+
+ non_os_cores = []
+ non_os_cores.extend(convert_range_to_list(reserved_vnf_cores))
+ non_os_cores.extend(convert_range_to_list(vswitch_pmd_cores))
+ non_os_cores.extend(convert_range_to_list(vswitch_dpdk_lcores))
+
+ os_reserved_cores = set(all_cores).difference(set(non_os_cores))
+
+ # return as string with comma separated value
+ return ','.join(map(str, list(os_reserved_cores)))
+
+
+def required_os_reserved_cores():
+ """
+ Returns value of os_reserved_cores from platform_profile used by
+ Role for worker nodes in PDF
+
+ :return: os_reserved_cores value expected by the PDF
+ """
+ worker_role = settings.getValue('WORKER_ROLE_NAME')
+ profile = get_platform_profile_by_role(worker_role)
+ return profile['os_reserved_cores']
+
+
+
+
+
+def trace_nova_scheduler_filters():
+ """
+ Trace scheduler_filters from Airship deployment
+
+ :return: value traced from `enabled_filters` key in nova.conf
+ of actual deployment
+ """
+ try:
+ config = get_nova_conf()
+ filters = config.get('filter_scheduler', 'enabled_filters')
+ except (configparser.NoOptionError, configparser.MissingSectionHeaderError):
+ filters = ''
+
+ filters = filters.split(',')
+ map(str.strip, filters)
+
+ return filters
+
+def required_nova_scheduler_filters():
+ """
+ Required nova scheduler_filters by the PDF
+ """
+ pdf = settings.getValue('pdf_file')
+ filters = pdf['vim_functional']['scheduler_filters']
+
+ filters = filters.split(',')
+ map(str.strip, filters)
+
+ return filters
+
+
+
+
+
+
+
+def trace_cpu_allocation_ratio():
+ """
+ Trace cpu_allocation_ratio from Airship deployment
+
+ :return: value traced from `cpu_allocation_ratio` key in nova.conf
+ of actual deployment
+ """
+ try:
+ config = get_nova_conf()
+ cpu_allocation_ratio = config.get('DEFAULT', 'cpu_allocation_ratio')
+ except (configparser.NoOptionError, configparser.MissingSectionHeaderError):
+ cpu_allocation_ratio = ''
+
+ return float(cpu_allocation_ratio)
+
+def required_cpu_allocation_ratio():
+ """
+ Required cpu_allocation_ratio by the PDF
+ """
+ pdf = settings.getValue('pdf_file')
+ cpu_allocation_ratio = pdf['vim_functional']['cpu_allocation_ratio']
+
+ return float(cpu_allocation_ratio)
+
+
+
+
+
+
+
+def get_role(role_name):
+ """
+ Searches and returns role with `role_name`
+ """
+ roles = settings.getValue('pdf_file')['roles']
+
+ for role in roles:
+ if role['name'] == role_name:
+ role_details = role
+
+ return role_details
+
+
+def get_platform_profile(profile_name):
+ """
+ Searches and returns platform_profile with `profile_name`
+ """
+ platform_profiles = settings.getValue('pdf_file')['platform_profiles']
+
+ for profile in platform_profiles:
+ if profile['profile_name'] == profile_name:
+ profile_details = profile
+
+ return profile_details
+
+def get_processor_profile(profile_name):
+ """
+ Searches and returns processor_profile with `profile_name`
+ """
+ processor_profiles = settings.getValue('pdf_file')['processor_profiles']
+
+ for profile in processor_profiles:
+ if profile['profile_name'] == profile_name:
+ profile_details = profile
+
+ return profile_details
+
+def get_platform_profile_by_role(role_name):
+ """
+ Returns platform profile details of a role
+ """
+ role = get_role(role_name)
+ profile = get_platform_profile(role['platform_profile'])
+ return profile
+
+
+def get_hardware_profile_by_role(role_name):
+ """
+ Returns hardware profile details of a role
+ """
+ role = get_role(role_name)
+
+ hardware_profiles = settings.getValue('pdf_file')['hardware_profiles']
+
+ for profile in hardware_profiles:
+ if profile['profile_name'] == role['hardware_profile']:
+ profile_details = profile
+
+ return profile_details
+
+
+def get_cores_by_role(role_name):
+ """
+ Returns cpu cores list of server hardware used in the role
+ """
+ hardware_profile = get_hardware_profile_by_role(role_name)
+ processor_profile = hardware_profile['profile_info']['processor_profile']
+ profile = get_processor_profile(processor_profile)
+
+ cpus = []
+
+ for numa in profile['profile_info']['numas']:
+ cpus.extend(convert_range_to_list(numa['cpu_set']))
+
+ return cpus
+
+
+
+
+
+
+
+def get_nova_conf():
+ """
+ Returns parsed nova.conf
+ """
+ pod = get_pod_with_labels('application=nova,component=compute')
+
+ cmd = ['cat', '/etc/nova/nova.conf']
+ response = kube_exec(pod, cmd)
+
+ config = configparser.ConfigParser()
+ config.read_string(response)
+
+ return config
+
+
+### cpu cores related helper function
+
+def convert_range_to_list(x):
+ """
+ Returns list of numbers from given range as string
+
+ e.g.: convert_range_to_list('3-5') will give [3, 4, 5]
+ """
+ # pylint: disable=C0103
+ result = []
+ for part in x.split(','):
+ if '-' in part:
+ a, b = part.split('-')
+ a, b = int(a), int(b)
+ result.extend(range(a, b + 1))
+ elif part != '':
+ a = int(part)
+ result.append(a)
+ # remove duplicates
+ result = list(dict.fromkeys(result))
+ return result
+
+
+def is_ranges_equals(range1, range2):
+ """
+ Checks whether two ranges passed as string are equal
+
+ e.g.: is_ranges_equals('2-5', '2-4,5') returns true
+ """
+ set1 = set(convert_range_to_list(range1))
+ set2 = set(convert_range_to_list(range2))
+ return set1 == set2
+
+def are_lists_equal(list1, list2):
+ """
+ Checks whether two list are identicals
+ """
+ set1 = set(list1)
+ set2 = set(list2)
+ return set1 == set2
+
+
+
+def hex_to_comma_list(hex_mask):
+ """
+ Converts CPU mask given in hex to list of cores
+ """
+ binary = bin(int(hex_mask, 16))[2:]
+ reversed_binary = binary[::-1]
+ i = 0
+ output = ""
+ for bit in reversed_binary:
+ if bit == '1':
+ output = output + str(i) + ','
+ i = i + 1
+ return output[:-1]
+
+
+def comma_list_to_hex(cpus):
+ """
+ Converts a list of cpu cores in corresponding hex value
+ of cpu-mask
+ """
+ cpu_arr = cpus.split(",")
+ binary_mask = 0
+ for cpu in cpu_arr:
+ binary_mask = binary_mask | (1 << int(cpu))
+ return format(binary_mask, '02x')
+
+
+
+def split_key_value(key_value_str, delimiter='='):
+ """
+ splits given string into key and value based on delimiter
+
+ :param key_value_str: example string `someKey=somevalue`
+ :param delimiter: default delimiter is `=`
+ :return: [ key, value]
+ """
+ key, value = key_value_str.split(delimiter)
+ key = key.strip()
+ value = value.strip()
+ return key, value