* 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: I428aebcbcdc3c9997a0c839e6a18564433e68ba8changes/29/643329/9
@ -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" |
@ -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()) |
@ -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,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()] |
@ -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'] |
@ -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) |
@ -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 %} |