mirror of
https://github.com/krkn-chaos/krkn.git
synced 2026-03-27 13:57:14 +00:00
Compare commits
1 Commits
v5.0.2
...
remove-pow
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8c4c27a7d |
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -11,16 +11,18 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Build the Docker images
|
|
||||||
run: docker build --no-cache -t quay.io/chaos-kubox/krkn containers/
|
|
||||||
- name: Create multi-node KinD cluster
|
- name: Create multi-node KinD cluster
|
||||||
uses: chaos-kubox/actions/kind@main
|
uses: chaos-kubox/actions/kind@main
|
||||||
- name: Install environment
|
- name: Install environment
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install build-essential python3-dev
|
sudo apt-get install build-essential python3-dev
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
- name: Run CI
|
- name: Run unit tests
|
||||||
|
run: python -m unittest discover
|
||||||
|
- name: Run e2e tests
|
||||||
run: ./CI/run.sh
|
run: ./CI/run.sh
|
||||||
|
- name: Build the Docker images
|
||||||
|
run: docker build --no-cache -t quay.io/chaos-kubox/krkn containers/
|
||||||
- name: Login in quay
|
- name: Login in quay
|
||||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
run: docker login quay.io -u ${QUAY_USER} -p ${QUAY_TOKEN}
|
run: docker login quay.io -u ${QUAY_USER} -p ${QUAY_TOKEN}
|
||||||
|
|||||||
0
kraken/health/__init__.py
Normal file
0
kraken/health/__init__.py
Normal file
0
kraken/health/cerberus/__init__.py
Normal file
0
kraken/health/cerberus/__init__.py
Normal file
6
kraken/health/cerberus/config.py
Normal file
6
kraken/health/cerberus/config.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CerberusConfig:
|
||||||
|
cerberus_url: str
|
||||||
13
kraken/health/cerberus/healthcheck.py
Normal file
13
kraken/health/cerberus/healthcheck.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import requests as requests
|
||||||
|
|
||||||
|
from kraken.health.cerberus.config import CerberusConfig
|
||||||
|
from kraken.health.health import HealthChecker, HealthCheckDecision
|
||||||
|
|
||||||
|
|
||||||
|
class CerberusHealthChecker(HealthChecker):
|
||||||
|
def __init__(self, config: CerberusConfig):
|
||||||
|
self._config = config
|
||||||
|
|
||||||
|
def check(self) -> HealthCheckDecision:
|
||||||
|
cerberus_status = requests.get(self._config.cerberus_url, timeout=60).content
|
||||||
|
return HealthCheckDecision.GO if cerberus_status == b"True" else HealthCheckDecision.STOP
|
||||||
14
kraken/health/health.py
Normal file
14
kraken/health/health.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class HealthCheckDecision(Enum):
|
||||||
|
GO = "GO"
|
||||||
|
PAUSE = "PAUSE"
|
||||||
|
STOP = "STOP"
|
||||||
|
|
||||||
|
|
||||||
|
class HealthChecker(ABC):
|
||||||
|
@abstractmethod
|
||||||
|
def check(self) -> HealthCheckDecision:
|
||||||
|
pass
|
||||||
125
kraken/kubernetes/kube.py
Normal file
125
kraken/kubernetes/kube.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import unittest
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Dict, List
|
||||||
|
from kubernetes import config, client
|
||||||
|
from kubernetes.client.models import V1Pod, V1PodSpec, V1ObjectMeta, V1Container
|
||||||
|
from kubernetes.client.exceptions import ApiException
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Pod:
|
||||||
|
"""
|
||||||
|
A pod is a simplified representation of a Kubernetes pod. We only extract the data we need in krkn.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
namespace: str
|
||||||
|
labels: Dict[str, str]
|
||||||
|
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
"""
|
||||||
|
This is the implementation of all Kubernetes API calls used in Krkn.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, kubeconfig_path: str = None):
|
||||||
|
# Note: this function replicates much of the functionality already represented in the Kubernetes Python client,
|
||||||
|
# but in an object-oriented manner. This allows for creating multiple clients and accessing multiple clusters
|
||||||
|
# with minimal effort if needed, which the procedural implementation doesn't allow.
|
||||||
|
if kubeconfig_path is None:
|
||||||
|
kubeconfig_path = config.KUBE_CONFIG_DEFAULT_LOCATION
|
||||||
|
kubeconfig = config.kube_config.KubeConfigMerger(kubeconfig_path)
|
||||||
|
|
||||||
|
if kubeconfig.config is None:
|
||||||
|
raise config.ConfigException(
|
||||||
|
'Invalid kube-config file: %s. '
|
||||||
|
'No configuration found.' % kubeconfig_path)
|
||||||
|
loader = config.kube_config.KubeConfigLoader(
|
||||||
|
config_dict=kubeconfig.config,
|
||||||
|
)
|
||||||
|
client_config = client.Configuration()
|
||||||
|
loader.load_and_set(client_config)
|
||||||
|
self.client = client.ApiClient(configuration=client_config)
|
||||||
|
self.core_v1 = client.CoreV1Api(self.client)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convert_pod(pod: V1Pod) -> Pod:
|
||||||
|
return Pod(
|
||||||
|
name=pod.metadata.name,
|
||||||
|
namespace=pod.metadata.namespace,
|
||||||
|
labels=pod.metadata.labels
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_test_pod(self) -> Pod:
|
||||||
|
"""
|
||||||
|
create_test_pod creates a test pod in the default namespace that can be safely killed.
|
||||||
|
"""
|
||||||
|
return self._convert_pod(self.core_v1.create_namespaced_pod(
|
||||||
|
"default",
|
||||||
|
V1Pod(
|
||||||
|
metadata=V1ObjectMeta(
|
||||||
|
generate_name="test-",
|
||||||
|
),
|
||||||
|
spec=V1PodSpec(
|
||||||
|
containers=[
|
||||||
|
V1Container(
|
||||||
|
name="test",
|
||||||
|
image="alpine",
|
||||||
|
tty=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
def list_all_pods(self, label_selector: str = None) -> List[Pod]:
|
||||||
|
"""
|
||||||
|
list_all_pods lists all pods in all namespaces, possibly with a label selector applied.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
pod_response = self.core_v1.list_pod_for_all_namespaces(watch=False, label_selector=label_selector)
|
||||||
|
pod_list: List[client.models.V1Pod] = pod_response.items
|
||||||
|
result: List[Pod] = []
|
||||||
|
for pod in pod_list:
|
||||||
|
result.append(self._convert_pod(pod))
|
||||||
|
return result
|
||||||
|
except ApiException as e:
|
||||||
|
if e.status == 404:
|
||||||
|
raise NotFoundException(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_pod(self, name: str, namespace: str = "default") -> Pod:
|
||||||
|
"""
|
||||||
|
get_pod returns a pod based on the name and a namespace.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self._convert_pod(self.core_v1.read_namespaced_pod(name, namespace))
|
||||||
|
except ApiException as e:
|
||||||
|
if e.status == 404:
|
||||||
|
raise NotFoundException(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def remove_pod(self, name: str, namespace: str = "default"):
|
||||||
|
"""
|
||||||
|
remove_pod removes a pod based on the name and namespace. A NotFoundException is raised if the pod doesn't
|
||||||
|
exist.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.core_v1.delete_namespaced_pod(name, namespace)
|
||||||
|
except ApiException as e:
|
||||||
|
if e.status == 404:
|
||||||
|
raise NotFoundException(e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class NotFoundException(Exception):
|
||||||
|
"""
|
||||||
|
NotFoundException is an exception specific to the scenario Kubernetes abstraction and is thrown when a specific
|
||||||
|
resource (e.g. a pod) cannot be found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cause: Exception):
|
||||||
|
self.__cause__ = cause
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
42
kraken/kubernetes/test_kube.py
Normal file
42
kraken/kubernetes/test_kube.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from kraken.scenarios import kube
|
||||||
|
|
||||||
|
|
||||||
|
class TestClient(unittest.TestCase):
|
||||||
|
def test_list_all_pods(self):
|
||||||
|
c = kube.Client()
|
||||||
|
pod = c.create_test_pod()
|
||||||
|
self.addCleanup(lambda: self._remove_pod(c, pod.name, pod.namespace))
|
||||||
|
pods = c.list_all_pods()
|
||||||
|
for pod in pods:
|
||||||
|
if pod.name == pod.name and pod.namespace == pod.namespace:
|
||||||
|
return
|
||||||
|
self.fail("The created pod %s was not in the pod list." % pod.name)
|
||||||
|
|
||||||
|
def test_get_pod(self):
|
||||||
|
c = kube.Client()
|
||||||
|
pod = c.create_test_pod()
|
||||||
|
self.addCleanup(lambda: c.remove_pod(pod.name, pod.namespace))
|
||||||
|
pod2 = c.get_pod(pod.name, pod.namespace)
|
||||||
|
assert pod2.name == pod.name
|
||||||
|
assert pod2.namespace == pod.namespace
|
||||||
|
|
||||||
|
def test_get_pod_notfound(self):
|
||||||
|
c = kube.Client()
|
||||||
|
try:
|
||||||
|
c.get_pod("non-existent-pod")
|
||||||
|
self.fail("Fetching a non-existent pod did not result in a NotFoundException.")
|
||||||
|
except kube.NotFoundException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _remove_pod(c: kube.Client, pod_name: str, pod_namespace: str):
|
||||||
|
try:
|
||||||
|
c.remove_pod(pod_name, pod_namespace)
|
||||||
|
except kube.NotFoundException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
0
kraken/runner/__init__.py
Normal file
0
kraken/runner/__init__.py
Normal file
16
kraken/runner/loader.py
Normal file
16
kraken/runner/loader.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
from kraken.scenarios.base import Scenario
|
||||||
|
from kraken.scenarios.runner import ScenarioRunnerConfig
|
||||||
|
|
||||||
|
|
||||||
|
class Loader:
|
||||||
|
def __init__(self, scenarios: List[Scenario]):
|
||||||
|
self.scenarios = scenarios
|
||||||
|
|
||||||
|
def load(self, data: Dict) -> ScenarioRunnerConfig:
|
||||||
|
"""
|
||||||
|
This function loads data from a dictionary and produces a scenario runner config. It uses the scenarios provided
|
||||||
|
when instantiating the loader.
|
||||||
|
"""
|
||||||
|
|
||||||
28
kraken/runner/runner.py
Normal file
28
kraken/runner/runner.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from kraken.scenarios import base
|
||||||
|
|
||||||
|
from kraken.scenarios.health import HealthChecker
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ScenarioRunnerConfig:
|
||||||
|
iterations: int
|
||||||
|
steps: List[base.ScenarioConfig]
|
||||||
|
|
||||||
|
|
||||||
|
class ScenarioRunner:
|
||||||
|
"""
|
||||||
|
This class provides the services to load a scenario configuration and iterate over the scenarios, while
|
||||||
|
observing the health checks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, scenarios: List[base.Scenario], health_checker: HealthChecker):
|
||||||
|
self._scenarios = scenarios
|
||||||
|
self._health_checker = health_checker
|
||||||
|
|
||||||
|
def run(self, config: ScenarioRunnerConfig):
|
||||||
|
"""
|
||||||
|
This function runs a list of scenarios described in the configuration.
|
||||||
|
"""
|
||||||
0
kraken/scenarios/__init__.py
Normal file
0
kraken/scenarios/__init__.py
Normal file
61
kraken/scenarios/base.py
Normal file
61
kraken/scenarios/base.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
from typing import TypeVar, Generic, Dict
|
||||||
|
|
||||||
|
from kraken.scenarios.kube import Client
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ScenarioConfig(ABC):
|
||||||
|
"""
|
||||||
|
ScenarioConfig is a generic base class for configurations for individual scenarios. Each scenario should define
|
||||||
|
its own configuration classes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def from_dict(self, data: Dict) -> None:
|
||||||
|
"""
|
||||||
|
from_dict loads the configuration from a dict. It is mainly used to load JSON data into the scenario
|
||||||
|
configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def validate(self) -> None:
|
||||||
|
"""
|
||||||
|
validate is a function that validates all data on the scenario configuration. If the scenario configuration
|
||||||
|
is invalid an Exception should be thrown.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar('T', bound=ScenarioConfig)
|
||||||
|
|
||||||
|
|
||||||
|
class Scenario(Generic[T]):
|
||||||
|
"""
|
||||||
|
Scenario is a generic base class that provides a uniform run function to call in a loop. Scenario implementations
|
||||||
|
should extend this class and accept their configuration via their initializer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_config(self) -> T:
|
||||||
|
"""
|
||||||
|
create_config creates a new copy of the configuration structure that allows loading data from a dictionary
|
||||||
|
and validating it.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self, kube: Client, config: T) -> None:
|
||||||
|
"""
|
||||||
|
run is a function that is called when the scenario should be run. A Kubernetes client implementation will be
|
||||||
|
passed. The scenario should execute and return immediately. If the scenario fails, an Exception should be
|
||||||
|
thrown.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutException(Exception):
|
||||||
|
"""
|
||||||
|
TimeoutException is an exception thrown when a scenario has a timeout waiting for a condition to happen.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
96
kraken/scenarios/pod.py
Normal file
96
kraken/scenarios/pod.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from kraken.scenarios import base
|
||||||
|
from kraken.scenarios.base import ScenarioConfig, Scenario
|
||||||
|
from kraken.scenarios.kube import Client, Pod, NotFoundException
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PodScenarioConfig(ScenarioConfig):
|
||||||
|
"""
|
||||||
|
PodScenarioConfig is a configuration structure specific to pod scenarios. It describes which pod from which
|
||||||
|
namespace(s) to select for killing and how many pods to kill.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name_pattern: str
|
||||||
|
namespace_pattern: str
|
||||||
|
label_selector: str
|
||||||
|
kill: int
|
||||||
|
|
||||||
|
def from_dict(self, data: Dict) -> None:
|
||||||
|
self.name_pattern = data.get("name_pattern")
|
||||||
|
self.namespace_pattern = data.get("namespace_pattern")
|
||||||
|
self.label_selector = data.get("label_selector")
|
||||||
|
self.kill = data.get("kill")
|
||||||
|
|
||||||
|
def validate(self) -> None:
|
||||||
|
re.compile(self.name_pattern)
|
||||||
|
re.compile(self.namespace_pattern)
|
||||||
|
if self.kill < 1:
|
||||||
|
raise Exception("Invalid value for 'kill': %d" % self.kill)
|
||||||
|
|
||||||
|
def namespace_regexp(self) -> re.Pattern:
|
||||||
|
return re.compile(self.namespace_pattern)
|
||||||
|
|
||||||
|
def name_regexp(self) -> re.Pattern:
|
||||||
|
return re.compile(self.name_pattern)
|
||||||
|
|
||||||
|
|
||||||
|
class PodScenario(Scenario[PodScenarioConfig]):
|
||||||
|
"""
|
||||||
|
PodScenario is a scenario that tests the stability of a Kubernetes cluster by killing one or more pods based on the
|
||||||
|
PodScenarioConfig.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, logger: logging.Logger):
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
|
def create_config(self) -> PodScenarioConfig:
|
||||||
|
return PodScenarioConfig(
|
||||||
|
name_pattern=".*",
|
||||||
|
namespace_pattern=".*",
|
||||||
|
label_selector="",
|
||||||
|
kill=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self, kube: Client, config: PodScenarioConfig):
|
||||||
|
pod_candidates: List[Pod] = []
|
||||||
|
namespace_re = config.namespace_regexp()
|
||||||
|
name_re = config.name_regexp()
|
||||||
|
|
||||||
|
self.logger.info("Listing all pods to determine viable pods to kill...")
|
||||||
|
for pod in kube.list_all_pods(label_selector=config.label_selector):
|
||||||
|
if namespace_re.match(pod.namespace) and name_re.match(pod.name):
|
||||||
|
pod_candidates.append(pod)
|
||||||
|
random.shuffle(pod_candidates)
|
||||||
|
removed_pod: List[Pod] = []
|
||||||
|
pods_to_kill = min(config.kill, len(pod_candidates))
|
||||||
|
|
||||||
|
self.logger.info("Killing %d pods...", pods_to_kill)
|
||||||
|
for i in range(pods_to_kill):
|
||||||
|
pod = pod_candidates[i]
|
||||||
|
self.logger.info("Killing pod %s...", pod.name)
|
||||||
|
removed_pod.append(pod)
|
||||||
|
kube.remove_pod(pod.name, pod.namespace)
|
||||||
|
|
||||||
|
self.logger.info("Waiting for pods to be removed...")
|
||||||
|
for i in range(60):
|
||||||
|
time.sleep(1)
|
||||||
|
for pod in removed_pod:
|
||||||
|
try:
|
||||||
|
kube.get_pod(pod.name, pod.namespace)
|
||||||
|
self.logger.info("Pod %s still exists...", pod.name)
|
||||||
|
except NotFoundException:
|
||||||
|
self.logger.info("Pod %s is now removed.", pod.name)
|
||||||
|
removed_pod.remove(pod)
|
||||||
|
if len(removed_pod) == 0:
|
||||||
|
self.logger.info("All pods removed, pod scenario complete.")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.logger.warning("Timeout waiting for pods to be removed.")
|
||||||
|
raise base.TimeoutException("Timeout while waiting for pods to be removed.")
|
||||||
43
kraken/scenarios/test_pod.py
Normal file
43
kraken/scenarios/test_pod.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from kraken.scenarios import kube
|
||||||
|
from kraken.scenarios.kube import Client, NotFoundException
|
||||||
|
from kraken.scenarios.pod import PodScenario
|
||||||
|
|
||||||
|
|
||||||
|
class TestPodScenario(unittest.TestCase):
|
||||||
|
def test_run(self):
|
||||||
|
"""
|
||||||
|
This test creates a test pod and then runs the pod scenario restricting the run to that specific pod.
|
||||||
|
"""
|
||||||
|
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
|
||||||
|
|
||||||
|
c = Client()
|
||||||
|
test_pod = c.create_test_pod()
|
||||||
|
self.addCleanup(lambda: self._remove_test_pod(c, test_pod.name, test_pod.namespace))
|
||||||
|
|
||||||
|
scenario = PodScenario(logging.getLogger(__name__))
|
||||||
|
config = scenario.create_config()
|
||||||
|
config.kill = 1
|
||||||
|
config.name_pattern = test_pod.name
|
||||||
|
config.namespace_pattern = test_pod.namespace
|
||||||
|
scenario.run(c, config)
|
||||||
|
|
||||||
|
try:
|
||||||
|
c.get_pod(test_pod.name)
|
||||||
|
self.fail("Getting the pod after a pod scenario run should result in a NotFoundException.")
|
||||||
|
except NotFoundException:
|
||||||
|
return
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _remove_test_pod(c: kube.Client, pod_name: str, pod_namespace: str):
|
||||||
|
try:
|
||||||
|
c.remove_pod(pod_name, pod_namespace)
|
||||||
|
except NotFoundException:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
datetime
|
datetime
|
||||||
pyfiglet
|
pyfiglet
|
||||||
PyYAML>=5.1
|
PyYAML>=5.1
|
||||||
git+https://github.com/powerfulseal/powerfulseal.git@3.3.0
|
|
||||||
requests
|
requests
|
||||||
boto3
|
boto3
|
||||||
google-api-python-client
|
google-api-python-client
|
||||||
azure-mgmt-compute
|
azure-mgmt-compute
|
||||||
azure-keyvault
|
azure-keyvault
|
||||||
azure-identity
|
azure-identity
|
||||||
kubernetes==18.20.0
|
kubernetes
|
||||||
oauth2client>=4.1.3
|
oauth2client>=4.1.3
|
||||||
python-openstackclient
|
python-openstackclient
|
||||||
gitpython
|
gitpython
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
import logging
|
import logging
|
||||||
import optparse
|
import optparse
|
||||||
@@ -22,6 +24,9 @@ import kraken.application_outage.actions as application_outage
|
|||||||
import kraken.pvc.pvc_scenario as pvc_scenario
|
import kraken.pvc.pvc_scenario as pvc_scenario
|
||||||
import kraken.network_chaos.actions as network_chaos
|
import kraken.network_chaos.actions as network_chaos
|
||||||
import server as server
|
import server as server
|
||||||
|
from kraken.scenarios.base import Scenario
|
||||||
|
from kraken.scenarios.pod import PodScenario
|
||||||
|
from kraken.scenarios.runner import ScenarioRunner
|
||||||
|
|
||||||
|
|
||||||
def publish_kraken_status(status):
|
def publish_kraken_status(status):
|
||||||
@@ -115,6 +120,12 @@ def main(cfg):
|
|||||||
run_uuid = str(uuid.uuid4())
|
run_uuid = str(uuid.uuid4())
|
||||||
logging.info("Generated a uuid for the run: %s" % run_uuid)
|
logging.info("Generated a uuid for the run: %s" % run_uuid)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
scenarios: List[Scenario] = [
|
||||||
|
PodScenario(logger),
|
||||||
|
]
|
||||||
|
health_checker = CerberusHealthChecker(config)
|
||||||
|
runner = ScenarioRunner(scenarios, health_checker)
|
||||||
# Initialize the start iteration to 0
|
# Initialize the start iteration to 0
|
||||||
iteration = 0
|
iteration = 0
|
||||||
|
|
||||||
|
|||||||
89
scenarios/kube/composite.schema.json
Normal file
89
scenarios/kube/composite.schema.json
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2019-09/schema",
|
||||||
|
"$id": "https://github.com/chaos-kubox/krkn/",
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"title": "Composite scenario for Krkn",
|
||||||
|
"required": [
|
||||||
|
"steps"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"iterations": {
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1,
|
||||||
|
"title": "How many iterations to execute",
|
||||||
|
"examples": [
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"title": "The steps Schema",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"title": "A Schema",
|
||||||
|
"required": [
|
||||||
|
"pod"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"pod": {
|
||||||
|
"type": "object",
|
||||||
|
"default": {},
|
||||||
|
"title": "The pod Schema",
|
||||||
|
"required": [
|
||||||
|
"name_pattern",
|
||||||
|
"namespace_pattern"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"name_pattern": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"title": "The name_pattern Schema",
|
||||||
|
"examples": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"namespace_pattern": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"title": "The namespace_pattern Schema",
|
||||||
|
"examples": [
|
||||||
|
""
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [{
|
||||||
|
"name_pattern": "test-.*",
|
||||||
|
"namespace_pattern": "default"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [{
|
||||||
|
"pod": {
|
||||||
|
"name_pattern": "test-.*",
|
||||||
|
"namespace_pattern": "default"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
[{
|
||||||
|
"pod": {
|
||||||
|
"name_pattern": "test-.*",
|
||||||
|
"namespace_pattern": "default"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"examples": [{
|
||||||
|
"iterations": 1,
|
||||||
|
"steps": [{
|
||||||
|
"pod": {
|
||||||
|
"name_pattern": "test-.*",
|
||||||
|
"namespace_pattern": "default"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}
|
||||||
5
scenarios/kube/composite.yml
Normal file
5
scenarios/kube/composite.yml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
iterations: 1
|
||||||
|
steps:
|
||||||
|
- pod:
|
||||||
|
name_pattern:
|
||||||
|
namespace_pattern:
|
||||||
Reference in New Issue
Block a user