Continue fixing small parts of issue #185 (#277)

* Added dataclasses to store info retrieved from k8 client calls

* Replaced few invoke commands in common_litmus

* Minor Documentation Changes

* Removed unused import and redundant variable

Signed-off-by: Shreyas Anantha Ramaprasad <ars.shreyas@gmail.com>
This commit is contained in:
Shreyas Anantha Ramaprasad
2022-07-19 05:57:17 -07:00
committed by GitHub
parent bbb66aa322
commit b01d9895fb
3 changed files with 196 additions and 53 deletions

View File

@@ -1,8 +1,10 @@
from kubernetes import client, config
from kubernetes import client, config, utils
from kubernetes.dynamic.client import DynamicClient
from kubernetes.stream import stream
from kubernetes.client.rest import ApiException
from dataclasses import dataclass
from typing import List
import logging
import kraken.invoke.command as runcommand
import sys
import re
import time
@@ -14,10 +16,17 @@ kraken_node_name = ""
def initialize_clients(kubeconfig_path):
global cli
global batch_cli
global api_client
global dyn_client
global custom_object_client
try:
config.load_kube_config(kubeconfig_path)
cli = client.CoreV1Api()
batch_cli = client.BatchV1Api()
api_client = client.ApiClient()
custom_object_client = client.CustomObjectsApi()
k8s_client = config.new_client_from_config()
dyn_client = DynamicClient(k8s_client)
except ApiException as e:
logging.error("Failed to initialize kubernetes client: %s\n" % e)
sys.exit(1)
@@ -31,8 +40,7 @@ def get_host() -> str:
def get_clusterversion_string() -> str:
"""Returns clusterversion status text on OpenShift, empty string on other distributions"""
try:
custom_objects_api = client.CustomObjectsApi()
cvs = custom_objects_api.list_cluster_custom_object(
cvs = custom_object_client.list_cluster_custom_object(
"config.openshift.io",
"v1",
"clusterversions",
@@ -400,6 +408,161 @@ def monitor_component(iteration, component_namespace):
return watch_component_status, failed_component_pods
def apply_yaml(path, namespace='default'):
"""
Apply yaml config to create Kubernetes resources
Args:
path (string)
- Path to the YAML file
namespace (string)
- Namespace to create the resource
Returns:
The object created
"""
return utils.create_from_yaml(api_client, yaml_file=path, namespace=namespace)
# Data class to hold information regarding containers in a pod
@dataclass(frozen=True, order=False)
class Container:
image: str
name: str
ready: bool
@dataclass(frozen=True, order=False)
class Pod:
"""Data class to hold information regarding a pod"""
name: str
podIP: str
namespace: str
containers: List[Container]
nodeName: str
@dataclass(frozen=True, order=False)
class LitmusChaosObject:
"""Data class to hold information regarding a custom object of litmus project"""
kind: str
group: str
namespace: str
name: str
plural: str
version: str
@dataclass(frozen=True, order=False)
class ChaosEngine(LitmusChaosObject):
"""Data class to hold information regarding a ChaosEngine object"""
engineStatus: str
expStatus: str
@dataclass(frozen=True, order=False)
class ChaosResult(LitmusChaosObject):
"""Data class to hold information regarding a ChaosResult object"""
verdict: str
failStep: str
def get_pod_info(pod_name: str, namespace: str = 'default') -> Pod:
"""
Function to retrieve information about a specific pod
in a given namespace. The kubectl command is given by:
kubectl get pods <pod_name> -n <namespace>
Args:
pod_name (string)
- Name of the pod
namespace (string)
- Namespace to look for the pod
Returns:
Data class object of type Pod with the output of the above kubectl command
in the given format
"""
response = cli.read_namespaced_pod(name=pod_name, namespace=namespace, pretty='true')
container_list = []
for container in response.status.container_statuses:
container_list.append(Container(name=container.name, image=container.image, ready=container.ready))
pod_info = Pod(name=response.metadata.name, podIP=response.status.pod_ip, namespace=response.metadata.namespace,
containers=container_list, nodeName=response.spec.node_name)
return pod_info
def get_litmus_chaos_object(kind: str, name: str, namespace: str) -> LitmusChaosObject:
"""
Function that returns an object of a custom resource typer the litmus project. Currently, only
ChaosEngine and ChaosResult is supported.
Args:
kind (string)
- The custom resource type
namespace (string)
- Namespace where the custom object is present
Returns:
Data class object of a subclass of LitmusChaosObject
"""
group = 'litmuschaos.io'
version = 'v1alpha1'
if kind.lower() == 'chaosengine':
plural = 'chaosengines'
response = custom_object_client.get_namespaced_custom_object(group=group, plural=plural,
version=version, namespace=namespace, name=name)
try:
engine_status = response['status']['engineStatus']
exp_status = response['status']['experiments'][0]['status']
except:
engine_status = 'Not Initialized'
exp_status = 'Not Initialized'
custom_object = ChaosEngine(kind='ChaosEngine', group=group, namespace=namespace, name=name,
plural=plural, version=version, engineStatus=engine_status,
expStatus=exp_status)
elif kind.lower() == 'chaosresult':
plural = 'chaosresults'
response = custom_object_client.get_namespaced_custom_object(group=group, plural=plural,
version=version, namespace=namespace, name=name)
try:
verdict = response['status']['experimentStatus']['verdict']
fail_step = response['status']['experimentStatus']['failStep']
except:
verdict = 'N/A'
fail_step = 'N/A'
custom_object = ChaosResult(kind='ChaosResult', group=group, namespace=namespace, name=name,
plural=plural, version=version, verdict=verdict, failStep=fail_step)
else:
logging.error("Invalid litmus chaos custom resource name")
custom_object = None
return custom_object
def check_if_namespace_exists(name: str):
"""
Function that checks if a namespace exists by parsing through the list of projects
Args:
name (string)
- Namespace name
Returns:
Boolean value indicating whethere the namespace exists or not
"""
v1_projects = dyn_client.resources.get(api_version='project.openshift.io/v1', kind='Project')
project_list = v1_projects.get()
return True if name in str(project_list) else False
# Find the node kraken is deployed on
# Set global kraken node to not delete
def find_kraken_node():
@@ -415,14 +578,7 @@ def find_kraken_node():
if kraken_pod_name:
# get kraken-deployment pod, find node name
try:
node_name = runcommand.invoke(
"kubectl get pods/"
+ str(kraken_pod_name)
+ ' -o jsonpath="{.spec.nodeName}"'
+ " -n"
+ str(kraken_project)
)
node_name = get_pod_info(kraken_pod_name, kraken_project).nodeName
global kraken_node_name
kraken_node_name = node_name
except Exception as e:

View File

@@ -1,4 +1,5 @@
import kraken.invoke.command as runcommand
import kraken.kubernetes.client as kubecli
import logging
import time
import sys
@@ -86,18 +87,17 @@ def deploy_all_experiments(version_string, namespace):
def wait_for_initialized(engine_name, experiment_name, namespace):
chaos_engine = runcommand.invoke(
"kubectl get chaosengines/%s -n %s -o jsonpath='{.status.engineStatus}'" % (engine_name, namespace)
)
chaos_engine = kubecli.get_litmus_chaos_object(kind='chaosengine', name=engine_name,
namespace=namespace).engineStatus
engine_status = chaos_engine.strip()
max_tries = 30
engine_counter = 0
while engine_status.lower() != "initialized":
time.sleep(10)
logging.info("Waiting for " + experiment_name + " to be initialized")
chaos_engine = runcommand.invoke(
"kubectl get chaosengines/%s -n %s -o jsonpath='{.status.engineStatus}'" % (engine_name, namespace)
)
chaos_engine = kubecli.get_litmus_chaos_object(kind='chaosengine', name=engine_name,
namespace=namespace).engineStatus
engine_status = chaos_engine.strip()
if engine_counter >= max_tries:
logging.error("Chaos engine " + experiment_name + " took longer than 5 minutes to be initialized")
@@ -117,18 +117,16 @@ def wait_for_status(engine_name, expected_status, experiment_name, namespace):
if not response:
logging.info("Chaos engine never initialized, exiting")
return False
chaos_engine = runcommand.invoke(
"kubectl get chaosengines/%s -n %s -o jsonpath='{.status.experiments[0].status}'" % (engine_name, namespace)
)
chaos_engine = kubecli.get_litmus_chaos_object(kind='chaosengine', name=engine_name,
namespace=namespace).expStatus
engine_status = chaos_engine.strip()
max_tries = 30
engine_counter = 0
while engine_status.lower() != expected_status:
time.sleep(10)
logging.info("Waiting for " + experiment_name + " to be " + expected_status)
chaos_engine = runcommand.invoke(
"kubectl get chaosengines/%s -n %s -o jsonpath='{.status.experiments[0].status}'" % (engine_name, namespace)
)
chaos_engine = kubecli.get_litmus_chaos_object(kind='chaosengine', name=engine_name,
namespace=namespace).expStatus
engine_status = chaos_engine.strip()
if engine_counter >= max_tries:
logging.error("Chaos engine " + experiment_name + " took longer than 5 minutes to be " + expected_status)
@@ -151,20 +149,14 @@ def check_experiment(engine_name, experiment_name, namespace):
else:
sys.exit(1)
chaos_result = runcommand.invoke(
"kubectl get chaosresult %s"
"-%s -n %s -o "
"jsonpath='{.status.experimentStatus.verdict}'" % (engine_name, experiment_name, namespace)
)
chaos_result = kubecli.get_litmus_chaos_object(kind='chaosresult', name=engine_name+'-'+experiment_name,
namespace=namespace).verdict
if chaos_result == "Pass":
logging.info("Engine " + str(engine_name) + " finished with status " + str(chaos_result))
return True
else:
chaos_result = runcommand.invoke(
"kubectl get chaosresult %s"
"-%s -n %s -o jsonpath="
"'{.status.experimentStatus.failStep}'" % (engine_name, experiment_name, namespace)
)
chaos_result = kubecli.get_litmus_chaos_object(kind='chaosresult', name=engine_name+'-'+experiment_name,
namespace=namespace).failStep
logging.info("Chaos scenario:" + engine_name + " failed with error: " + str(chaos_result))
logging.info(
"See 'kubectl get chaosresult %s"
@@ -176,8 +168,7 @@ def check_experiment(engine_name, experiment_name, namespace):
# Delete all chaos engines in a given namespace
def delete_chaos_experiments(namespace):
namespace_exists = runcommand.invoke("oc get project -o name | grep -c " + namespace + " | xargs")
if namespace_exists.strip() != "0":
if kubecli.check_if_namespace_exists(namespace):
chaos_exp_exists = runcommand.invoke_no_exit("kubectl get chaosexperiment")
if "returned non-zero exit status 1" not in chaos_exp_exists:
logging.info("Deleting all litmus experiments")
@@ -187,8 +178,7 @@ def delete_chaos_experiments(namespace):
# Delete all chaos engines in a given namespace
def delete_chaos(namespace):
namespace_exists = runcommand.invoke("oc get project -o name | grep -c " + namespace + " | xargs")
if namespace_exists.strip() != "0":
if kubecli.check_if_namespace_exists(namespace):
logging.info("Deleting all litmus run objects")
chaos_engine_exists = runcommand.invoke_no_exit("kubectl get chaosengine")
if "returned non-zero exit status 1" not in chaos_engine_exists:
@@ -201,8 +191,8 @@ def delete_chaos(namespace):
def uninstall_litmus(version, litmus_namespace):
namespace_exists = runcommand.invoke("oc get project -o name | grep -c " + litmus_namespace + " | xargs")
if namespace_exists.strip() != "0":
if kubecli.check_if_namespace_exists(litmus_namespace):
logging.info("Uninstalling Litmus operator")
runcommand.invoke_no_exit(
"kubectl delete -n %s -f "

View File

@@ -119,14 +119,13 @@ def container_killing_in_pod(cont_scenario):
container_pod_list = []
for pod in pods:
if type(pod) == list:
container_names = runcommand.invoke(
'kubectl get pods %s -n %s -o jsonpath="{.spec.containers[*].name}"' % (pod[0], pod[1])
).split(" ")
pod_output = kubecli.get_pod_info(pod[0], pod[1])
container_names = [container.name for container in pod_output.containers]
container_pod_list.append([pod[0], pod[1], container_names])
else:
container_names = runcommand.invoke(
'oc get pods %s -n %s -o jsonpath="{.spec.containers[*].name}"' % (pod, namespace)
).split(" ")
pod_output = kubecli.get_pod_info(pod, namespace)
container_names = [container.name for container in pod_output.containers]
container_pod_list.append([pod, namespace, container_names])
killed_count = 0
@@ -176,13 +175,11 @@ def check_failed_containers(killed_container_list, wait_time):
while timer <= wait_time:
for killed_container in killed_container_list:
# pod namespace contain name
pod_output = runcommand.invoke(
"kubectl get pods %s -n %s -o yaml" % (killed_container[0], killed_container[1])
)
pod_output_yaml = yaml.full_load(pod_output)
for statuses in pod_output_yaml["status"]["containerStatuses"]:
if statuses["name"] == killed_container[2]:
if str(statuses["ready"]).lower() == "true":
pod_output = kubecli.get_pod_info(killed_container[0], killed_container[1])
for container in pod_output.containers:
if container.name == killed_container[2]:
if container.ready:
container_ready.append(killed_container)
if len(container_ready) != 0:
for item in container_ready: