Browse Source
* 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
12 changed files with 272 additions and 3 deletions
@ -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 %} |
Loading…
Reference in new issue