# # Copyright (c) 2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # import re import configparser import time import yaml from utils import table_parser, exceptions from utils.tis_log import LOG from utils.clients.ssh import ControllerClient from keywords import common, system_helper from consts.stx import PodStatus def exec_kube_cmd(sub_cmd, args=None, con_ssh=None, fail_ok=False, grep=None): """ Execute an kubectl cmd on given ssh client. i.e., 'kubectl ' Args: sub_cmd (str): args (None|str): con_ssh: fail_ok: grep (None|str|tuple|list) Returns (tuple): (0, ) (1, ) """ if not con_ssh: con_ssh = ControllerClient.get_active_controller() cmd = 'kubectl {} {}'.format(sub_cmd.strip(), args.strip() if args else '').strip() get_exit_code = True if cmd.endswith(';echo'): get_exit_code = False if grep: if isinstance(grep, str): grep = (grep,) for grep_str in grep: if '-v ' not in grep_str and '-e ' in grep_str and 'NAME' not in \ grep_str: grep_str += ' -e NAME' cmd += ' | grep --color=never {}'.format(grep_str) code, out = con_ssh.exec_cmd(cmd, fail_ok=True, get_exit_code=get_exit_code) if code <= 0: return 0, out if fail_ok: return 1, out else: raise exceptions.KubeCmdError('CMD: {} Output: {}'.format(cmd, out)) def __get_resource_tables(namespace=None, all_namespaces=None, resource_types=None, resource_names=None, labels=None, field_selectors=None, wide=True, con_ssh=None, fail_ok=False, grep=None): if not resource_types: resource_types = '' elif isinstance(resource_types, (list, tuple)): resource_types = ','.join(resource_types) resources = resource_types if resource_names: if ',' in resource_types: raise ValueError( "At most 1 resource_types can be specified if resource_names " "are provided.") if all_namespaces and not namespace: raise ValueError( "all_namespaces is disallowed when resource_names are provided") if isinstance(resource_names, (list, tuple)): resource_names = ' '.join(resource_names) resources = '{} {}'.format(resources, resource_names) args_dict = { '-n': namespace, '--all-namespaces': True if all_namespaces and not namespace else None, '-l': labels, '--field-selector': field_selectors, '-o': 'wide' if wide else None } args = '{} {}'.format(resources, common.parse_args(args_dict, repeat_arg=False, vals_sep=',')) code, out = exec_kube_cmd(sub_cmd='get', args=args, con_ssh=con_ssh, fail_ok=fail_ok, grep=grep) if code > 0: return code, out tables = table_parser.tables_kube(out) return code, tables def get_unhealthy_pods(field='NAME', namespace=None, all_namespaces=True, pod_names=None, labels=None, exclude=False, strict=True, con_ssh=None, **kwargs): """ Get pods that are not Completed and not Running Args: namespace (str|None): all_namespaces: (bool|None) pod_names (str|list|tuple|None): full names of pods to check labels (str|dict|None): field (str|tuple|list): exclude: strict: con_ssh: Returns (list): """ field_selector = 'status.phase!=Running,status.phase!=Succeeded' return get_pods(field=field, namespace=namespace, all_namespaces=all_namespaces, pod_names=pod_names, labels=labels, field_selectors=field_selector, exclude=exclude, strict=strict, con_ssh=con_ssh, **kwargs) def get_pods(field='NAME', namespace=None, all_namespaces=False, pod_names=None, labels=None, field_selectors=None, fail_ok=False, con_ssh=None, exclude=False, strict=True, **kwargs): """ Get pods values for specified field(s) Args: field (str|tuple|list): return values for given header(s) namespace (str|None): when None, --all-namespaces will be used. all_namespaces (bool|none): pod_names (str|list|tuple): Full pod name(s). When specified, labels and field_selectors will be ignored. labels (str|dict|None|list|tuple): label selectors. Used only if full_names are unspecified. e.g., application=nova,component=compute field_selectors (str): Used only if full_names are unspecified. e.g., , 'spec.nodeName=controller-0,status.phase!=Running, status.phase!=Succeeded' exclude (bool): strict (bool): con_ssh: fail_ok (bool) **kwargs: table filters for post processing output to return filtered values Returns (list): examples: Input: field=('NAME', 'STATUS') OR 'Name' labels='application=nova,component=compute', field_selector='spec.nodeName=compute-0' Output: [('nova-compute-compute-0-xdjkds', 'Running')] OR [ 'nova-compute-compute-0-xdjkds'] """ return get_resources(field=field, namespace=namespace, all_namespaces=all_namespaces, resource_type='pod', resource_names=pod_names, labels=labels, field_selectors=field_selectors, con_ssh=con_ssh, fail_ok=fail_ok, exclude=exclude, strict=strict, **kwargs) def get_resources(field='NAME', namespace=None, all_namespaces=None, resource_names=None, resource_type='pod', labels=None, field_selectors=None, con_ssh=None, fail_ok=False, grep=None, exclude=False, strict=True, **kwargs): """ Get resources values for single resource type via kubectl get Args: field (str|tuple|list) namespace (None|str): e.g., kube-system, openstack, default. all_namespaces (bool|None): used only when namespace is unspecified resource_names (str|None|list|tuple): e.g., calico-typha resource_type (str): e.g., "deployments.apps", "pod", "service" labels (dict|str|list|tuple): Used only when resource_names are unspecified field_selectors (dict|str|list|tuple): Used only when resource_names are unspecified con_ssh: fail_ok: grep (str|None): grep on cmd output exclude strict **kwargs: table filters for post processing return values Returns (list): key is the name prefix, e.g., service, default, deployment.apps, replicaset.apps value is a list. Each item is a dict rep for a row with lowercase keys. e.g., [{'name': 'cinder-api', 'age': '4d19h', ... }, ...] """ name_filter = None if resource_names and ( (all_namespaces and not namespace) or field_selectors or labels): name_filter = {'name': resource_names} resource_names = None code, tables = __get_resource_tables(namespace=namespace, all_namespaces=all_namespaces, resource_types=resource_type, resource_names=resource_names, labels=labels, field_selectors=field_selectors, con_ssh=con_ssh, fail_ok=fail_ok, grep=grep) if code > 0: output = tables if 'NAME ' not in output: # no resource returned return [] output = output.split('\nError from server')[0] tables = table_parser.tables_kube(output) final_table = tables[0] if len(tables) > 1: combined_values = final_table['values'] column_count = len(combined_values) for table_ in tables[1:]: table_values = table_['values'] combined_values = [combined_values[i] + table_values[i] for i in range(column_count)] final_table['values'] = combined_values if name_filter: final_table = table_parser.filter_table(final_table, **name_filter) return table_parser.get_multi_values(final_table, fields=field, zip_values=True, strict=strict, exclude=exclude, **kwargs) def apply_pod(file_path, pod_name, namespace=None, recursive=None, select_all=None, labels=None, con_ssh=None, fail_ok=False, check_both_controllers=True): """ Apply a pod from given file via kubectl apply Args: file_path (str): pod_name (str): namespace (None|str): recursive (None|bool): select_all (None|bool): labels (dict|str|list|tuple|None): key value pairs con_ssh: fail_ok: check_both_controllers (bool): Returns (tuple): (0, (dict)) (1, ) (2, ) # pod is not running after apply (3, ) # pod if not running on the other controller after apply """ arg_dict = { '--all': select_all, '-l': labels, '--recursive': recursive, } arg_str = common.parse_args(args_dict=arg_dict, vals_sep=',') arg_str += ' -f {}'.format(file_path) if not con_ssh: con_ssh = ControllerClient.get_active_controller() code, output = exec_kube_cmd(sub_cmd='apply', args=arg_str, con_ssh=con_ssh, fail_ok=fail_ok) if code > 0: return 1, output LOG.info("Check pod is running on current host") res = wait_for_pods_status(pod_names=pod_name, namespace=namespace, status=PodStatus.RUNNING, con_ssh=con_ssh, fail_ok=fail_ok) if not res: return 2, "Pod {} is not running after apply on active " \ "controller".format(pod_name) if check_both_controllers and not system_helper.is_aio_simplex( con_ssh=con_ssh): LOG.info("Check pod is running on the other controller as well") con_name = 'controller-1' if con_ssh.get_hostname() == 'controller-0' \ else 'controller-0' from keywords import host_helper with host_helper.ssh_to_host(hostname=con_name, con_ssh=con_ssh) as other_con: res, pods_info = wait_for_pods_status(pod_names=pod_name, namespace=namespace, con_ssh=other_con, fail_ok=fail_ok) if not res: return 3, "Pod {} is not running after apply on standby " \ "controller".format(pod_name) LOG.info("{} pod is successfully applied and running".format(pod_name)) return 0, pod_name def wait_for_pods_status(pod_names=None, partial_names=None, labels=None, namespace=None, status=PodStatus.RUNNING, timeout=120, check_interval=3, con_ssh=None, fail_ok=False, strict=False, **kwargs): """ Wait for pod(s) to reach given status via kubectl get pod Args: pod_names (str|list|tuple): full name of the pods partial_names (str|list|tuple): Used only if pod_names are not provided labels (str|list|tuple|dict|None): Used only if pod_names are not provided namespace (None|str): status (str|None|list): None means any state as long as pod exists. timeout: check_interval: con_ssh: fail_ok: strict (bool): Returns (tuple): (True, ) # actual_pods_info is a dict with pod_name as key, and pod_info(dict) as value (False, ) """ pods_to_check = [] if pod_names: if isinstance(pod_names, str): pod_names = [pod_names] else: pod_names = list(pod_names) labels = partial_names = None pods_to_check = list(pod_names) elif partial_names: if isinstance(partial_names, str): partial_names = [partial_names] else: partial_names = list(partial_names) kwargs['NAME'] = partial_names pods_to_check = list(partial_names) actual_status = {} end_time = time.time() + timeout while time.time() < end_time: pod_full_names = pods_to_check if pod_names else None pods_values = get_pods(pod_names=pod_full_names, field=('NAME', 'status'), namespace=namespace, labels=labels, strict=strict, fail_ok=True, con_ssh=con_ssh, **kwargs) if not pods_values: # No pods returned, continue to check. time.sleep(check_interval) continue continue_check = False # This is used when only labels are provided for pod_info in pods_values: pod_name, pod_status = pod_info actual_status[pod_name] = pod_status if status and pod_status not in status: # Status not as expected, continue to wait continue_check = True if partial_names: # In this case, there might be multiple pods that matches # 1 partial name, so the partial name that # matches current pod could have been removed if there # was one other pod that also matched the name # had reached the desired state. In this case, we will # add the partial name back to check list for partial_name in partial_names: if partial_name in pod_name and partial_name not in \ pods_to_check: pods_to_check.append(partial_name) break else: # Criteria met for current pod, remove it from check_list if pod_names: pods_to_check.remove(pod_name) elif partial_names: for partial_name in partial_names: if partial_name in pod_name and partial_name in \ pods_to_check: pods_to_check.remove(partial_name) break if not pods_to_check and not continue_check: return True, actual_status time.sleep(check_interval) name_str = 'Names: {}'.format(pods_to_check) if pods_to_check else '' label_str = 'Labels: {}'.format(labels) if labels else '' criteria = '{} {}'.format(name_str, label_str).strip() msg = "Pods did not reach expected status within {}s. Criteria not met: " \ "{}. Actual info: {}".format(timeout, criteria, actual_status) if fail_ok: LOG.info(msg) return False, actual_status raise exceptions.KubeError(msg) def wait_for_resources_gone(resource_names=None, resource_type='pod', namespace=None, timeout=120, check_interval=3, con_ssh=None, fail_ok=False, strict=True, exclude=False, **kwargs): """ Wait for pod(s) to be gone from kubectl get Args: resource_names (str|list|tuple): full name of a pod resource_type (str): namespace (None|str): timeout: check_interval: con_ssh: fail_ok: strict (bool): exclude **kwargs Returns (tuple): (True, None) (False, ) # actual_pods_info is a dict with pod_name as key, and pod_info(dict) as value """ end_time = time.time() + timeout resources_to_check = resource_names while time.time() < end_time: resources_to_check = get_resources(resource_names=resources_to_check, namespace=namespace, resource_type=resource_type, con_ssh=con_ssh, fail_ok=True, strict=strict, exclude=exclude, **kwargs) if not resources_to_check: return True, resources_to_check time.sleep(check_interval) msg = 'Resources did not disappear in {} seconds. Remaining resources: ' \ '{}, namespace: {}'.format(timeout, resources_to_check, namespace) if fail_ok: LOG.info(msg) return False, resources_to_check raise exceptions.KubeError(msg) def delete_resources(resource_names=None, select_all=None, resource_types='pod', namespace=None, recursive=None, labels=None, con_ssh=None, fail_ok=False, post_check=True, check_both_controllers=True): """ Delete pods via kubectl delete Args: resource_names (None|str|list|tuple): select_all (None|bool): resource_types (str|list|tuple): namespace (None|str): recursive (bool): labels (None|dict): con_ssh: fail_ok: post_check (bool): Whether to check if resources are gone after deletion check_both_controllers (bool): Returns (tuple): (0, None) # pods successfully deleted (1, ) (2, (list of dict)) # pod(s) still exist in kubectl after deletion (3, (list of dict)) # pod(s) still exist on the other controller """ arg_dict = { '--all': select_all, '-l': labels, '--recursive': recursive, } arg_str = common.parse_args(args_dict=arg_dict, vals_sep=',') if resource_types: if isinstance(resource_types, str): resource_types = [resource_types] arg_str = '{} {}'.format(','.join(resource_types), arg_str).strip() if resource_names: if isinstance(resource_names, str): resource_names = [resource_names] arg_str = '{} {}'.format(arg_str, ' '.join(resource_names)) if not con_ssh: con_ssh = ControllerClient.get_active_controller() code, output = exec_kube_cmd(sub_cmd='delete', args=arg_str, con_ssh=con_ssh, fail_ok=fail_ok) if code > 0: return 1, output if post_check: def __wait_for_resources_gone(ssh_client): final_remaining = [] if resource_types: for resource_type in resource_types: res, remaining_res = wait_for_resources_gone( resource_names=resource_names, resource_type=resource_type, namespace=namespace, con_ssh=ssh_client, fail_ok=fail_ok) if not res: final_remaining += remaining_res else: res, final_remaining = wait_for_resources_gone( resource_names=resource_names, namespace=namespace, con_ssh=ssh_client, fail_ok=fail_ok) return final_remaining LOG.info("Check pod is not running on current host") remaining = __wait_for_resources_gone(con_ssh) if remaining: return 2, remaining if check_both_controllers and not system_helper.is_aio_simplex( con_ssh=con_ssh): LOG.info("Check pod is running on the other controller as well") con_name = 'controller-1' if \ con_ssh.get_hostname() == 'controller-0' else 'controller-0' from keywords import host_helper with host_helper.ssh_to_host(hostname=con_name, con_ssh=con_ssh) as other_con: remaining = __wait_for_resources_gone(other_con) if remaining: return 3, remaining LOG.info("{} are successfully removed.".format(resource_names)) return 0, None def get_pods_info_yaml(type_names='pods', namespace=None, con_ssh=None, fail_ok=False): """ pods info parsed from yaml output of kubectl get cmd Args: namespace (None|str): e.g., kube-system, openstack, default. If set to 'all', use --all-namespaces. type_names (None|list|tuple|str): e.g., ("deployments.apps", "services/calico-typha") con_ssh: fail_ok: Returns (list): each item is a pod info dictionary """ if isinstance(type_names, (list, tuple)): type_names = ','.join(type_names) args = type_names if namespace == 'all': args += ' --all-namespaces' elif namespace: args += ' --namespace={}'.format(namespace) args += ' -o yaml' code, out = exec_kube_cmd(sub_cmd='get', args=args, con_ssh=con_ssh, fail_ok=fail_ok) if code > 0: return [] try: pods_info = yaml.load(out) except yaml.YAMLError: LOG.warning('Output is not yaml') return [] pods_info = pods_info.get('items', [pods_info]) return pods_info def get_pod_value_jsonpath(type_name, jsonpath, namespace=None, con_ssh=None): """ Get value for specified pod with jsonpath Args: type_name (str): e.g., 'service/kubernetes' jsonpath (str): e.g., '{.spec.ports[0].targetPort}' namespace (str|None): e.g., 'kube-system' con_ssh: Returns (str): """ args = '{} -o jsonpath="{}"'.format(type_name, jsonpath) if namespace: args += ' --namespace {}'.format(namespace) args += ';echo' value = exec_kube_cmd('get', args, con_ssh=con_ssh)[1] return value def expose_the_service(deployment_name, type, service_name, namespace=None, con_ssh=None): """ Exposes the service of a deployment Args: deployment_name (str): name of deployment type (str): "LoadBalancer" or "NodePort" service_name(str): service name namespace (str|None): e.g., 'kube-system' con_ssh: Returns (str): """ args = '{} --type={} --name={}'.format(deployment_name, type, service_name) if namespace: args += ' --namespace {}'.format(namespace) return exec_kube_cmd('expose deployment', args, con_ssh=con_ssh) def get_nodes(hosts=None, status=None, field='STATUS', exclude=False, con_ssh=None, fail_ok=False): """ Get nodes values via 'kubectl get nodes' Args: hosts (None|str|list|tuple): table filter status (None|str|list|tuple): table filter field (str|list|tuple): any header of the nodes table exclude (bool): whether to exclude rows with given criteria con_ssh: fail_ok: Returns (None|list): None if cmd failed. """ code, output = exec_kube_cmd('get', args='nodes', con_ssh=con_ssh, fail_ok=fail_ok) if code > 0: return None table_ = table_parser.table_kube(output) if hosts or status: table_ = table_parser.filter_table(table_, exclude=exclude, **{'NAME': hosts, 'STATUS': status}) return table_parser.get_multi_values(table_, field) def wait_for_nodes_ready(hosts=None, timeout=120, check_interval=5, con_ssh=None, fail_ok=False): """ Wait for hosts in ready state via kubectl get nodes Args: hosts (None|list|str|tuple): Wait for all hosts ready if None is specified timeout: check_interval: con_ssh: fail_ok: Returns (tuple): (True, None) (False, (list)) """ end_time = time.time() + timeout nodes_not_ready = None while time.time() < end_time: nodes_not_ready = get_nodes(status='Ready', field='NAME', exclude=True, con_ssh=con_ssh, fail_ok=True) if nodes_not_ready and hosts: nodes_not_ready = list(set(nodes_not_ready) & set(hosts)) if nodes_not_ready: LOG.info('{} not ready yet'.format(nodes_not_ready)) elif nodes_not_ready is not None: LOG.info("All nodes are ready{}".format( ': {}'.format(hosts) if hosts else '')) return True, None time.sleep(check_interval) msg = '{} are not ready within {}s'.format(nodes_not_ready, timeout) LOG.warning(msg) if fail_ok: return False, nodes_not_ready else: raise exceptions.KubeError(msg) def exec_cmd_in_container(cmd, pod, namespace=None, container_name=None, stdin=None, tty=None, con_ssh=None, fail_ok=False): """ Execute given cmd in given pod via kubectl exec Args: cmd: pod: namespace: container_name: stdin: tty: con_ssh: fail_ok: Returns (tuple): (0, ) (1, ) """ args = pod if namespace: args += ' -n {}'.format(namespace) if container_name: args += ' -c {}'.format(container_name) if stdin: args += ' -i' if tty: args += ' -t' args += ' -- {}'.format(cmd) code, output = exec_kube_cmd(sub_cmd='exec', args=args, con_ssh=con_ssh, fail_ok=fail_ok) return code, output def wait_for_pods_healthy(pod_names=None, namespace=None, all_namespaces=True, labels=None, timeout=300, check_interval=5, con_ssh=None, fail_ok=False, exclude=False, strict=False, **kwargs): """ Wait for pods ready Args: pod_names (list|tuple|str|None): full name of pod(s) namespace (str|None): all_namespaces (bool|None) labels (str|dict|list|tuple|None): timeout: check_interval: con_ssh: fail_ok: exclude (bool) strict (bool): strict applies to node and name matching if given **kwargs Returns (tuple): """ LOG.info("Wait for pods ready..") if not pod_names: pod_names = None elif isinstance(pod_names, str): pod_names = [pod_names] bad_pods = None end_time = time.time() + timeout while time.time() < end_time: bad_pods_info = get_unhealthy_pods(labels=labels, field=('NAME', 'STATUS'), namespace=namespace, all_namespaces=all_namespaces, con_ssh=con_ssh, exclude=exclude, strict=strict, **kwargs) bad_pods = {pod_info[0]: pod_info[1] for pod_info in bad_pods_info if (not pod_names or pod_info[0] in pod_names)} if not bad_pods: LOG.info("Pods are Completed or Running.") if pod_names: pod_names = [pod for pod in pod_names if not re.search('audit-|init-', pod)] if not pod_names: return True is_ready = wait_for_running_pods_ready( pod_names=pod_names, namespace=namespace, all_namespaces=all_namespaces, labels=labels, timeout=int(end_time - time.time()), strict=strict, con_ssh=con_ssh, fail_ok=fail_ok, **kwargs) return is_ready time.sleep(check_interval) msg = 'Some pods are not Running or Completed: {}'.format(bad_pods) LOG.warning(msg) if fail_ok: return False dump_pods_info(con_ssh=con_ssh) raise exceptions.KubeError(msg) def wait_for_running_pods_ready(pod_names=None, namespace=None, all_namespaces=False, labels=None, timeout=300, fail_ok=False, con_ssh=None, exclude=False, strict=False, **kwargs): """ Wait for Running pods to be Ready, such as 1/1, 3/3 Args: pod_names: namespace: all_namespaces: labels: timeout: fail_ok: con_ssh: exclude: strict: **kwargs: Returns (bool): """ unready_pods = get_unready_running_pods(namespace=namespace, all_namespaces=all_namespaces, pod_names=pod_names, labels=labels, exclude=exclude, strict=strict, con_ssh=con_ssh, **kwargs) if not unready_pods: return True end_time = time.time() + timeout while time.time() < end_time: pods_info = get_pods(field=('NAME', 'READY'), namespace=namespace, all_namespaces=all_namespaces, pod_names=unready_pods, con_ssh=con_ssh) for pod_info in pods_info: pod_name, pod_ready = pod_info ready_count, total_count = pod_ready.split('/') if ready_count == total_count: unready_pods.remove(pod_name) if not unready_pods: return True msg = "Some pods are not ready within {}s: {}".format(timeout, unready_pods) LOG.warning(msg) if fail_ok: return False raise exceptions.KubeError(msg) def get_unready_running_pods(pod_names=None, namespace=None, all_namespaces=False, labels=None, con_ssh=None, exclude=False, strict=False, **kwargs): """ Get Running pods that are not yet Ready. Args: pod_names: namespace: all_namespaces: labels: con_ssh: exclude: strict: **kwargs: Returns (list): pod names """ # field_selector does not work with pod_names, determine whether to use # field_selector or do post filtering instead # If field_selector is specified, the underlying get_pods function will # use pod_names for post filtering if exclude or labels or (not namespace and all_namespaces) or not pod_names: field_selector = 'status.phase=Running' else: field_selector = None kwargs['Status'] = 'Running' pods_running = get_pods(field=('NAME', 'READY'), namespace=namespace, all_namespaces=all_namespaces, pod_names=pod_names, labels=labels, field_selectors=field_selector, grep='-v 1/1', exclude=exclude, strict=strict, con_ssh=con_ssh, fail_ok=True, **kwargs) not_ready_pods = [] for pod_info in pods_running: pod_name, pod_ready = pod_info ready_count, total_count = pod_ready.split('/') if ready_count != total_count: not_ready_pods.append(pod_name) return not_ready_pods def wait_for_openstack_pods_status(pod_names=None, application=None, component=None, status=PodStatus.RUNNING, con_ssh=None, timeout=60, check_interval=5, fail_ok=False): """ Wait for openstack pods to be in Completed or Running state Args: pod_names (str|tuple|list|None): application (str|None): only used when pod_names are not provided component (str|None): only used when pod_names are not provided status (str|tuple|list|None): con_ssh: timeout: check_interval: fail_ok: Returns: """ if not pod_names and not application and not component: raise ValueError( 'pod_names, or application and component have to be provided to ' 'filter out pods') labels = None if not pod_names: labels = [] if application: labels.append('application={}'.format(application)) if component: labels.append('component={}'.format(component)) return wait_for_pods_status(pod_names=pod_names, labels=labels, status=status, namespace='openstack', con_ssh=con_ssh, check_interval=check_interval, timeout=timeout, fail_ok=fail_ok) def get_pod_logs(pod_name, namespace='openstack', grep_pattern=None, tail_count=10, strict=False, fail_ok=False, con_ssh=None): """ Get logs for given pod via kubectl logs cmd Args: pod_name (str): partial or full pod_name. If full name, set strict to True. namespace (str|None): grep_pattern (str|None): tail_count (int|None): strict (bool): fail_ok: con_ssh: Returns (str): """ if pod_name and not strict: grep = '-E -i "{}|NAME"'.format(pod_name) pod_name = get_resources(namespace='openstack', resource_type='pod', con_ssh=con_ssh, rtn_list=True, grep=grep, fail_ok=fail_ok)[0].get('name') namespace = '-n {} '.format(namespace) if namespace else '' grep = '' if grep_pattern: if isinstance(grep_pattern, str): grep_pattern = (grep_pattern,) grep = ''.join( [' | grep --color=never {}'.format(grep_str) for grep_str in grep_pattern]) tail = ' | tail -n {}'.format(tail_count) if tail_count else '' args = '{}{}{}{}'.format(namespace, pod_name, grep, tail) code, output = exec_kube_cmd(sub_cmd='logs', args=args, con_ssh=con_ssh) if not output and not fail_ok: raise exceptions.KubeError( "No kubectl logs found with args: {}".format(args)) return output def dump_pods_info(con_ssh=None): """ Dump pods info for debugging purpose. Args: con_ssh: Returns: """ LOG.info('------- Dump pods info --------') exec_kube_cmd('get pods', '--all-namespaces -o wide | grep -v -e Running -e Completed', con_ssh=con_ssh, fail_ok=True) exec_kube_cmd( 'get pods', "--all-namespaces -o wide | grep -v -e Running -e Completed " "-e NAMESPACE | awk " + """'{system("kubectl describe pods -n "$1" "$2)}'""""", con_ssh=con_ssh, fail_ok=True) def get_openstack_pods(field='Name', namespace='openstack', application=None, component=None, pod_names=None, extra_labels=None, field_selectors=None, exclude_label=False, fail_ok=False, con_ssh=None, strict=True, exclude=False, **kwargs): """ Get openstack pods via kubectl get pods Note that pod labels can be found via kubectl get pods -n --show-labels Args: field (str|list|tuple): namespace: application (str|None): label: application component (str|None): label: component pod_names extra_labels (str|None): field_selectors (str|list|tuple|dict|None): exclude_label fail_ok: con_ssh: exclude: strict: **kwargs: Returns (list): """ if pod_names: labels = None else: connector = '!=' if exclude_label else '=' labels = [] if application: labels.append('application{}{}'.format(connector, application)) if component: labels.append('component{}{}'.format(connector, component)) if extra_labels: labels.append(extra_labels) labels = ','.join(labels) pods = get_pods(pod_names=pod_names, field=field, namespace=namespace, labels=labels, fail_ok=fail_ok, field_selectors=field_selectors, strict=strict, exclude=exclude, con_ssh=con_ssh, **kwargs) if not pods: msg = "No pods found for namespace - {} with selectors: {}".format( namespace, labels) LOG.info(msg) if not fail_ok: raise exceptions.KubeError(msg) return pods def get_openstack_configs(conf_file, configs=None, node=None, pods=None, label_component=None, label_app=None, fail_ok=False, con_ssh=None): """ Get config values for openstack pods with given chart Args: pods (str|list|tuple): openstack pod name(s) label_app (str|None): e.g., nova, neutron, panko, ... label_component (str|None): e.g., api, compute, etc. conf_file (str): config file path inside the filtered openstack container, e.g., /etc/nova/nova.conf configs (dict): {(str): (str|list|tuple), : ..} e.g., {'database': 'event_time_to_live'} node (str|None) fail_ok: con_ssh: Returns (dict): {(str): (dict), ... } """ if not pods and not (label_component and label_app): raise ValueError('Either pods, or label_component and label_app ' 'have to be specified to locate the containers') if not pods: pods = get_openstack_pods(component=label_component, application=label_app, fail_ok=fail_ok, node=node, con_ssh=con_ssh) elif isinstance(pods, str): pods = (pods,) LOG.info('Getting {} {} values from openstack pods: {}'.format(conf_file, configs, pods)) cmd = 'cat {}'.format(conf_file) if configs: all_fields = [] section_filter = r'$1 ~ /^\[.*\]/' for fields in configs.values(): if isinstance(fields, str): all_fields.append(fields) elif isinstance(fields, (tuple, list)): all_fields += list(fields) fields_filter = '|| '.join( ['$1 ~ /^{}/'.format(field) for field in set(all_fields)]) cmd += r" | awk '{{ if ( {} || {}) print }}' | grep --color=never " \ r"--group-separator='' -B 1 -v '\[.*\]'". \ format(section_filter, fields_filter) config_values = {} for pod in pods: code, output = exec_cmd_in_container(cmd, pod=pod, namespace='openstack', con_ssh=con_ssh, fail_ok=fail_ok) if code > 0: config_values[pod] = {} continue # Remove irrelevant string at beginning of the output output = "[{}".format( re.split(r'\n\[', r'\n{}'.format(output), maxsplit=1)[-1]) settings = configparser.ConfigParser() settings.read_string(output) config_values[pod] = settings return config_values