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
|
boto>=2.32.1,<=2.42.0 # MIT
|
||||||
gnocchiclient>=2.2.0,<=2.6.0 # Apache Software License
|
gnocchiclient>=2.2.0,<=2.6.0 # Apache Software License
|
||||||
keystoneauth1>=2.10.0,<=2.13.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-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-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
|
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