Fault injection hook plugin
This patch adds os-faults to requirements.txt and allows to use this library as hook plugin. Change-Id: Ifa8f33fbcff3e41f6ae019c6823f0a5ec328d780
This commit is contained in:
parent
23060ebc60
commit
46ca5c4d77
0
rally/plugins/openstack/hook/__init__.py
Normal file
0
rally/plugins/openstack/hook/__init__.py
Normal file
79
rally/plugins/openstack/hook/fault_injection.py
Normal file
79
rally/plugins/openstack/hook/fault_injection.py
Normal file
@ -0,0 +1,79 @@
|
||||
# Copyright 2016: Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os_faults
|
||||
|
||||
from rally import api
|
||||
from rally.common import logging
|
||||
from rally import consts
|
||||
from rally.task import hook
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@hook.configure(name="fault_injection")
|
||||
class FaultInjectionHook(hook.Hook):
|
||||
"""Performs fault injection using os-faults library.
|
||||
|
||||
Configuration:
|
||||
action - string that represents an action (more info in [1])
|
||||
verify - whether to verify connection to cloud nodes or not
|
||||
|
||||
This plugin discovers extra config of ExistingCloud
|
||||
and looks for "cloud_config" field. If cloud_config is present then
|
||||
it will be used to connect to the cloud by os-faults.
|
||||
|
||||
Another option is to provide os-faults config file through
|
||||
OS_FAULTS_CONFIG env variable. Format of the config can
|
||||
be found in [1].
|
||||
|
||||
[1] http://os-faults.readthedocs.io/en/latest/usage.html
|
||||
"""
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
"type": "object",
|
||||
"$schema": consts.JSON_SCHEMA,
|
||||
"properties": {
|
||||
"action": {"type": "string"},
|
||||
"verify": {"type": "boolean"},
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
def get_cloud_config(self):
|
||||
deployment = api.Deployment.get(self.task["deployment_uuid"])
|
||||
deployment_config = deployment["config"]
|
||||
if deployment_config["type"] != "ExistingCloud":
|
||||
return None
|
||||
|
||||
extra_config = deployment_config.get("extra", {})
|
||||
return extra_config.get("cloud_config")
|
||||
|
||||
def run(self):
|
||||
# get cloud configuration
|
||||
cloud_config = self.get_cloud_config()
|
||||
|
||||
# connect to the cloud
|
||||
injector = os_faults.connect(cloud_config)
|
||||
|
||||
# verify that all nodes are available
|
||||
if self.config.get("verify"):
|
||||
injector.verify()
|
||||
|
||||
LOG.debug("Injecting fault: %s", self.config["action"])
|
||||
os_faults.human_api(injector, self.config["action"])
|
@ -28,6 +28,7 @@ six>=1.9.0,<=1.10.0 # MIT
|
||||
boto>=2.32.1,<=2.42.0 # MIT
|
||||
gnocchiclient>=2.2.0,<=2.6.0 # Apache Software License
|
||||
keystoneauth1>=2.10.0,<=2.13.0 # Apache Software License
|
||||
os-faults>=0.1.5,<0.2.0 # Apache License, Version 2.0
|
||||
python-ceilometerclient>=2.5.0,<=2.6.1 # Apache Software License
|
||||
python-cinderclient>=1.6.0,!=1.7.0,!=1.7.1,<=1.9.0 # Apache Software License
|
||||
python-cueclient>=1.0.0 # Apache License, Version 2.0
|
||||
|
0
tests/unit/plugins/openstack/hook/__init__.py
Normal file
0
tests/unit/plugins/openstack/hook/__init__.py
Normal file
139
tests/unit/plugins/openstack/hook/test_fault_injection.py
Normal file
139
tests/unit/plugins/openstack/hook/test_fault_injection.py
Normal file
@ -0,0 +1,139 @@
|
||||
# Copyright 2016: Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
import ddt
|
||||
import jsonschema
|
||||
import mock
|
||||
from os_faults.api import error
|
||||
|
||||
from rally import consts
|
||||
from rally.plugins.openstack.hook import fault_injection
|
||||
from tests.unit import fakes
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
def create_config(**kwargs):
|
||||
return {
|
||||
"name": "fault_injection",
|
||||
"args": kwargs,
|
||||
"trigger": {
|
||||
"name": "event",
|
||||
"args": {
|
||||
"unit": "iteration",
|
||||
"at": [10]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class FaultInjectionHookTestCase(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FaultInjectionHookTestCase, self).setUp()
|
||||
self.task = {"deployment_uuid": "foo_uuid"}
|
||||
|
||||
@ddt.data((create_config(action="foo"), True),
|
||||
(create_config(action="foo", verify=True), True),
|
||||
(create_config(action=10), False),
|
||||
(create_config(action="foo", verify=10), False),
|
||||
(create_config(), False))
|
||||
@ddt.unpack
|
||||
def test_config_schema(self, config, valid):
|
||||
if valid:
|
||||
fault_injection.FaultInjectionHook.validate(config)
|
||||
else:
|
||||
self.assertRaises(jsonschema.ValidationError,
|
||||
fault_injection.FaultInjectionHook.validate,
|
||||
config)
|
||||
|
||||
@mock.patch("rally.cli.commands.deployment.api.Deployment.get")
|
||||
@mock.patch("os_faults.human_api")
|
||||
@mock.patch("os_faults.connect")
|
||||
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
|
||||
def test_run(self, mock_timer, mock_connect, mock_human_api,
|
||||
mock_deployment_get):
|
||||
injector_inst = mock_connect.return_value
|
||||
hook = fault_injection.FaultInjectionHook(
|
||||
self.task, {"action": "foo", "verify": True},
|
||||
{"iteration": 1})
|
||||
|
||||
hook.run_sync()
|
||||
|
||||
self.assertEqual(
|
||||
{"finished_at": fakes.FakeTimer().finish_timestamp(),
|
||||
"started_at": fakes.FakeTimer().timestamp(),
|
||||
"status": consts.HookStatus.SUCCESS,
|
||||
"triggered_by": {"iteration": 1}},
|
||||
hook.result())
|
||||
|
||||
mock_connect.assert_called_once_with(None)
|
||||
injector_inst.verify.assert_called_once_with()
|
||||
mock_human_api.assert_called_once_with(injector_inst, "foo")
|
||||
|
||||
@mock.patch("rally.cli.commands.deployment.api.Deployment.get")
|
||||
@mock.patch("os_faults.human_api")
|
||||
@mock.patch("os_faults.connect")
|
||||
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
|
||||
def test_run_extra_config(self, mock_timer, mock_connect, mock_human_api,
|
||||
mock_deployment_get):
|
||||
mock_deployment_get.return_value = {
|
||||
"config": {"type": "ExistingCloud",
|
||||
"extra": {"cloud_config": {"conf": "foo_config"}}}}
|
||||
injector_inst = mock_connect.return_value
|
||||
hook = fault_injection.FaultInjectionHook(
|
||||
self.task, {"action": "foo"}, {"iteration": 1})
|
||||
|
||||
hook.run_sync()
|
||||
|
||||
self.assertEqual(
|
||||
{"finished_at": fakes.FakeTimer().finish_timestamp(),
|
||||
"started_at": fakes.FakeTimer().timestamp(),
|
||||
"status": consts.HookStatus.SUCCESS,
|
||||
"triggered_by": {"iteration": 1}},
|
||||
hook.result())
|
||||
|
||||
mock_connect.assert_called_once_with({"conf": "foo_config"})
|
||||
mock_human_api.assert_called_once_with(injector_inst, "foo")
|
||||
|
||||
@mock.patch("rally.cli.commands.deployment.api.Deployment.get")
|
||||
@mock.patch("os_faults.human_api")
|
||||
@mock.patch("os_faults.connect")
|
||||
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
|
||||
def test_run_error(self, mock_timer, mock_connect, mock_human_api,
|
||||
mock_deployment_get):
|
||||
injector_inst = mock_connect.return_value
|
||||
mock_human_api.side_effect = error.OSFException("foo error")
|
||||
hook = fault_injection.FaultInjectionHook(
|
||||
self.task, {"action": "foo", "verify": True},
|
||||
{"iteration": 1})
|
||||
|
||||
hook.run_sync()
|
||||
|
||||
self.assertEqual(
|
||||
{"finished_at": fakes.FakeTimer().finish_timestamp(),
|
||||
"started_at": fakes.FakeTimer().timestamp(),
|
||||
"status": consts.HookStatus.FAILED,
|
||||
"error": {
|
||||
"details": mock.ANY,
|
||||
"etype": "OSFException",
|
||||
"msg": "foo error"},
|
||||
"triggered_by": {"iteration": 1}},
|
||||
hook.result())
|
||||
|
||||
mock_connect.assert_called_once_with(None)
|
||||
injector_inst.verify.assert_called_once_with()
|
||||
mock_human_api.assert_called_once_with(injector_inst, "foo")
|
Loading…
Reference in New Issue
Block a user