diff --git a/CHANGELOG.md b/CHANGELOG.md index a7587ca..41ef860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,28 +1,28 @@ # Changelog -## [0.1.1][] +## [0.1.3] ### Changed -- Bugfix: kill process now passes correct parameter -- Refactory: abstract core workflow into one common function to reduce duplicates +- Refactory: [Issue #4](https://github.com/chaostoolkit-incubator/chaostoolkit-saltstack/issues/4) Salt API accepts eauth method as parameter in configuration ### Added -- Network experiments now accepts device name to adapt different os -- Add experiment action of kill processes by name -- Add experiment action of kill process by PID -- Add experiment probe to check process PID by name +- Add experiment action: run customized command line with run_cmd method + +## [0.1.1] ### Changed -- Refactory: abstract core workflow into one common function to reduce duplicates +- Bugfix: kill process now passes correct parameter +- Refactory: abstract core workflow into one common function to reduce duplicates ### Added -- Add experiment action of kill processes by name -- Add experiment action of kill process by PID -- Add experiment probe to check process PID by name +- Network experiments now accepts device name to adapt different os +- Add experiment action of kill processes by name +- Add experiment action of kill process by PID +- Add experiment probe to check process PID by name ## [0.1.0][] diff --git a/README.md b/README.md index 1871592..afbb15c 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ There are two ways of doing this: ``` * or you inject the secrets explicitly to the experiment definition: + SALTMASTER_EAUTH is optional and default to pam ```json { @@ -127,6 +128,7 @@ There are two ways of doing this: "SALTMASTER_HOST": "https://172.10.20.666", "SALTMASTER_USER": "username", "SALTMASTER_PASSWORD": "password" + "SALTMASTER_EAUTH": "sharedsecret" } } ``` diff --git a/chaossaltstack/__init__.py b/chaossaltstack/__init__.py index 57e9fa7..5fa8566 100644 --- a/chaossaltstack/__init__.py +++ b/chaossaltstack/__init__.py @@ -33,6 +33,7 @@ def __init__(self, configuration): elif 'username' in configuration: self.username = configuration['username'] self.password = configuration['password'] + self.eauth = configuration['eauth'] # Default settings for Salt Master self.headers = {"Content-type": "application/json"} self.params = {'client': 'local', 'fun': '', 'tgt': ''} @@ -40,7 +41,7 @@ def __init__(self, configuration): self.login_url = self.url + "/login" self.login_params = { 'username': self.username, 'password': self.password, - 'eauth': 'pam' + 'eauth': self.eauth } def run_cmd(self, tgt, method: str, arg=None): @@ -164,9 +165,10 @@ def saltstack_api_client(secrets: Secrets = None) -> salt_api_client: * SALTMASTER_HOST: Salt Master API address - You can authenticate with user / password via: + You can authenticate with user / password / eauth via: * SALTMASTER_USER: the user name * SALTMASTER_PASSWORD: the password + * SALTMASTER_EAUTH: the auth method, default to pam Or via a token: * SALTMASTER_TOKEN @@ -197,6 +199,7 @@ def lookup(k: str, d: str = None) -> str: if "SALTMASTER_USER" in env or "SALTMASTER_USER" in secrets: configuration['username'] = lookup("SALTMASTER_USER", "") configuration['password'] = lookup("SALTMASTER_PASSWORD", "") + configuration['eauth'] = lookup("SALTMASTER_EAUTH", "pam") elif "SALTMASTER_TOKEN" in env or "SALTMASTER_TOKEN" in secrets: configuration['token'] = lookup("SALTMASTER_TOKEN") else: diff --git a/chaossaltstack/machine/actions.py b/chaossaltstack/machine/actions.py index ea48467..9c723b4 100644 --- a/chaossaltstack/machine/actions.py +++ b/chaossaltstack/machine/actions.py @@ -12,12 +12,12 @@ from ..types import SaltStackResponse from .constants import OS_LINUX, OS_WINDOWS from .constants import BURN_CPU, FILL_DISK, NETWORK_UTIL, \ - BURN_IO, KILLALL_PROCESSES, KILL_PROCESS + BURN_IO, KILLALL_PROCESSES, KILL_PROCESS, RUN_CMD __all__ = ["burn_cpu", "fill_disk", "network_latency", "burn_io", "network_loss", "network_corruption", "network_advanced", - "killall_processes", "kill_process"] + "killall_processes", "kill_process", "run_cmd"] def burn_cpu(instance_ids: List[str] = None, @@ -209,7 +209,7 @@ def network_loss(instance_ids: List[str] = None, param["duration"] = execution_duration param["param"] = "loss " + loss_ratio param["device"] = device - + return __default_salt_experiment__(instance_ids=instance_ids, execution_duration=execution_duration, param=param, @@ -253,7 +253,6 @@ def network_corruption(instance_ids: List[str] = None, param["param"] = "corrupt " + corruption_ratio param["device"] = device - return __default_salt_experiment__(instance_ids=instance_ids, execution_duration=execution_duration, param=param, @@ -341,7 +340,7 @@ def killall_processes(instance_ids: List[str] = None, Chaostoolkit Secrets """ logger.debug( - "Start network_latency: configuration='{}', instance_ids='{}'".format( + "Start killall_process: configuration='{}', instance_ids='{}'".format( configuration, instance_ids)) param = dict() @@ -389,7 +388,7 @@ def kill_process(instance_ids: List[str] = None, Chaostoolkit Secrets """ logger.debug( - "Start network_latency: configuration='{}', instance_ids='{}'".format( + "Start kill_process: configuration='{}', instance_ids='{}'".format( configuration, instance_ids)) param = dict() @@ -406,6 +405,50 @@ def kill_process(instance_ids: List[str] = None, ) +def run_cmd(instance_ids: List[str] = None, + execution_duration: str = "60", + cmd: List[str] = None, + configuration: Configuration = None, + secrets: Secrets = None) -> SaltStackResponse: + """ + run cmd + Linus -> Shell + Windows -> PowerShell + + Parameters + ---------- + instance_ids : List[str] + Filter the virtual machines. If the filter is omitted all machines in + the subscription will be selected as potential chaos candidates. + execution_duration : str, optional default to 1 second + This is not technically not useful as the process usually is killed + without and delay, however you can set more seconds here to let the + thread wait for more time to extend your experiment execution in case + you need to watch more on the observation metrics. + cmd : List[str] + Lines of your commands + configuration : Configuration + Chaostoolkit Configuration + secrets : Secrets + Chaostoolkit Secrets + """ + logger.debug( + "Start run_cmd: configuration='{}', instance_ids='{}'".format( + configuration, instance_ids)) + + param = dict() + param["duration"] = execution_duration + param["cmd"] = cmd + + return __default_salt_experiment__(instance_ids=instance_ids, + execution_duration=execution_duration, + param=param, + experiment_type=RUN_CMD, + configuration=configuration, + secrets=secrets + ) + + ############################################################################### # Private helper functions ############################################################################### @@ -459,25 +502,52 @@ def __default_salt_experiment__(instance_ids: List[str] = None, ) -def __construct_script_content__(action, os_type, parameters): +def __construct_script_content__(action: str = None, + os_type: str = None, + parameters: dict = None): + """ + As for now, no Windows action supported except burn CPU + + :param action: + :param os_type: { OS_LINUX | OS_WINDOWS } + :param parameters: + :return: + """ - if os_type == OS_WINDOWS: - script_name = action+".ps1" - # TODO in ps1 - cmd_param = '\n'.join( - ['='.join([k, "'"+v+"'"]) for k, v in parameters.items()]) - elif os_type == OS_LINUX: - script_name = action+".sh" - cmd_param = '\n'.join( - ['='.join([k, "'"+v+"'"]) for k, v in parameters.items()]) + cmd_param = "" + if os_type == OS_LINUX: + file_suffix = ".sh" + p_delimiter = "" + cmdline_delimiter = " && " + elif os_type == OS_WINDOWS: + file_suffix = ".ps1" + p_delimiter = "$" + cmdline_delimiter = "\n" else: raise FailedActivity( "Cannot find corresponding script for {} on OS: {}".format( action, os_type)) + if action == "run_cmd": + cmdline_param = cmdline_delimiter.join(parameters['cmd']) + # parameters.pop('cmd') + del parameters['cmd'] + else: + cmdline_param = "" + + if parameters is not None: + param_list = list() + for k, v in parameters.items(): + param_list.append('='.join([p_delimiter + k, "'" + v + "'"])) + cmd_param = '\n'.join(param_list) + else: + logger.info("No parameter parsed, return default script content") + + script_name = action + file_suffix + with open(os.path.join(os.path.dirname(__file__), "scripts", script_name)) as file: script_content = file.read() # merge duration - script_content = cmd_param + "\n" + script_content + script_content = cmd_param + "\n" + cmdline_param + "\n" + script_content return script_content diff --git a/chaossaltstack/machine/constants.py b/chaossaltstack/machine/constants.py index 637797c..59b8a18 100644 --- a/chaossaltstack/machine/constants.py +++ b/chaossaltstack/machine/constants.py @@ -9,3 +9,4 @@ NETWORK_UTIL = "network_advanced" KILLALL_PROCESSES = "killall_processes" KILL_PROCESS = "kill_process" +RUN_CMD = "run_cmd" diff --git a/chaossaltstack/machine/scripts/burn_io.sh b/chaossaltstack/machine/scripts/burn_io.sh index 73fab70..288d63e 100644 --- a/chaossaltstack/machine/scripts/burn_io.sh +++ b/chaossaltstack/machine/scripts/burn_io.sh @@ -8,7 +8,7 @@ done EOF chmod +x /tmp/loop.sh -timeout --preserve-status $duration /tmp/loop.sh +timeout $duration /tmp/loop.sh # while true; # do @@ -16,7 +16,7 @@ timeout --preserve-status $duration /tmp/loop.sh # done & ret=$? -if [ $ret -eq 0 ]; then +if [[ $ret -eq 0 || $ret -eq 124 ]]; then echo "experiment burnio -> <$instance_id>: success" else echo "experiment brunio -> <$instance_id>: fail" diff --git a/chaossaltstack/machine/scripts/network_advanced.sh b/chaossaltstack/machine/scripts/network_advanced.sh index d761188..83dff6a 100644 --- a/chaossaltstack/machine/scripts/network_advanced.sh +++ b/chaossaltstack/machine/scripts/network_advanced.sh @@ -17,9 +17,9 @@ fi experiment=$(tc qdisc add dev $device root netem $param) ret=$? if [ $ret -eq 0 ]; then - echo "experiment network_latency -> <$instance_id>: success" + echo "experiment network:[$param] -> <$instance_id>: success" else - echo "experiment network_latency -> <$instance_id>: fail" + echo "experiment network:[$param] -> <$instance_id>: fail" fi #Sleep $duration @@ -38,7 +38,7 @@ fi tc -s qdisc show dev $device |grep pfifo_fast 2>&1 >/dev/null ret2=$? if [ $ret2 -eq 0 ]; then - echo "recover network_latency -> <$instance_id>: success" + echo "recover network:[$param] -> <$instance_id>: success" else - echo "recover network_latency -> <$instance_id>: fail" + echo "recover network:[$param] -> <$instance_id>: fail" fi \ No newline at end of file diff --git a/chaossaltstack/machine/scripts/run_cmd.ps1 b/chaossaltstack/machine/scripts/run_cmd.ps1 new file mode 100644 index 0000000..e4ea4f6 --- /dev/null +++ b/chaossaltstack/machine/scripts/run_cmd.ps1 @@ -0,0 +1,12 @@ +Try +{ + +} +Catch +{ + +} +Finally +{ + +} \ No newline at end of file diff --git a/chaossaltstack/machine/scripts/run_cmd.sh b/chaossaltstack/machine/scripts/run_cmd.sh new file mode 100644 index 0000000..de9d4c2 --- /dev/null +++ b/chaossaltstack/machine/scripts/run_cmd.sh @@ -0,0 +1,7 @@ + +ret=$? +if [ $ret -eq 0 ]; then + echo "experiment run_cmd -> <$instance_id>: success" +else + echo "experiment run_cmd -> <$instance_id>: fail" +fi \ No newline at end of file