mirror of
https://github.com/krkn-chaos/krkn.git
synced 2026-02-14 18:10:00 +00:00
* Created new file for dataclasses and replaced kubectl pvc cli calls * Added checks for existence of pod/pvc * Modified command to get pvc capacity Removed redundant function call
This commit is contained in:
committed by
GitHub
parent
5ab16baafa
commit
d5615ac470
@@ -2,8 +2,7 @@ from kubernetes import client, config, utils, watch
|
||||
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
|
||||
from kraken.kubernetes.resources import *
|
||||
import logging
|
||||
import sys
|
||||
import re
|
||||
@@ -411,82 +410,62 @@ def apply_yaml(path, namespace='default'):
|
||||
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:
|
||||
def get_pod_info(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>
|
||||
kubectl get pods <name> -n <namespace>
|
||||
|
||||
Args:
|
||||
pod_name (string)
|
||||
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
|
||||
- Data class object of type Pod with the output of the above kubectl command
|
||||
in the given format if the pod exists
|
||||
- Returns None if the pod doesn't exist
|
||||
"""
|
||||
pod_exists = check_if_pod_exists(name=name, namespace=namespace)
|
||||
if pod_exists:
|
||||
response = cli.read_namespaced_pod(name=name, namespace=namespace, pretty='true')
|
||||
container_list = []
|
||||
|
||||
response = cli.read_namespaced_pod(name=pod_name, namespace=namespace, pretty='true')
|
||||
container_list = []
|
||||
# Create a list of containers present in the pod
|
||||
for container in response.spec.containers:
|
||||
volume_mount_list = []
|
||||
for volume_mount in container.volume_mounts:
|
||||
volume_mount_list.append(VolumeMount(name=volume_mount.name, mountPath=volume_mount.mount_path))
|
||||
container_list.append(Container(name=container.name, image=container.image, volumeMounts= volume_mount_list))
|
||||
|
||||
for container in response.status.container_statuses:
|
||||
container_list.append(Container(name=container.name, image=container.image, ready=container.ready))
|
||||
for i, container in enumerate(response.status.container_statuses):
|
||||
container_list[i].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
|
||||
# Create a list of volumes associated with the pod
|
||||
volume_list = []
|
||||
for volume in response.spec.volumes:
|
||||
volume_name = volume.name
|
||||
pvc_name = volume.persistent_volume_claim.claim_name if volume.persistent_volume_claim is not None else None
|
||||
volume_list.append(Volume(name=volume_name, pvcName=pvc_name))
|
||||
|
||||
# Create the Pod data class object
|
||||
pod_info = Pod(name=response.metadata.name, podIP=response.status.pod_ip, namespace=response.metadata.namespace,
|
||||
containers=container_list, nodeName=response.spec.node_name, volumes = volume_list)
|
||||
return pod_info
|
||||
else:
|
||||
logging.error(
|
||||
"Pod '%s' doesn't exist in namespace '%s'" % (str(name), str(namespace))
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
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.
|
||||
Function that returns an object of a custom resource type of the litmus project. Currently, only
|
||||
ChaosEngine and ChaosResult objects are supported.
|
||||
|
||||
Args:
|
||||
kind (string)
|
||||
@@ -533,7 +512,7 @@ def get_litmus_chaos_object(kind: str, name: str, namespace: str) -> LitmusChaos
|
||||
return custom_object
|
||||
|
||||
|
||||
def check_if_namespace_exists(name: str):
|
||||
def check_if_namespace_exists(name: str) -> bool:
|
||||
"""
|
||||
Function that checks if a namespace exists by parsing through the list of projects
|
||||
Args:
|
||||
@@ -541,7 +520,7 @@ def check_if_namespace_exists(name: str):
|
||||
- Namespace name
|
||||
|
||||
Returns:
|
||||
Boolean value indicating whethere the namespace exists or not
|
||||
Boolean value indicating whether the namespace exists or not
|
||||
"""
|
||||
|
||||
v1_projects = dyn_client.resources.get(api_version='project.openshift.io/v1', kind='Project')
|
||||
@@ -549,6 +528,97 @@ def check_if_namespace_exists(name: str):
|
||||
return True if name in str(project_list) else False
|
||||
|
||||
|
||||
def check_if_pod_exists(name: str, namespace: str) -> bool:
|
||||
"""
|
||||
Function that checks if a pod exists in the given namespace
|
||||
Args:
|
||||
name (string)
|
||||
- Pod name
|
||||
|
||||
namespace (string)
|
||||
- Namespace name
|
||||
|
||||
Returns:
|
||||
Boolean value indicating whether the pod exists or not
|
||||
"""
|
||||
|
||||
namespace_exists = check_if_namespace_exists(namespace)
|
||||
if namespace_exists:
|
||||
pod_list = list_pods(namespace=namespace)
|
||||
if name in pod_list:
|
||||
return True
|
||||
else:
|
||||
logging.error("Namespace '%s' doesn't exist" % str(namespace))
|
||||
return False
|
||||
|
||||
|
||||
def check_if_pvc_exists(name: str, namespace: str) -> bool:
|
||||
"""
|
||||
Function that checks if a namespace exists by parsing through the list of projects
|
||||
Args:
|
||||
name (string)
|
||||
- PVC name
|
||||
|
||||
namespace (string)
|
||||
- Namespace name
|
||||
|
||||
Returns:
|
||||
Boolean value indicating whether the Persistent Volume Claim exists or not
|
||||
"""
|
||||
|
||||
namespace_exists = check_if_namespace_exists(namespace)
|
||||
if namespace_exists:
|
||||
response = cli.list_namespaced_persistent_volume_claim(namespace=namespace)
|
||||
pvc_list = [pvc.metadata.name for pvc in response.items]
|
||||
if name in pvc_list:
|
||||
return True
|
||||
else:
|
||||
logging.error("Namespace '%s' doesn't exist" % str(namespace))
|
||||
return False
|
||||
|
||||
|
||||
def get_pvc_info(name: str, namespace: str) -> PVC:
|
||||
"""
|
||||
Function to retrieve information about a Persistent Volume Claim in a
|
||||
given namespace
|
||||
|
||||
Args:
|
||||
name (string)
|
||||
- Name of the persistent volume claim
|
||||
|
||||
namespace (string)
|
||||
- Namespace where the persistent volume claim is present
|
||||
|
||||
Returns:
|
||||
- A PVC data class containing the name, capacity, volume name, namespace
|
||||
and associated pod names of the PVC if the PVC exists
|
||||
- Returns None if the PVC doesn't exist
|
||||
"""
|
||||
|
||||
pvc_exists = check_if_pvc_exists(name=name, namespace=namespace)
|
||||
if pvc_exists:
|
||||
pvc_info_response = cli.read_namespaced_persistent_volume_claim(name=name, namespace=namespace, pretty=True)
|
||||
pod_list_response = cli.list_namespaced_pod(namespace=namespace)
|
||||
|
||||
capacity = pvc_info_response.status.capacity['storage']
|
||||
volume_name = pvc_info_response.spec.volume_name
|
||||
|
||||
# Loop through all pods in the namespace to find associated PVCs
|
||||
pvc_pod_list = []
|
||||
for pod in pod_list_response.items:
|
||||
for volume in pod.spec.volumes:
|
||||
if volume.persistent_volume_claim is not None and volume.persistent_volume_claim.claim_name == name:
|
||||
pvc_pod_list.append(pod.metadata.name)
|
||||
|
||||
pvc_info = PVC(name=name, capacity=capacity, volumeName=volume_name, podNames=pvc_pod_list, namespace=namespace)
|
||||
return pvc_info
|
||||
else:
|
||||
logging.error(
|
||||
"PVC '%s' doesn't exist in namespace '%s'" % (str(name), str(namespace))
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
# Find the node kraken is deployed on
|
||||
# Set global kraken node to not delete
|
||||
def find_kraken_node():
|
||||
|
||||
74
kraken/kubernetes/resources.py
Normal file
74
kraken/kubernetes/resources.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass(frozen=True, order=False)
|
||||
class Volume:
|
||||
"""Data class to hold information regarding volumes in a pod"""
|
||||
name: str
|
||||
pvcName: str
|
||||
|
||||
|
||||
@dataclass(order=False)
|
||||
class VolumeMount:
|
||||
"""Data class to hold information regarding volume mounts"""
|
||||
name: str
|
||||
mountPath: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, order=False)
|
||||
class PVC:
|
||||
"""Data class to hold information regarding persistent volume claims"""
|
||||
name: str
|
||||
capacity: str
|
||||
volumeName: str
|
||||
podNames: List[str]
|
||||
namespace: str
|
||||
|
||||
|
||||
@dataclass(order=False)
|
||||
class Container:
|
||||
"""Data class to hold information regarding containers in a pod"""
|
||||
image: str
|
||||
name: str
|
||||
volumeMounts: List[VolumeMount]
|
||||
ready: bool = False
|
||||
|
||||
|
||||
@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
|
||||
volumes: List[Volume]
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
import random
|
||||
import yaml
|
||||
import re
|
||||
import json
|
||||
@@ -6,7 +7,6 @@ import logging
|
||||
import time
|
||||
import kraken.cerberus.setup as cerberus
|
||||
import kraken.kubernetes.client as kubecli
|
||||
import kraken.invoke.command as runcommand
|
||||
|
||||
# Reads the scenario config and creates a temp file to fill up the PVC
|
||||
|
||||
@@ -46,75 +46,59 @@ pvc_name: '%s'\npod_name: '%s'\nnamespace: '%s'\ntarget_fill_percentage: '%s%%'\
|
||||
if pvc_name:
|
||||
if pod_name:
|
||||
logging.info(
|
||||
"pod_name '%s' will be overridden from the pod mounted in the PVC" % (str(pod_name))
|
||||
"pod_name '%s' will be overridden with one of the pods mounted in the PVC" % (str(pod_name))
|
||||
)
|
||||
command = "kubectl describe pvc %s -n %s | grep -E 'Mounted By:|Used By:' | grep -Eo '[^: ]*$'" % (
|
||||
str(pvc_name),
|
||||
str(namespace),
|
||||
)
|
||||
logging.debug("Get pod name command:\n %s" % command)
|
||||
pod_name = runcommand.invoke(command, 60).rstrip()
|
||||
logging.info("Pod name: %s" % pod_name)
|
||||
if pod_name == "<none>":
|
||||
pvc = kubecli.get_pvc_info(pvc_name, namespace)
|
||||
try:
|
||||
pod_name = random.choice(pvc.podNames)
|
||||
logging.info("Pod name: %s" % pod_name)
|
||||
except Exception:
|
||||
logging.error(
|
||||
"Pod associated with %s PVC, on namespace %s, not found" % (str(pvc_name), str(namespace))
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Get volume name
|
||||
command = 'kubectl get pods %s -n %s -o json | jq -r ".spec.volumes"' % (
|
||||
str(pod_name),
|
||||
str(namespace),
|
||||
)
|
||||
logging.debug("Get mount path command:\n %s" % command)
|
||||
volumes_list = runcommand.invoke(command, 60).rstrip()
|
||||
volumes_list_json = json.loads(volumes_list)
|
||||
for entry in volumes_list_json:
|
||||
if len(entry["persistentVolumeClaim"]["claimName"]) > 0:
|
||||
volume_name = entry["name"]
|
||||
pvc_name = entry["persistentVolumeClaim"]["claimName"]
|
||||
pod = kubecli.get_pod_info(name=pod_name, namespace=namespace)
|
||||
|
||||
if pod is None:
|
||||
logging.error("Exiting as pod '%s' doesn't exist in namespace '%s'" % (str(pod_name), str(namespace)))
|
||||
sys.exit(1)
|
||||
|
||||
for volume in pod.volumes:
|
||||
if volume.pvcName is not None:
|
||||
volume_name = volume.name
|
||||
pvc_name = volume.pvcName
|
||||
pvc = kubecli.get_pvc_info(pvc_name, namespace)
|
||||
break
|
||||
if 'pvc' not in locals():
|
||||
logging.error(
|
||||
"Pod '%s' in namespace '%s' does not use a pvc" % (str(pod_name), str(namespace))
|
||||
)
|
||||
sys.exit(1)
|
||||
logging.info("Volume name: %s" % volume_name)
|
||||
logging.info("PVC name: %s" % pvc_name)
|
||||
|
||||
# Get container name and mount path
|
||||
command = 'kubectl get pods %s -n %s -o json | jq -r ".spec.containers"' % (
|
||||
str(pod_name),
|
||||
str(namespace),
|
||||
)
|
||||
logging.debug("Get mount path command:\n %s" % command)
|
||||
volume_mounts_list = runcommand.invoke(command, 60).rstrip().replace("\n]\n[\n", ",\n")
|
||||
volume_mounts_list_json = json.loads(volume_mounts_list)
|
||||
for entry in volume_mounts_list_json:
|
||||
for vol in entry["volumeMounts"]:
|
||||
if vol["name"] == volume_name:
|
||||
mount_path = vol["mountPath"]
|
||||
container_name = entry["name"]
|
||||
for container in pod.containers:
|
||||
for vol in container.volumeMounts:
|
||||
if vol.name == volume_name:
|
||||
mount_path = vol.mountPath
|
||||
container_name = container.name
|
||||
break
|
||||
logging.info("Container path: %s" % container_name)
|
||||
logging.info("Mount path: %s" % mount_path)
|
||||
|
||||
# Get PVC capacity
|
||||
command = "kubectl describe pvc %s -n %s | grep \"Capacity:\" | grep -Eo '[^: ]*$'" % (
|
||||
str(pvc_name),
|
||||
str(namespace),
|
||||
)
|
||||
pvc_capacity = runcommand.invoke(
|
||||
command,
|
||||
60,
|
||||
).rstrip()
|
||||
logging.debug("Get PVC capacity command:\n %s" % command)
|
||||
pvc_capacity_kb = toKbytes(pvc_capacity)
|
||||
# Get PVC capacity and used bytes
|
||||
command = "df %s -B 1024 | sed 1d" % (str(mount_path))
|
||||
command_output = (kubecli.exec_cmd_in_pod(command, pod_name, namespace, container_name, "sh")).split()
|
||||
pvc_used_kb = int(command_output[2])
|
||||
pvc_capacity_kb = pvc_used_kb + int(command_output[3])
|
||||
logging.info("PVC used: %s KB" % pvc_used_kb)
|
||||
logging.info("PVC capacity: %s KB" % pvc_capacity_kb)
|
||||
|
||||
# Get used bytes in PVC
|
||||
command = "df %s -B 1024 | sed 1d | awk -F' ' '{print $3}'" % (str(mount_path))
|
||||
logging.debug("Get used bytes in PVC command:\n %s" % command)
|
||||
pvc_used_kb = kubecli.exec_cmd_in_pod(command, pod_name, namespace, container_name, "sh")
|
||||
logging.info("PVC used: %s KB" % pvc_used_kb)
|
||||
|
||||
# Check valid fill percentage
|
||||
current_fill_percentage = float(pvc_used_kb) / float(pvc_capacity_kb)
|
||||
current_fill_percentage = pvc_used_kb / pvc_capacity_kb
|
||||
if not (current_fill_percentage * 100 < float(target_fill_percentage) <= 99):
|
||||
logging.error(
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user