diff --git a/Pipfile b/Pipfile index 18b826c5b..973705cce 100644 --- a/Pipfile +++ b/Pipfile @@ -8,7 +8,8 @@ tobiko = {editable = true,path = "."} ansible = "*" testscenarios = "*" crayons = "*" -cryptography = "2.2.2" +os-faults = "*" +tempest = "*" [dev-packages] diff --git a/README.rst b/README.rst index 905fcdb94..55bb69bb1 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,6 @@ Tempest plugin for testing upgrades * Source: https://git.openstack.org/cgit/openstack/tobiko * Bugs: https://bugs.launchpad.net/tobiko - Usage ----- diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 000000000..93a5e5924 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,12 @@ +# Tobiko Usage Examples + + +## Faults + +Use `tobiko-fault` to run only faults, without running resources population or tests. + +Note: `tobiko-fault` can executed only from undercloud node. + +To restart openvswitch service, run the following command: + + tobiko-fault --fault "restart openvswitch service" diff --git a/extra-requirements.txt b/extra-requirements.txt index faa63a7ea..a92145ab3 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -4,3 +4,4 @@ ansible>=2.4.0 # GPLv3 os-faults>=0.1.18 # Apache-2.0 tempest>=17.1.0 # Apache-2.0 cryptography<=2.2.2 # Apache-2.0 +Jinja2>=2.8.0 # BSD diff --git a/setup.cfg b/setup.cfg index 1dbd7c635..5c7008051 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,7 +22,7 @@ keywords = distutils [files] -packages = +packages = tobiko [entry_points] @@ -31,6 +31,7 @@ console_scripts = tobiko-delete = tobiko.cmd.delete:main tobiko-fixture = tobiko.cmd.fixture:main tobiko-list = tobiko.cmd.list:main + tobiko-fault = tobiko.cmd.fault:main tobiko = tobiko.cmd.run:main [global] diff --git a/tobiko/cmd/fault.py b/tobiko/cmd/fault.py new file mode 100644 index 000000000..c6efb861d --- /dev/null +++ b/tobiko/cmd/fault.py @@ -0,0 +1,55 @@ +# Copyright 2019 Red Hat +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from __future__ import absolute_import + +import argparse +import logging +import sys + +from tobiko.common import clients +from tobiko.fault import executor + + +LOG = logging.getLogger(__name__) + + +class FaultCMD(object): + + def __init__(self): + self.parser = self.get_parser() + self.args = self.parser.parse_args() + self.clients = clients.ClientManager() + + def get_parser(self): + parser = argparse.ArgumentParser(add_help=True) + parser.add_argument( + '--fault', + required=True, + help="The fault to execute (e.g. restart neutron service).\n") + return parser + + def run(self): + """Run faults.""" + fault_exec = executor.FaultExecutor(clients=self.clients) + fault_exec.execute(self.args.fault) + + +def main(): + """Run CLI main entry.""" + fault_cli = FaultCMD() + fault_cli.run() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tobiko/common/utils/file.py b/tobiko/common/utils/file.py new file mode 100644 index 000000000..1583406ba --- /dev/null +++ b/tobiko/common/utils/file.py @@ -0,0 +1,30 @@ +# Copyright 2019 Red Hat +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from __future__ import absolute_import + +import os + +from oslo_log import log + + +LOG = log.getLogger(__name__) + + +def makedirs(path, mode=777, exist_ok=True): + """Creates directory and its parents if directory doesn't exists.""" + try: + os.makedirs(path, mode, exist_ok) + except FileExistsError: + if not exist_ok: + raise diff --git a/tobiko/fault/__init__.py b/tobiko/fault/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tobiko/fault/config.py b/tobiko/fault/config.py new file mode 100644 index 000000000..8ff2014c3 --- /dev/null +++ b/tobiko/fault/config.py @@ -0,0 +1,73 @@ +# Copyright 2019 Red Hat +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from __future__ import absolute_import + +import os + +import jinja2 + +from oslo_log import log + +from tobiko.fault import constants as fault_const +from tobiko.common.utils import file as file_utils + + +LOG = log.getLogger(__name__) + + +class FaultConfig(object): + """Responsible for managing faults configuration.""" + + DEFAULT_CONF_PATH = os.path.expanduser('~/.config/openstack') + DEFAULT_CONF_NAME = "os-faults.yml" + DEFAULT_CONF_FILE = os.path.join(DEFAULT_CONF_PATH, DEFAULT_CONF_NAME) + + def __init__(self, conf_file, clients): + self.templates_dir = os.path.join(os.path.dirname(__file__), + 'templates') + self.clients = clients + if conf_file: + self.conf_file = conf_file + else: + conf_file = self.DEFAULT_CONF_FILE + if os.path.isfile(conf_file): + self.conf_file = conf_file + else: + self.conf_file = self.generate_config_file() + + def generate_config_file(self): + """Generates os-faults configuration file.""" + LOG.info("Generating os-fault configuration file.") + file_utils.makedirs(self.DEFAULT_CONF_PATH) + rendered_conf = self.get_rendered_configuration() + with open(self.DEFAULT_CONF_FILE, "w") as f: + f.write(rendered_conf) + return self.DEFAULT_CONF_FILE + + def get_rendered_configuration(self): + """Returns rendered os-fault configuration file.""" + j2_env = jinja2.Environment( + loader=jinja2.FileSystemLoader(self.templates_dir), + trim_blocks=True) + template = j2_env.get_template('os-faults.yml.j2') + nodes = self.get_nodes() + return template.render(nodes=nodes, + services=fault_const.SERVICES, + containers=fault_const.CONTAINERS) + + def get_nodes(self): + """Returns a list of dictonaries with nodes name and address.""" + return [{'name': server.name, + 'address': server.addresses['ctlplane'][0]['addr']} + for server in self.clients.nova_client.servers.list()] diff --git a/tobiko/fault/constants.py b/tobiko/fault/constants.py new file mode 100644 index 000000000..ab3dccc47 --- /dev/null +++ b/tobiko/fault/constants.py @@ -0,0 +1,17 @@ +# Copyright 2019 Red Hat +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from __future__ import absolute_import + +SERVICES = ['openvsiwtch'] +CONTAINERS = ['neutron_ovs_agent', 'neutron_metadata_agent', 'neutron_api'] diff --git a/tobiko/fault/executor.py b/tobiko/fault/executor.py new file mode 100644 index 000000000..6a860ef52 --- /dev/null +++ b/tobiko/fault/executor.py @@ -0,0 +1,49 @@ +# Copyright 2019 Red Hat +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from __future__ import absolute_import + +from oslo_log import log + +import os_faults + +from tobiko.fault.config import FaultConfig + +LOG = log.getLogger(__name__) + + +class FaultExecutor(object): + """Responsible for executing faults.""" + + def __init__(self, conf_file=None, clients=None): + self.config = FaultConfig(conf_file=conf_file, clients=clients) + try: + self.connect() + LOG.info("os-faults connected.") + except os_faults.api.error.OSFError: + msg = "Unable to connect. Please check your configuration." + raise RuntimeError(msg) + + def connect(self): + """Connect to the cloud using os-faults.""" + try: + self.cloud = os_faults.connect( + config_filename=self.config.conf_file) + self.cloud.verify() + except os_faults.ansible.executor.AnsibleExecutionUnreachable: + LOG.warning("Couldn't verify connectivity to the" + " cloud with os-faults configuration") + + def execute(self, fault): + """Executes given fault using os-faults human API.""" + os_faults.human_api(self.cloud, fault) diff --git a/tobiko/fault/templates/os-faults.yml.j2 b/tobiko/fault/templates/os-faults.yml.j2 new file mode 100644 index 000000000..428707891 --- /dev/null +++ b/tobiko/fault/templates/os-faults.yml.j2 @@ -0,0 +1,31 @@ +cloud_management: + driver: universal + +node_discover: + driver: node_list + args: +{% for node in nodes %} + - fqdn: {{ node['name'] }} + ip: {{ node['address'] }} + auth: + username: heat-admin + private_key_file: /home/stack/.ssh/id_rsa + become: true +{% endfor %} + +services: +{% for service in services %} + {{ service }}: + driver: system_service + args: + service_name: {{ service }} + grep: {{ service }} +{% endfor %} + +containers: +{% for container in containers %} + {{ container }}: + driver: docker_container + args: + container_name: {{ container }} +{% endfor %}