Fixing parts of issue #185 for PVC scenario (#290)

* 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:
Shreyas Anantha Ramaprasad
2022-09-01 06:44:37 -07:00
committed by GitHub
parent 5ab16baafa
commit d5615ac470
3 changed files with 240 additions and 112 deletions

View File

@@ -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():

View 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

View File

@@ -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(
"""