Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QACTL logging #1791

Merged
merged 9 commits into from
Aug 27, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
}
}
}
1 change: 1 addition & 0 deletions deps/wazuh_testing/wazuh_testing/qa_ctl/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
QACTL_LOGGER = 'qactl'
Empty file.
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 `<network_id>`'
raise QAValueError(exception_message, DockerWrapper.LOGGER.critical)

def restart(self):
"""Restart the container.
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -15,39 +18,46 @@ 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]
if not data['enabled']:
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()

Expand All @@ -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]))

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

Expand All @@ -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()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -42,41 +47,45 @@ 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.
"""
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.
Expand All @@ -87,23 +96,23 @@ 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.
"""
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.
"""
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.
Expand Down
Loading