diff --git a/deps/wazuh_testing/wazuh_testing/data/qactl_conf_validator_schema.json b/deps/wazuh_testing/wazuh_testing/data/qactl_conf_validator_schema.json index f681cea9cf..1514dfc9fd 100644 --- a/deps/wazuh_testing/wazuh_testing/data/qactl_conf_validator_schema.json +++ b/deps/wazuh_testing/wazuh_testing/data/qactl_conf_validator_schema.json @@ -353,5 +353,32 @@ } } } + }, + "config": { + "$id": "#/properties/config", + "type": "object", + "patternProperties": { + "vagrant_output": { + "type": "boolean" + }, + "ansible_output": { + "type": "boolean" + }, + "logging":{ + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "level": { + "type": "string", + "enum": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + }, + "file": { + "type": "string" + } + } + } + } } } diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/__init__.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/__init__.py index e69de29bb2..43495ae54c 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/__init__.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/__init__.py @@ -0,0 +1 @@ +QACTL_LOGGER = 'qactl' diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/__init__.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py new file mode 100644 index 0000000000..aeb754b6ef --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/configuration/qa_ctl_configuration.py @@ -0,0 +1,32 @@ + +class QACTLConfiguration: + + def __init__(self, configuration_data): + self.configuration_data = configuration_data + self.vagrant_output = False + self.ansible_output = False + self.logging_enable = True + self.logging_level = 'INFO' + self.logging_file = None + + self.__read_configuration_data() + + def __read_configuration_data(self): + if 'config' in self.configuration_data: + if 'vagrant_output' in self.configuration_data['config']: + self.vagrant_output = self.configuration_data['config']['vagrant_output'] + if 'ansible_output' in self.configuration_data['config']: + self.ansible_output = self.configuration_data['config']['ansible_output'] + if 'logging' in self.configuration_data['config']: + if 'enable' in self.configuration_data['config']['logging']: + self.logging_enable = self.configuration_data['config']['logging']['enable'] + if 'level' in self.configuration_data['config']['logging']: + self.logging_level = self.configuration_data['config']['logging']['level'] + if 'file' in self.configuration_data['config']['logging']: + self.logging_file = self.configuration_data['config']['logging']['file'] + + + def __str__(self): + return f"vagrant_output: {self.vagrant_output}\nansible_output: {self.ansible_output}\n" \ + f"logging_enable: {self.logging_enable}\nloggin_level: {self.logging_level}\n"\ + f"logging_file: {self.logging_file}\n" diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/docker_wrapper.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/docker_wrapper.py index bb91d56595..8191062122 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/docker_wrapper.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/docker_wrapper.py @@ -1,7 +1,11 @@ import docker -from wazuh_testing.qa_ctl.deployment.instance import Instance from json import dumps +from wazuh_testing.qa_ctl.deployment.instance import Instance +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging +from wazuh_testing.tools.exceptions import QAValueError + class DockerWrapper(Instance): """Class to handle docker operations. This class uses the docker python SDK to read a dockerfile and create @@ -35,6 +39,8 @@ class DockerWrapper(Instance): no static IP will be assigned. network_name (string): Name of the docker network. """ + LOGGER = Logging.get_logger(QACTL_LOGGER) + def __init__(self, docker_client, dockerfile_path, name, remove=False, ports=None, detach=True, stdout=False, stderr=False, ip=None, network_name=None): self.docker_client = docker_client @@ -68,11 +74,19 @@ def get_container(self): return self.docker_client.containers.get(self.name) def run(self): + DockerWrapper.LOGGER.debug(f"Running {self.name} cointainer...") container = self.docker_client.containers.run(image=self.image, name=self.name, ports=self.ports, remove=self.remove, detach=self.detach, stdout=self.stdout, stderr=self.stderr) if self.ip and self.network_name: - self.docker_client.networks.get(self.network_name).connect(container, ipv4_address=self.ip) + try: + self.docker_client.networks.get(self.network_name).connect(container, ipv4_address=self.ip) + except docker.errors.APIError: #requests.exceptions.HTTPError: + exception_message = f"Invalid address {self.ip} It does not belong to any of this network's " \ + 'subnets. Please check if you have already set this docker network ' \ + '(run `docker network ls`) and then remove it if it is created with ' \ + 'docker network rm ``' + raise QAValueError(exception_message, DockerWrapper.LOGGER.critical) def restart(self): """Restart the container. @@ -81,6 +95,7 @@ def restart(self): docker.errors.APIError: If the server returns an error. """ try: + DockerWrapper.LOGGER.debug(f"Restarting {self.name} cointainer...") self.get_container().restart() except docker.errors.NotFound: pass @@ -92,6 +107,7 @@ def halt(self): docker.errors.APIError: If the server returns an error. """ try: + DockerWrapper.LOGGER.debug(f"Stopping {self.name} cointainer...") self.get_container().stop() except docker.errors.NotFound: pass @@ -111,11 +127,13 @@ def destroy(self, remove_image=False): pass try: + DockerWrapper.LOGGER.debug(f"Removing {self.name} cointainer...") self.get_container().remove() except docker.errors.NotFound: pass if remove_image: + DockerWrapper.LOGGER.debug(f"Removing {self.image.id} docker image...") self.docker_client.images.remove(image=self.image.id, force=True) def get_instance_info(self): diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py index 6eb35a5cb6..f803933863 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/qa_infraestructure.py @@ -7,6 +7,9 @@ from wazuh_testing.qa_ctl.deployment.docker_wrapper import DockerWrapper from wazuh_testing.qa_ctl.deployment.vagrant_wrapper import VagrantWrapper from wazuh_testing.tools.thread_executor import ThreadExecutor +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging +from wazuh_testing.tools.exceptions import QAValueError class QAInfraestructure: @@ -15,25 +18,30 @@ class QAInfraestructure: Args: instance_list (dict): Dictionary with the information of the instances. Must follow the format of the yaml template. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. Class Attributes: - DOCKER_NETWORK_NAME: Name of the docker network where the containers will be connected. + DOCKER_NETWORK_NAME (str): Name of the docker network where the containers will be connected. + LOGGER (Logging): Logger object. Instance Attributes: + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. instances (list): List with the instances to handle. docker_client (Docker Client): Client to communicate with the docker daemon. docker_network (Docker Network): Network object to handle container's static IP address. network_address (IPNetwork): Docker network address. - """ DOCKER_NETWORK_NAME = 'wazuh_net' + LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, instance_list): + def __init__(self, instance_list, qa_ctl_configuration): + self.qa_ctl_configuration = qa_ctl_configuration self.instances = [] self.docker_client = None self.docker_network = None self.network_address = None + QAInfraestructure.LOGGER.debug('Processing deployment configuration...') for host in instance_list: for provider in instance_list[host]['provider']: data = instance_list[host]['provider'][provider] @@ -41,13 +49,15 @@ def __init__(self, instance_list): continue if provider == 'vagrant': - quiet_out = True if 'quiet_out' not in data else data['quiet_out'] + QAInfraestructure.LOGGER.debug(f"Setting {data['vm_name']} vagrant instance for deployment...") + quiet_out = True if not self.qa_ctl_configuration.vagrant_output else False vagrant_instance = VagrantWrapper(data['vagrantfile_path'], data['vagrant_box'], data['label'], data['vm_name'], data['vm_cpu'], data['vm_memory'], data['vm_system'], data['vm_ip'], quiet_out) self.instances.append(vagrant_instance) elif provider == 'docker': + QAInfraestructure.LOGGER.debug(f"Setting {data['name']} docker instance for deployment...") if not self.docker_client: self.docker_client = docker.from_env() @@ -65,14 +75,17 @@ def __init__(self, instance_list): self.network_address = network if network != self.network_address: - raise ValueError('Two different networks where found for docker containers when only one ' - f"network is allowed: {network} != {self.network_address}") + exception_message = 'Two different networks where found for docker containers when only ' \ + f"one network is allowed: {network} != {self.network_address}" + raise QAValueError(exception_message, QAInfraestructure.LOGGER.critical) if not self.docker_network: # Try to get the DOCKER_NETWORK_NAME network, if it fails, try to create it. try: self.docker_network = self.docker_client.networks.get(self.DOCKER_NETWORK_NAME) except docker.errors.NotFound: + QAInfraestructure.LOGGER.debug(f"Docker network {self.network_address} not found." + 'Creating it...') ipam_pool = docker.types.IPAMPool(subnet=str(self.network_address), gateway=str(self.network_address[-2])) @@ -84,7 +97,6 @@ def __init__(self, instance_list): docker_instance = DockerWrapper(self.docker_client, data['dockerfile_path'], data['name'], _remove, _ports, _detach, _stdout, _stderr, ip=_ip, network_name=self.DOCKER_NETWORK_NAME) - self.instances.append(docker_instance) def __threads_runner(self, threads): @@ -101,24 +113,30 @@ def __threads_runner(self, threads): def run(self): """Execute the run method on every configured instance.""" + QAInfraestructure.LOGGER.info(f"Running {len(self.instances)} instances deployment...") self.__threads_runner([ThreadExecutor(instance.run) for instance in self.instances]) def halt(self): """Execute the 'halt' method on every configured instance.""" + QAInfraestructure.LOGGER.info(f"Stopping {len(self.instances)} instances...") self.__threads_runner([ThreadExecutor(instance.halt) for instance in self.instances]) def restart(self): """Execute the 'restart' method on every configured instance.""" + QAInfraestructure.LOGGER.info(f"Restarting {len(self.instances)} instances...") self.__threads_runner([ThreadExecutor(instance.restart) for instance in self.instances]) def destroy(self): """Execute the 'destroy' method on every configured instance.""" + QAInfraestructure.LOGGER.info(f"Destroying {len(self.instances)} instances...") self.__threads_runner([ThreadExecutor(instance.destroy) for instance in self.instances]) if self.docker_network: + QAInfraestructure.LOGGER.debug('Removing docker network...') try: self.docker_network.remove() except docker.errors.NotFound: + QAInfraestructure.LOGGER.error('Could not remove docker network') pass def status(self): @@ -128,6 +146,7 @@ def status(self): (dict): Contains the status for each configured instance. """ status = {} + QAInfraestructure.LOGGER.debug('Getting instances status...') for instance in self.instances: status[instance.get_name()] = instance.status() @@ -140,6 +159,7 @@ def get_instances_info(self): (dict): Dictionary with the information for each configured instance. """ info = {} + QAInfraestructure.LOGGER.debug('Getting instances info...') for instance in self.instances: info[instance.get_name()] = instance.get_instance_info() diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrant_wrapper.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrant_wrapper.py index ce8a2229cf..f731576a6a 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrant_wrapper.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrant_wrapper.py @@ -3,10 +3,14 @@ # This program is free software; you can redistribute it and/or modify it under the terms of GPLv2 import os import vagrant + from shutil import rmtree -from wazuh_testing.qa_ctl.deployment.instance import Instance + import wazuh_testing.qa_ctl.deployment.vagrantfile as vfile +from wazuh_testing.qa_ctl.deployment.instance import Instance +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class VagrantWrapper(Instance): """Class to handle Vagrant operations. The class will use the Vagrantfile class to create a vagrantfile in @@ -26,13 +30,14 @@ class VagrantWrapper(Instance): Attributes: vagrantfile (Vagrantfile): Vagrantfile object containing the vagrantfile information. vagrant (Vagrant): Vagrant object to handle vagrant operations - + vm_name (String): Name that will be assigned to the VM """ + LOGGER = Logging.get_logger(QACTL_LOGGER) def __init__(self, vagrant_root_folder, vm_box, vm_label, vm_name, vm_cpus, vm_memory, vm_system, vm_ip, quiet_out=True): - self.box_folder = os.path.join(vagrant_root_folder, vm_name) + self.vm_name = vm_name os.makedirs(self.box_folder, exist_ok=True) self.vagrantfile = vfile.Vagrantfile(self.box_folder, vm_box, vm_label, vm_name, vm_cpus, vm_memory, @@ -42,33 +47,37 @@ def __init__(self, vagrant_root_folder, vm_box, vm_label, vm_name, vm_cpus, vm_m self.vagrantfile.write_vagrantfile() def run(self): - """Writes the vagrantfile and starts the VM specified in the vagrantfile.""" + """Write the vagrantfile and starts the VM specified in the vagrantfile.""" + VagrantWrapper.LOGGER.debug(f"Running {self.vm_name} vagrant up...") self.vagrant.up() def halt(self): - """Stops the VM specified in the vagrantfile.""" + """Stop the VM specified in the vagrantfile.""" + VagrantWrapper.LOGGER.debug(f"Running {self.vm_name} vagrant halt...") self.vagrant.halt() def restart(self): - """Restarts the VM specified in the vagrantfile.""" + """Restart the VM specified in the vagrantfile.""" + VagrantWrapper.LOGGER.debug(f"Running {self.vm_name} vagrant restrt...") self.vagrant.restart() def destroy(self): - """Destroys the VM specified in the vagrantfile and remove the vagrantfile.""" + """Destroy the VM specified in the vagrantfile and remove the vagrantfile.""" + VagrantWrapper.LOGGER.debug(f"Running {self.vm_name} vagrant destroy...") self.vagrant.destroy() self.vagrantfile.remove_vagrantfile() rmtree(self.box_folder) def suspend(self): - """Suspends the VM specified in the vagrantfile.""" + """Suspend the VM specified in the vagrantfile.""" self.vagrant.suspend() def resume(self): - """Resumes the VM specified in the vagrantfile.""" + """Resume the VM specified in the vagrantfile.""" self.vagrant.resume() def get_vagrant_version(self): - """Gets the vagrant version of the host. + """Get the vagrant version of the host. Returns: (str): Vagrant version. @@ -76,7 +85,7 @@ def get_vagrant_version(self): return self.vagrant.version() def status(self): - """Gets the status of the VM specified in the vagrantfile. + """Get the status of the VM specified in the vagrantfile. The vagrant module returns a list of namedtuples like the following `[Status(name='ubuntu', state='not_created', provider='virtualbox')]` but we are only interested in the `state` field. @@ -87,7 +96,7 @@ def status(self): return self.vagrant.status()[0].state def get_ssh_config(self): - """Gets the config of the VM specified in the vagrantfile. + """Get the config of the VM specified in the vagrantfile. Returns: (dict): Dictionary with the configuration of the VM. @@ -95,7 +104,7 @@ def get_ssh_config(self): return self.vagrant.conf() def get_instance_info(self): - """Gets the instance info. + """Get the instance info. Returns: (dict): Dictionary with the parameters of the VM. @@ -103,7 +112,7 @@ def get_instance_info(self): return str(self.vagrantfile) def get_name(self): - """Gets the name of the VM. + """Get the name of the VM. Returns: (str): Name of the VM. diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile.py index 07ef204ab1..f41fbf4379 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/deployment/vagrantfile.py @@ -4,7 +4,9 @@ from pathlib import Path import os import json -import logging + +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class Vagrantfile(): @@ -37,6 +39,7 @@ class Vagrantfile(): It MUST start with '/' to assign properly the group in VirtualBox. private_ip (str): IP of the VM. """ + LOGGER = Logging.get_logger(QACTL_LOGGER) TEMPLATE_FILE = os.path.join( Path(__file__).parent, 'vagrantfile_template.txt') REPLACE_PATTERN = 'json_box = {}\n' @@ -61,7 +64,7 @@ def __get_box_url(self): try: return box_mapping[self.box_image] except KeyError: - logging.warning('Using a box not tested') + Vagrantfile.LOGGER.warning('Using a non default box...') return None def __str__(self): @@ -84,6 +87,7 @@ def read_vagrantfile_template(self): List: List with the content of the template vagrant template.""" with open(self.TEMPLATE_FILE, 'r') as template_fd: return template_fd.readlines() + Vagrantfile.LOGGER.debug(f"Read vagrantfile {self.TEMPLATE_FILE} template") def write_vagrantfile(self): """Replace the self.REPLACE_PATTERN line with a string with the parameters in JSON format and write the new @@ -95,8 +99,11 @@ def write_vagrantfile(self): with open(self.file_path, 'w') as vagrantfile_fd: vagrantfile_fd.writelines(read_lines) + Vagrantfile.LOGGER.debug(f"Vagranfile written in {self.file_path}") + def remove_vagrantfile(self): """Removes the file self.file_path if it exists.""" if os.path.exists(self.file_path): os.remove(self.file_path) + Vagrantfile.LOGGER.debug(f"{self.file_path} Vagranfile was removed") diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_playbook.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_playbook.py index 4666fd04e9..349cfbf007 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_playbook.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_playbook.py @@ -4,7 +4,8 @@ from tempfile import gettempdir from wazuh_testing.tools.time import get_current_timestamp - +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class AnsiblePlaybook(): """Class to create playbook file with a custom tasks list @@ -29,6 +30,8 @@ class AnsiblePlaybook(): playbook_vars (dict): Variables for playbook generate_file (bool): If True, write playbook in file. """ + LOGGER = Logging.get_logger(QACTL_LOGGER) + def __init__(self, name='generic_playbook', tasks_list=None, playbook_file_path=None, hosts='all', gather_facts=False, ignore_errors=False, become=False, playbook_vars=None, generate_file=True): self.name = name @@ -70,4 +73,5 @@ def write_playbook_to_file(self): def delete_playbook_file(self): if os.path.exists(self.playbook_file_path): + AnsiblePlaybook.LOGGER.debug(f"Removing {self.playbook_file_path} playbook") os.remove(self.playbook_file_path) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py index d6918edd6e..0c4178f23e 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/ansible/ansible_runner.py @@ -4,6 +4,8 @@ from wazuh_testing.qa_ctl.provisioning.ansible.ansible_output import AnsibleOutput from wazuh_testing.qa_ctl.provisioning.ansible.ansible_playbook import AnsiblePlaybook +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class AnsibleRunner: @@ -13,16 +15,21 @@ class AnsibleRunner: ansible_inventory_path (string): Path where is located the ansible inventory file. ansible_playbook_path (string): Path where is located the playbook file. private_data_dir (string): Path where the artifacts files (result files) will be stored. + output (boolean): True for showing ansible task output in stdout False otherwise. Attributes: ansible_inventory_path (string): Path where is located the ansible inventory file. ansible_playbook_path (string): Path where is located the playbook file. private_data_dir (string): Path where the artifacts files (result files) will be stored. + output (boolean): True for showing ansible task output in stdout False otherwise. """ - def __init__(self, ansible_inventory_path, ansible_playbook_path, private_data_dir=gettempdir()): + LOGGER = Logging.get_logger(QACTL_LOGGER) + + def __init__(self, ansible_inventory_path, ansible_playbook_path, private_data_dir=gettempdir(), output=False): self.ansible_inventory_path = ansible_inventory_path self.ansible_playbook_path = ansible_playbook_path self.private_data_dir = private_data_dir + self.output = output def run(self): """Run the ansible playbook in the indicated hosts. @@ -30,9 +37,12 @@ def run(self): Returns: AnsibleOutput: Result of the ansible playbook run. """ + quiet = not self.output + AnsibleRunner.LOGGER.debug(f"Running {self.ansible_playbook_path} ansible-playbook with " + f"{self.ansible_inventory_path} inventory") runner = ansible_runner.run(private_data_dir=self.private_data_dir, playbook=self.ansible_playbook_path, - inventory=self.ansible_inventory_path) + inventory=self.ansible_inventory_path, quiet=quiet) ansible_output = AnsibleOutput(runner) if ansible_output.rc != 0: @@ -41,11 +51,13 @@ def run(self): return ansible_output @staticmethod - def run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_error=True): + def run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_error=True, output=False): ansible_playbook = AnsiblePlaybook(**playbook_parameters) + quiet = not output try: - runner = ansible_runner.run(playbook=ansible_playbook.playbook_file_path, inventory=ansible_inventory_path) + runner = ansible_runner.run(playbook=ansible_playbook.playbook_file_path, inventory=ansible_inventory_path, + quiet=quiet) ansible_output = AnsibleOutput(runner) if ansible_output.rc != 0 and raise_on_error: diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_framework/qa_framework.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_framework/qa_framework.py index c345a94c57..e407bb22e7 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_framework/qa_framework.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_framework/qa_framework.py @@ -2,7 +2,8 @@ from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask from wazuh_testing.qa_ctl.provisioning.ansible.ansible_runner import AnsibleRunner - +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class QAFramework(): """Encapsulates all the functionality regarding the preparation and installation of qa-framework @@ -11,17 +12,22 @@ class QAFramework(): workdir (str): Directory where the qa repository files are stored qa_repository (str): Url to the QA repository. qa_branch (str): QA branch of the qa repository. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. Attributes: workdir (str): Directory where the qa repository files are stored qa_repository (str): Url to the QA repository. qa_branch (str): QA branch of the qa repository. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. """ + LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, workdir=gettempdir(), qa_repository='https://github.com/wazuh/wazuh-qa.git', qa_branch='master'): + def __init__(self, qa_ctl_configuration, workdir=gettempdir(), qa_branch='master', + qa_repository='https://github.com/wazuh/wazuh-qa.git'): self.qa_repository = qa_repository self.qa_branch = qa_branch self.workdir = f"{workdir}/wazuh-qa" + self.qa_ctl_configuration = qa_ctl_configuration def install_dependencies(self, inventory_file_path, hosts='all'): """Install all the necessary dependencies to allow the execution of the tests. @@ -35,7 +41,10 @@ def install_dependencies(self, inventory_file_path, hosts='all'): 'args': {'chdir': self.workdir}}) ansible_tasks = [dependencies_task] playbook_parameters = {'hosts': hosts, 'tasks_list': ansible_tasks} - AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters) + QAFramework.LOGGER.debug(f"Installing python dependencies in {hosts} hosts.") + + AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) def install_framework(self, inventory_file_path, hosts='all'): """Install the wazuh_testing framework to allow the execution of the tests. @@ -48,8 +57,10 @@ def install_framework(self, inventory_file_path, hosts='all'): 'args': {'chdir': f"{self.workdir}/deps/wazuh_testing"}}) ansible_tasks = [install_framework_task] playbook_parameters = {'hosts': hosts, 'tasks_list': ansible_tasks, 'become': True} + QAFramework.LOGGER.debug(f"Installing wazuh-qa framework in {hosts} hosts.") - AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters) + AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) def download_qa_repository(self, inventory_file_path, hosts='all'): """Download the qa-framework in the specified attribute workdir. @@ -65,5 +76,7 @@ def download_qa_repository(self, inventory_file_path, hosts='all'): 'version': self.qa_branch}}) ansible_tasks = [create_path_task, download_qa_repo_task] playbook_parameters = {'hosts': hosts, 'tasks_list': ansible_tasks} + QAFramework.LOGGER.debug(f"Downloading qa-repository in {hosts} hosts") - AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters) + AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py index 2b7d7e39a5..d4437d1e71 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/qa_provisioning.py @@ -12,33 +12,35 @@ from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask from wazuh_testing.qa_ctl.provisioning.qa_framework.qa_framework import QAFramework from wazuh_testing.tools.thread_executor import ThreadExecutor +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class QAProvisioning(): """Class to control different options and instances to provisioning with Wazuh and QA Framework. - Attributes: + Args: provision_info (dict): Dict with all the info needed coming from config file. - instances_list (list): List with every instance (each host) needed to build the ansible inventory. - group_dict (dict): Dict with groups and every host belonging to them. - host_list (list): List with every host given in config file. - inventory_file_path (string): Path of the inventory file generated. - wazuh_installation_paths (dict): Dict indicating the Wazuh installation paths for every host. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. - Args: + Attributes: provision_info (dict): Dict with all the info needed coming from config file. instances_list (list): List with every instance (each host) needed to build the ansible inventory. group_dict (dict): Dict with groups and every host belonging to them. host_list (list): List with every host given in config file. inventory_file_path (string): Path of the inventory file generated. wazuh_installation_paths (dict): Dict indicating the Wazuh installation paths for every host. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. """ - def __init__(self, provision_info): + LOGGER = Logging.get_logger(QACTL_LOGGER) + + def __init__(self, provision_info, qa_ctl_configuration): self.provision_info = provision_info self.instances_list = [] self.group_dict = {} self.host_list = [] self.inventory_file_path = None self.wazuh_installation_paths = {} + self.qa_ctl_configuration = qa_ctl_configuration self.__process_inventory_data() @@ -64,6 +66,8 @@ def __read_ansible_instance(self, host_info): def __process_inventory_data(self): """Process config file info to generate the ansible inventory file.""" + QAProvisioning.LOGGER.debug('Processing inventory data from provisioning hosts info...') + for root_key, root_value in self.provision_info.items(): if root_key == "hosts": for _, host_value in root_value.items(): @@ -78,7 +82,7 @@ def __process_inventory_data(self): inventory_instance = AnsibleInventory(ansible_instances=self.instances_list, ansible_groups=self.group_dict) self.inventory_file_path = inventory_instance.inventory_file_path - + def __process_config_data(self, host_provision_info): """Process config file info to generate all the tasks needed for deploy Wazuh @@ -86,6 +90,7 @@ def __process_config_data(self, host_provision_info): Args: host_provision_info (dict): Dicionary with host provisioning info """ + QAProvisioning.LOGGER.debug('Processing provisioning data from hosts..') current_host = host_provision_info['host_info']['host'] if 'wazuh_deployment' in host_provision_info: @@ -112,6 +117,8 @@ def __process_config_data(self, host_provision_info): if wazuh_install_path: installation_files_parameters['wazuh_install_path'] = wazuh_install_path + installation_files_parameters['qa_ctl_configuration'] = self.qa_ctl_configuration + if install_type == "sources": installation_files_parameters['wazuh_branch'] = wazuh_branch installation_instance = WazuhSources(**installation_files_parameters) @@ -131,11 +138,13 @@ def __process_config_data(self, host_provision_info): deployment_instance = AgentDeployment(remote_files_path, inventory_file_path=self.inventory_file_path, install_mode=install_type, hosts=current_host, - server_ip=manager_ip) + server_ip=manager_ip, + qa_ctl_configuration=self.qa_ctl_configuration) if install_target == "manager": deployment_instance = ManagerDeployment(remote_files_path, inventory_file_path=self.inventory_file_path, - install_mode=install_type, hosts=current_host) + install_mode=install_type, hosts=current_host, + qa_ctl_configuration=self.qa_ctl_configuration) deployment_instance.install() if health_check: @@ -150,7 +159,7 @@ def __process_config_data(self, host_provision_info): wazuh_qa_branch = None if 'wazuh_qa_branch' not in qa_framework_info \ else qa_framework_info['wazuh_qa_branch'] - qa_instance = QAFramework(qa_branch=wazuh_qa_branch) + qa_instance = QAFramework(qa_branch=wazuh_qa_branch, qa_ctl_configuration=self.qa_ctl_configuration) qa_instance.download_qa_repository(inventory_file_path=self.inventory_file_path, hosts=current_host) qa_instance.install_dependencies(inventory_file_path=self.inventory_file_path, hosts=current_host) qa_instance.install_framework(inventory_file_path=self.inventory_file_path, hosts=current_host) @@ -161,19 +170,21 @@ def __check_hosts_connection(self, hosts='all'): Args: hosts (str): Hosts to check. """ + QAProvisioning.LOGGER.info('Checking hosts SSH connection...') wait_for_connection = AnsibleTask({'name': 'Waiting for SSH hosts connection are reachable', 'wait_for_connection': {'delay': 5, 'timeout': 60}}) playbook_parameters = {'hosts': hosts, 'tasks_list': [wait_for_connection]} - AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters) + AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) def run(self): """Provision all hosts in a parallel way""" self.__check_hosts_connection() - provision_threads = [ThreadExecutor(self.__process_config_data, parameters={'host_provision_info': host_value}) for _, host_value in self.provision_info['hosts'].items()] + QAProvisioning.LOGGER.info(f"Provisioning {len(provision_threads)} instances...") for runner_thread in provision_threads: runner_thread.start() diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/agent_deployment.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/agent_deployment.py index 2cf8bd2504..4b9487aad7 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/agent_deployment.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/agent_deployment.py @@ -15,6 +15,7 @@ class AgentDeployment(WazuhDeployment): install_dir_path (string): Path where the Wazuh installation will be stored. hosts (string): Group of hosts to be deployed. server_ip (string): Manager IP to connect. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. Attributes: installation_files (string): Path where is located the Wazuh instalation files. @@ -24,8 +25,10 @@ class AgentDeployment(WazuhDeployment): install_dir_path (string): Path where the Wazuh installation will be stored. hosts (string): Group of hosts to be deployed. server_ip (string): Manager IP to connect. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. """ + def install(self): """Child method to install Wazuh in agent @@ -85,7 +88,8 @@ def register_agent(self): self.stop_service() - output = AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters) + output = AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) self.start_service() @@ -109,4 +113,5 @@ def health_check(self): playbook_parameters = {'tasks_list': tasks_list, 'hosts': self.hosts, 'gather_facts': True, 'become': True} - return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters) + return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/manager_deployment.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/manager_deployment.py index fdb0908889..1a527471e7 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/manager_deployment.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/manager_deployment.py @@ -15,6 +15,7 @@ class ManagerDeployment(WazuhDeployment): install_dir_path (string): Path where the Wazuh installation will be stored. hosts (string): Group of hosts to be deployed. server_ip (string): Manager IP to connect. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. Attributes: installation_files (string): Path where is located the Wazuh instalation files. @@ -24,6 +25,7 @@ class ManagerDeployment(WazuhDeployment): install_dir_path (string): Path where the Wazuh installation will be stored. hosts (string): Group of hosts to be deployed. server_ip (string): Manager IP to connect. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. """ def install(self): @@ -78,4 +80,5 @@ def health_check(self): playbook_parameters = {'tasks_list': tasks_list, 'hosts': self.hosts, 'gather_facts': True, 'become': True} - return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters) + return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_deployment.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_deployment.py index 93f91e0832..1ba3bfc4a6 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_deployment.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_deployment.py @@ -1,10 +1,14 @@ +import os + from abc import ABC, abstractmethod from pathlib import Path -import os from tempfile import gettempdir + from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask from wazuh_testing.qa_ctl.provisioning.ansible.ansible_runner import AnsibleRunner +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class WazuhDeployment(ABC): @@ -18,6 +22,7 @@ class WazuhDeployment(ABC): install_dir_path (string): Path where the Wazuh installation will be stored. hosts (string): Group of hosts to be deployed. server_ip (string): Manager IP to let agent get autoenrollment. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. Attributes: installation_files_path (string): Path where is located the Wazuh instalation files. @@ -27,8 +32,11 @@ class WazuhDeployment(ABC): install_dir_path (string): Path where the Wazuh installation will be stored. hosts (string): Group of hosts to be deployed. server_ip (string): Manager IP to let agent get autoenrollment. + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. """ - def __init__(self, installation_files_path, inventory_file_path, configuration=None, + LOGGER = Logging.get_logger(QACTL_LOGGER) + + def __init__(self, installation_files_path, inventory_file_path, qa_ctl_configuration, configuration=None, install_mode='package', install_dir_path='/var/ossec', hosts='all', server_ip=None): self.installation_files_path = installation_files_path @@ -38,6 +46,7 @@ def __init__(self, installation_files_path, inventory_file_path, configuration=N self.install_dir_path = install_dir_path self.hosts = hosts self.server_ip = server_ip + self.qa_ctl_configuration = qa_ctl_configuration @abstractmethod def install(self, install_type): @@ -46,6 +55,7 @@ def install(self, install_type): Returns: AnsibleOutput: Result of the ansible playbook run. """ + WazuhDeployment.LOGGER.debug(f"Installing wazuh {install_type} via {self.install_mode} in {self.hosts} hosts..") tasks_list = [] parent_path = Path(__file__).parent if self.install_mode == 'sources': @@ -103,7 +113,8 @@ def install(self, install_type): playbook_parameters = {'tasks_list': tasks_list, 'hosts': self.hosts, 'gather_facts': True, 'become': True} - return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters) + return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) def __control_service(self, command, install_type): """Private method to control the Wazuh service in different systems. @@ -111,6 +122,7 @@ def __control_service(self, command, install_type): Returns: AnsibleOutput: Result of the ansible playbook run. """ + WazuhDeployment.LOGGER.debug(f"{command}ing wazuh service in {self.hosts} hosts") tasks_list = [] service_name = install_type if install_type == 'agent' else 'manager' service_command = f'{command}ed' if command != 'stop' else 'stopped' @@ -137,7 +149,8 @@ def __control_service(self, command, install_type): playbook_parameters = {'tasks_list': tasks_list, 'hosts': self.hosts, 'gather_facts': True, 'become': True} - return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters) + return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) @abstractmethod def start_service(self, install_type): @@ -173,6 +186,7 @@ def health_check(self): Returns: AnsibleOutput: Result of the ansible playbook run. """ + WazuhDeployment.LOGGER.debug(f"Doing wazuh deployment healthcheck in {self.hosts} hosts") tasks_list = [] tasks_list.append(AnsibleTask({'name': 'Read ossec.log searching errors', 'lineinfile': {'path': f'{self.install_dir_path}/logs/ossec.log', @@ -192,7 +206,8 @@ def health_check(self): playbook_parameters = {'tasks_list': tasks_list, 'hosts': self.hosts, 'gather_facts': True, 'become': True} - return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters) + return AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) def wazuh_is_already_installed(self): """Check if Wazuh is installed in the system @@ -208,7 +223,8 @@ def wazuh_is_already_installed(self): playbook_parameters = {'tasks_list': tasks_list, 'hosts': self.hosts, 'gather_facts': True, 'become': True} - output = AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters, raise_on_error=False) + output = AnsibleRunner.run_ephemeral_tasks(self.inventory_file_path, playbook_parameters, raise_on_error=False, + output=self.qa_ctl_configuration.ansible_output) if output.rc == 0: return False diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_installation.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_installation.py index f1f24868ea..9c12d9d7ed 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_installation.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_installation.py @@ -5,9 +5,10 @@ class WazuhInstallation(ABC): - def __init__(self, wazuh_target, installation_files_path): + def __init__(self, wazuh_target, installation_files_path, qa_ctl_configuration): self.wazuh_target = wazuh_target self.installation_files_path = installation_files_path + self.qa_ctl_configuration = qa_ctl_configuration super().__init__() @abstractmethod @@ -18,4 +19,5 @@ def download_installation_files(self, inventory_file_path, ansible_tasks, hosts= ansible_tasks.insert(0, create_path_task) playbook_parameters = {'hosts': hosts, 'tasks_list': ansible_tasks} - AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters) + AnsibleRunner.run_ephemeral_tasks(inventory_file_path, playbook_parameters, + output=self.qa_ctl_configuration.ansible_output) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_local_package.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_local_package.py index 7f6f65f25c..01ad22a5c8 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_local_package.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_local_package.py @@ -1,22 +1,30 @@ +import os + +from pathlib import Path + from wazuh_testing.qa_ctl.provisioning.wazuh_deployment.wazuh_package import WazuhPackage from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask -from pathlib import Path -import os +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class WazuhLocalPackage(WazuhPackage): + LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, wazuh_target, installation_files_path, local_package_path, version=None, system=None): + def __init__(self, wazuh_target, installation_files_path, local_package_path, qa_ctl_configuration, version=None, + system=None): self.local_package_path = local_package_path self.package_name = Path(self.local_package_path).name super().__init__(wazuh_target=wazuh_target, installation_files_path=installation_files_path, version=version, - system=system) + system=system, qa_ctl_configuration=qa_ctl_configuration) def download_installation_files(self, inventory_file_path, hosts='all'): copy_ansible_task = AnsibleTask({'name': f"Copy {self.local_package_path} package to \ {self.installation_files_path}", 'copy': {'src': self.local_package_path, 'dest': self.installation_files_path}}) + WazuhLocalPackage.LOGGER.debug(f"Copying local package {self.local_package_path} to " + f"{self.installation_files_path} in {hosts} hosts") super().download_installation_files(inventory_file_path, [copy_ansible_task], hosts) return os.path.join(self.installation_files_path, self.package_name) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_package.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_package.py index 5a4aa57ae7..b870192189 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_package.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_package.py @@ -4,11 +4,11 @@ class WazuhPackage(WazuhInstallation, ABC): - - def __init__(self, version, system, wazuh_target, installation_files_path): + def __init__(self, version, system, wazuh_target, installation_files_path, qa_ctl_configuration): self.version = version self.system = system - super().__init__(wazuh_target=wazuh_target, installation_files_path=installation_files_path) + super().__init__(wazuh_target=wazuh_target, installation_files_path=installation_files_path, + qa_ctl_configuration=qa_ctl_configuration) @abstractmethod def download_installation_files(self, inventory_file_path, ansible_tasks, hosts='all'): diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_s3_package.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_s3_package.py index cfeca08dfc..b588b2dff9 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_s3_package.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_s3_package.py @@ -1,19 +1,25 @@ import os + +from pathlib import Path + from wazuh_testing.qa_ctl.provisioning.wazuh_deployment.wazuh_package import WazuhPackage from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask -from pathlib import Path +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class WazuhS3Package(WazuhPackage): + LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, wazuh_target, s3_package_url, installation_files_path, version=None, system=None, revision=None, repository=None, architecture=None): + def __init__(self, wazuh_target, s3_package_url, installation_files_path, qa_ctl_configuration, version=None, + system=None, revision=None, repository=None, architecture=None): self.revision = revision self.repository = repository self.architecture = architecture self.s3_package_url = s3_package_url self.package_name = Path(self.s3_package_url).name super().__init__(wazuh_target=wazuh_target, installation_files_path=installation_files_path, version=version, - system=system) + system=system, qa_ctl_configuration=qa_ctl_configuration) def get_package_name(self): pass @@ -28,6 +34,7 @@ def download_installation_files(self, s3_package_url, inventory_file_path, hosts 'dest': self.installation_files_path}, 'register': 'download_state', 'retries': 6, 'delay': 10, 'until': 'download_state is success'}) + WazuhS3Package.LOGGER.debug(f"Downloading Wazuh S3 package from in {hosts} hosts") super().download_installation_files(inventory_file_path, [download_s3_package], hosts) return os.path.join(self.installation_files_path, self.package_name) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_sources.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_sources.py index e21f6c0169..531cdc8b0c 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_sources.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/provisioning/wazuh_deployment/wazuh_sources.py @@ -1,21 +1,24 @@ from wazuh_testing.qa_ctl.provisioning.wazuh_deployment.wazuh_installation import WazuhInstallation from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask - +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class WazuhSources(WazuhInstallation): + LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, wazuh_target, installation_files_path, wazuh_branch='master', + def __init__(self, wazuh_target, installation_files_path, qa_ctl_configuration, wazuh_branch='master', wazuh_repository_url='https://github.com/wazuh/wazuh.git'): self.wazuh_branch = wazuh_branch self.wazuh_repository_url = wazuh_repository_url - super().__init__(wazuh_target=wazuh_target, installation_files_path=f"{installation_files_path}/" + - f"wazuh-{self.wazuh_branch}") + super().__init__(wazuh_target=wazuh_target, qa_ctl_configuration=qa_ctl_configuration, + installation_files_path=f"{installation_files_path}/wazuh-{self.wazuh_branch}") def download_installation_files(self, inventory_file_path, hosts='all'): download_wazuh_sources_task = AnsibleTask({'name': f"Download Wazuh branch in {self.installation_files_path}", 'shell': f"cd {self.installation_files_path} && " + 'curl -Ls https://github.com/wazuh/wazuh/archive/' + f"{self.wazuh_branch}.tar.gz | tar zx && mv wazuh-*/* ."}) + WazuhSources.LOGGER.debug(f"Downloading Wazuh sources from {self.wazuh_branch} branch in {hosts} hosts") super().download_installation_files(inventory_file_path, [download_wazuh_sources_task], hosts) return self.installation_files_path diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py index 90ba64b03c..9b41226849 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/pytest.py @@ -7,6 +7,8 @@ from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask from wazuh_testing.qa_ctl.run_tests.test import Test from wazuh_testing.tools.time import get_current_timestamp +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class Pytest(Test): @@ -17,6 +19,7 @@ class Pytest(Test): tests_result_path(str): Path to the directory where the reports will be stored in the local machine tests_path (str): Path to the set of tests to be executed tests_run_dir (str): Path to the directory from where the tests are going to be executed + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. tiers (list(int), []): List of tiers to be executed stop_after_first_failure (boolean, False): If set to true then the tests' execution will stop after the first failure @@ -34,6 +37,7 @@ class Pytest(Test): tests_result_path(str): Path to the directory where the reports will be stored in the local machine tests_path (str): Path to the set of tests to be executed tests_run_dir (str): Path to the directory from where the tests are going to be executed + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. tier (srt, None): List of tiers to be executed stop_after_first_failure (boolean, False): If set to true then the tests' execution will stop after the first failure @@ -47,12 +51,13 @@ class Pytest(Test): markers(list(str), None): Set of markers to be added to the test execution command hosts(list(), ['all']): List of hosts aliases where the tests will be runned """ - RUN_PYTEST = 'python3 -m pytest ' + LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, tests_result_path, tests_path, tests_run_dir, tiers=[], stop_after_first_failure=False, - keyword_expression=None, traceback='auto', dry_run=False, custom_args=[], verbose_level=False, - log_level=None, markers=[], hosts=['all']): + def __init__(self, tests_result_path, tests_path, tests_run_dir, qa_ctl_configuration, + tiers=[], stop_after_first_failure=False, keyword_expression=None, traceback='auto', dry_run=False, + custom_args=[], verbose_level=False, log_level=None, markers=[], hosts=['all']): + self.qa_ctl_configuration = qa_ctl_configuration self.tiers = tiers self.stop_after_first_failure = stop_after_first_failure self.keyword_expression = keyword_expression @@ -77,7 +82,6 @@ def run(self, ansible_inventory_path): Args: ansible_inventory_path (str): Path to ansible inventory file """ - assets_folder = 'assets/' reports_folder = 'reports/' assets_zip = "assets.zip" @@ -174,8 +178,13 @@ def run(self, ansible_inventory_path): playbook_parameters = {'become': True, 'tasks_list': ansible_tasks, 'playbook_file_path': playbook_file_path, "hosts": self.hosts} + Pytest.LOGGER.debug(f"Running {pytest_command} on {self.hosts} hosts...") + Pytest.LOGGER.info(f"Running {self.tests_path} test on {self.hosts} hosts...") + + AnsibleRunner.run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_error=False, + output=self.qa_ctl_configuration.ansible_output) - AnsibleRunner.run_ephemeral_tasks(ansible_inventory_path, playbook_parameters, raise_on_error=False) + Pytest.LOGGER.debug(f"Saving {self.tests_path} test resulsts of {self.hosts} hosts in {self.tests_result_path}") self.result = TestResult(html_report_file_path=os.path.join(self.tests_result_path, html_report_file_name), plain_report_file_path=os.path.join(self.tests_result_path, plain_report_file_name)) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py index 7dcd66651b..ed727a1014 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/qa_test_runner.py @@ -5,6 +5,8 @@ from wazuh_testing.qa_ctl.run_tests.test_launcher import TestLauncher from wazuh_testing.qa_ctl.run_tests.pytest import Pytest from wazuh_testing.tools.thread_executor import ThreadExecutor +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class QATestRunner(): @@ -12,15 +14,19 @@ class QATestRunner(): Args: test_parameters (dict): a dictionary containing all the required data to build the tests + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. Attributes: inventory_file_path (string): Path of the inventory file generated. test_launchers (list(TestLauncher)): Test launchers objects (one for each host). + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. """ + LOGGER = Logging.get_logger(QACTL_LOGGER) - def __init__(self, tests_parameters): + def __init__(self, tests_parameters, qa_ctl_configuration): self.inventory_file_path = None self.test_launchers = [] + self.qa_ctl_configuration = qa_ctl_configuration self.__process_inventory_data(tests_parameters) self.__process_test_data(tests_parameters) @@ -51,6 +57,7 @@ def __process_inventory_data(self, instances_info): Args: instances_info (dict): Dictionary with hosts configuration. """ + QATestRunner.LOGGER.debug('Processing inventory data from testing hosts info...') instances_list = [] for _, host_value in instances_info.items(): @@ -70,8 +77,10 @@ def __process_test_data(self, instances_info): Args: instances_info (dict): Dictionary with hosts configuration. """ + QATestRunner.LOGGER.debug('Processing testing data from hosts..') + for _, host_value in instances_info.items(): - test_launcher = TestLauncher([], self.inventory_file_path) + test_launcher = TestLauncher([], self.inventory_file_path, self.qa_ctl_configuration) for module_key, module_value in host_value.items(): hosts = host_value['host_info']['host'] if module_key == 'test': @@ -92,6 +101,7 @@ def __build_test(self, test_params, host=['all']): if test_params['type'] == 'pytest': test_dict = {} test_dict['hosts'] = [host] + test_dict['qa_ctl_configuration'] = self.qa_ctl_configuration if 'path' in test_params: paths = test_params['path'] @@ -122,12 +132,18 @@ def run(self): """Run testing threads. One thread per TestLauncher object""" runner_threads = [ThreadExecutor(test_launcher.run) for test_launcher in self.test_launchers] + QATestRunner.LOGGER.info(f"Launching {len(runner_threads)} tests...") + for runner_thread in runner_threads: runner_thread.start() + QATestRunner.LOGGER.info(f'Waiting until tests finish...') + for runner_thread in runner_threads: runner_thread.join() - + + QATestRunner.LOGGER.info(f'Tests have been finished...') + def destroy(self): if os.path.exists(self.inventory_file_path): - os.remove(self.inventory_file_path) \ No newline at end of file + os.remove(self.inventory_file_path) diff --git a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/test_launcher.py b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/test_launcher.py index 818467be2c..ed6cd34f24 100644 --- a/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/test_launcher.py +++ b/deps/wazuh_testing/wazuh_testing/qa_ctl/run_tests/test_launcher.py @@ -5,6 +5,8 @@ from wazuh_testing.qa_ctl.provisioning.ansible.ansible_runner import AnsibleRunner from wazuh_testing.qa_ctl.provisioning.ansible.ansible_task import AnsibleTask from wazuh_testing.tools.time import get_current_timestamp +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging class TestLauncher: """The class encapsulates the execution of a list of tests previously built and passed as a parameter. @@ -12,23 +14,26 @@ class TestLauncher: Attributes: tests (list(Test)): List containing all the tests to be executed in the remote machine ansible_inventory_path (str): path to the ansible inventory file + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. qa_framework_path (str, None): remote directory path where the qa repository will be download to Args: tests (list(Test)): List containing all the tests to be executed in the remote machine ansible_inventory_path (str): path to the ansible inventory file + qa_ctl_configuration (QACTLConfiguration): QACTL configuration. qa_framework_path (str, None): remote directory path where the qa repository will be download to """ - + LOGGER = Logging.get_logger(QACTL_LOGGER) DEBUG_OPTIONS = ["syscheck.debug=2", "agent.debug=2", "monitord.rotate_log=0", "analysisd.debug=2", "wazuh_modules.debug=2", "wazuh_database.interval=1", "wazuh_db.commit_time=2", "wazuh_db.commit_time_max=3", "remoted.debug=2"] - def __init__(self, tests, ansible_inventory_path, qa_framework_path=None): + def __init__(self, tests, ansible_inventory_path, qa_ctl_configuration, qa_framework_path=None): self.qa_framework_path = qa_framework_path if qa_framework_path is not None else \ os.path.join(gettempdir(), 'wazuh-qa/') self.ansible_inventory_path = ansible_inventory_path + self.qa_ctl_configuration = qa_ctl_configuration self.tests = tests @@ -52,7 +57,10 @@ def __set_local_internal_options(self, hosts): playbook_parameters = {'become': True, 'tasks_list': ansible_tasks, 'playbook_file_path': playbook_file_path, 'hosts': hosts} - AnsibleRunner.run_ephemeral_tasks(self.ansible_inventory_path, playbook_parameters, raise_on_error=False) + TestLauncher.LOGGER.debug(f"Setting local_internal_options configuration in {hosts} hosts") + + AnsibleRunner.run_ephemeral_tasks(self.ansible_inventory_path, playbook_parameters, raise_on_error=False, + output=self.qa_ctl_configuration.ansible_output) def add(self, test): """Add new test to the TestLauncher instance. diff --git a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py index 530574405d..57ab641aab 100644 --- a/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py +++ b/deps/wazuh_testing/wazuh_testing/scripts/qa_ctl.py @@ -7,18 +7,31 @@ import yaml from jsonschema import validate + from wazuh_testing.qa_ctl.deployment.qa_infraestructure import QAInfraestructure from wazuh_testing.qa_ctl.provisioning.qa_provisioning import QAProvisioning from wazuh_testing.qa_ctl.run_tests.qa_test_runner import QATestRunner +from wazuh_testing.qa_ctl.configuration.qa_ctl_configuration import QACTLConfiguration +from wazuh_testing.qa_ctl import QACTL_LOGGER +from wazuh_testing.tools.logging import Logging DEPLOY_KEY = 'deployment' PROVISION_KEY = 'provision' TEST_KEY = 'tests' + +qactl_logger = None _data_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'data') -def validate_conf(configuration): +def read_configuration_data(configuration_file_path): + with open(configuration_file_path) as config_file_fd: + configuration_data = yaml.safe_load(config_file_fd) + + return configuration_data + + +def validate_configuration_data(configuration): data_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'data') schema_file = os.path.join(data_path, 'qactl_conf_validator_schema.json') @@ -28,9 +41,17 @@ def validate_conf(configuration): validate(instance=configuration, schema=schema) +def set_qactl_logging(qactl_configuration): + if not qactl_configuration.logging_enable: + qactl_logger = Logging(QACTL_LOGGER) + qactl_logger.disable() + else: + qactl_logger = Logging(QACTL_LOGGER, qactl_configuration.logging_level, True, qactl_configuration.logging_file) + + def main(): parser = argparse.ArgumentParser() - yaml_config = {} + configuration_data = {} instance_handler = None parser.add_argument('--config', '-c', type=str, action='store', required=True, @@ -41,39 +62,47 @@ def main(): arguments = parser.parse_args() + # Check configuration file path exists assert os.path.exists(arguments.config), f"{arguments.config} file doesn't exists" + # Read configuration data + configuration_data = read_configuration_data(arguments.config) + # Validate configuration schema - with open(arguments.config) as config_file_fd: - yaml_config = yaml.safe_load(config_file_fd) - validate_conf(yaml_config) + validate_configuration_data(configuration_data) + + # Set QACTL configuration + qactl_configuration = QACTLConfiguration(configuration_data) + + # Set QACTL logging + set_qactl_logging(qactl_configuration) # Run QACTL modules try: - if DEPLOY_KEY in yaml_config: - deploy_dict = yaml_config[DEPLOY_KEY] - instance_handler = QAInfraestructure(deploy_dict) + if DEPLOY_KEY in configuration_data: + deploy_dict = configuration_data[DEPLOY_KEY] + instance_handler = QAInfraestructure(deploy_dict, qactl_configuration) instance_handler.run() - if PROVISION_KEY in yaml_config: - provision_dict = yaml_config[PROVISION_KEY] - qa_provisioning = QAProvisioning(provision_dict) + if PROVISION_KEY in configuration_data: + provision_dict = configuration_data[PROVISION_KEY] + qa_provisioning = QAProvisioning(provision_dict, qactl_configuration) qa_provisioning.run() - if TEST_KEY in yaml_config: - test_dict = yaml_config[TEST_KEY] - tests_runner = QATestRunner(test_dict) + if TEST_KEY in configuration_data: + test_dict = configuration_data[TEST_KEY] + tests_runner = QATestRunner(test_dict, qactl_configuration) tests_runner.run() finally: if arguments.destroy: - if DEPLOY_KEY in yaml_config: + if DEPLOY_KEY in configuration_data: instance_handler.destroy() - - if PROVISION_KEY in yaml_config: + + if PROVISION_KEY in configuration_data: qa_provisioning.destroy() - if TEST_KEY in yaml_config: + if TEST_KEY in configuration_data: tests_runner.destroy() diff --git a/deps/wazuh_testing/wazuh_testing/tools/exceptions.py b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py new file mode 100644 index 0000000000..8ba6b76b39 --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/tools/exceptions.py @@ -0,0 +1,6 @@ + +class QAValueError(Exception): + def __init__(self, message, logger=None): + self.message = message + logger(message) + super().__init__(self.message) diff --git a/deps/wazuh_testing/wazuh_testing/tools/logging.py b/deps/wazuh_testing/wazuh_testing/tools/logging.py new file mode 100644 index 0000000000..f49dd97234 --- /dev/null +++ b/deps/wazuh_testing/wazuh_testing/tools/logging.py @@ -0,0 +1,113 @@ +import logging +import os + +class Logging: + """Class to handle modules logging. It is a wrapper class from logging standard python module. + + Args: + logger_name (str): Logger name + level (str): Logger level: DEBUG, INFO, WARNING, ERROR or CRITICAL + stdout (boolean): True for add stodut stream handler False otherwise + log_file (str): True for add file handler, False otherwise + + Attributes: + logger_name (str): Logger name + level (str): Logger level: DEBUG, INFO, WARNING, ERROR or CRITICAL + stdout (boolean): True for add stodut stream handler False otherwise + log_file (str): True for add file handler, False otherwise + """ + def __init__(self, logger_name, level='INFO', stdout=True, log_file=None): + self.logger = logging.getLogger(logger_name) + self.level = level + self.stdout = stdout + self.log_file = log_file + + self.__validate_parameters() + self.__initialize_parameters() + self.__default_config() + + def __validate_parameters(self): + """Verify class parameters value""" + if self.level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: + raise ValueError('LOGGER level must be one of the following values: DEBUG, INFO, WARNING, ERROR, CRITICAL') + + def __initialize_parameters(self): + """Set logger level, mapping the string into enum constant""" + level_mapping = { + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'ERROR': logging.ERROR, + 'CRITICAL': logging.CRITICAL + } + + self.level = level_mapping[self.level] + + def __default_config(self): + """Set default handler configuration""" + formatter = logging.Formatter(fmt='%(asctime)s - %(levelname)s - %(module)s - %(message)s') + self.logger.setLevel(self.level) + + if self.stdout: + handler = logging.StreamHandler() + handler.setFormatter(formatter) + self.logger.addHandler(handler) + + if self.log_file: + # Create folder path if not exist + if not os.path.exists(os.path.dirname(self.log_file)): + os.makedirs(os.path.dirname(self.log_file)) + + handler = logging.FileHandler(self.log_file) + handler.setFormatter(formatter) + self.logger.addHandler(handler) + + @staticmethod + def __logger_exists(logger_name): + """Get if logger exists or not. + + Returns: + boolean: True if logger exists, false otherwise + """ + return logger_name in logging.Logger.manager.loggerDict + + @staticmethod + def get_logger(logger_name): + """Get the logger object if exists + + Returns: + logging.Logger: Logger object + + Raises: + ValueError: If logger not exists + + """ + return logging.getLogger(logger_name) + + def enable(self): + """Enable logger""" + self.logger.disabled = False + + def disable(self): + """Disable logger""" + self.logger.disabled = True + + def debug(self, message): + """Log DEBUG message""" + self.logger.debug(message) + + def info(self, message): + """Log INFO message""" + self.logger.info(message) + + def warning(self, message): + """Log WARNING message""" + self.logger.warning(message) + + def error(self, message): + """Log ERROR message""" + self.logger.error(message) + + def critical(self, message): + """Log CRITICAL message""" + self.logger.critical(message)