Add tobiko-fault command

* tobiko-fault allows you to run different faults on different
  nodes in your environment.
* If os-faults configuration not present, tobiko-fault will generate
  it.

Change-Id: I428aebcbcdc3c9997a0c839e6a18564433e68ba8
This commit is contained in:
abregman 2019-03-13 10:05:31 +02:00
parent 0329e30896
commit 9fbfe5c321
12 changed files with 272 additions and 3 deletions

View File

@ -8,7 +8,8 @@ tobiko = {editable = true,path = "."}
ansible = "*"
testscenarios = "*"
crayons = "*"
cryptography = "2.2.2"
os-faults = "*"
tempest = "*"
[dev-packages]

View File

@ -8,7 +8,6 @@ Tempest plugin for testing upgrades
* Source: https://git.openstack.org/cgit/openstack/tobiko
* Bugs: https://bugs.launchpad.net/tobiko
Usage
-----

12
docs/usage.md Normal file
View File

@ -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"

View File

@ -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

View File

@ -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]

55
tobiko/cmd/fault.py Normal file
View File

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

View File

@ -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

0
tobiko/fault/__init__.py Normal file
View File

73
tobiko/fault/config.py Normal file
View File

@ -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()]

17
tobiko/fault/constants.py Normal file
View File

@ -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']

49
tobiko/fault/executor.py Normal file
View File

@ -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)

View File

@ -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 %}