diff --git a/etc/zun/policy.json b/etc/zun/policy.json index 3cce6c72f..f1ab97ba1 100644 --- a/etc/zun/policy.json +++ b/etc/zun/policy.json @@ -38,5 +38,6 @@ "zun-service:delete": "rule:admin_api", "zun-service:disable": "rule:admin_api", "zun-service:enable": "rule:admin_api", + "zun-service:force_down": "rule:admin_api", "zun-service:get_all": "rule:admin_api" } diff --git a/zun/api/controllers/v1/schemas/services.py b/zun/api/controllers/v1/schemas/services.py index 1c1fedb1e..dcdfc9fe0 100644 --- a/zun/api/controllers/v1/schemas/services.py +++ b/zun/api/controllers/v1/schemas/services.py @@ -36,3 +36,15 @@ query_param_disable = { }, 'additionalProperties': False } + +query_param_force_down = { + 'type': 'object', + 'properties': { + 'host': parameter_types.hostname, + 'binary': { + 'type': 'string', 'minLength': 1, 'maxLength': 255, + }, + 'forced_down': parameter_types.boolean + }, + 'additionalProperties': False +} diff --git a/zun/api/controllers/v1/zun_services.py b/zun/api/controllers/v1/zun_services.py index fe1aebee1..ee9136c17 100644 --- a/zun/api/controllers/v1/zun_services.py +++ b/zun/api/controllers/v1/zun_services.py @@ -10,7 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_utils import strutils import pecan +import six from zun.api.controllers import base from zun.api.controllers.v1 import collection @@ -56,6 +58,7 @@ class ZunServiceController(base.Controller): _custom_actions = { 'enable': ['PUT'], 'disable': ['PUT'], + 'force_down': ['PUT'], } def __init__(self, **kwargs): @@ -98,6 +101,23 @@ class ZunServiceController(base.Controller): {'disabled': True, 'disabled_reason': reason}) + def _update_forced_down(self, context, body): + """Set or unset forced_down flag for the service""" + try: + forced_down = strutils.bool_from_string(body['forced_down'], True) + except ValueError as err: + raise exception.InvalidValue(six.text_type(err)) + self._update(context, body['host'], body['binary'], + {"forced_down": forced_down}) + res = { + 'service': { + 'host': body['host'], + 'binary': body['binary'], + 'forced_down': forced_down + }, + } + return res + @pecan.expose('json') @exception.wrap_pecan_controller_exception def get_all(self, **kwargs): @@ -157,3 +177,13 @@ class ZunServiceController(base.Controller): else: reason = None return self._disable(context, kwargs, reason) + + @pecan.expose('json') + @exception.wrap_pecan_controller_exception + @validation.validate_query_param(pecan.request, + schema.query_param_force_down) + def force_down(self, **kwargs): + context = pecan.request.context + policy.enforce(context, "zun-service:force_down", + action="zun-service:force_down") + return self._update_forced_down(context, kwargs) diff --git a/zun/tests/unit/api/controllers/v1/test_zun_service.py b/zun/tests/unit/api/controllers/v1/test_zun_service.py index bca31d4a3..85b9bebf3 100644 --- a/zun/tests/unit/api/controllers/v1/test_zun_service.py +++ b/zun/tests/unit/api/controllers/v1/test_zun_service.py @@ -99,6 +99,22 @@ class TestZunServiceController(api_base.FunctionalTest): self.assertEqual('abc', response.json['service']['disabled_reason']) self.assertEqual(return_value, response.json) + @mock.patch.object(objects.ZunService, 'get_by_host_and_binary') + @mock.patch.object(objects.ZunService, 'update') + def test_force_down(self, mock_force_down, mock_get_host): + return_value = { + 'service': { + 'host': 'fake-host', + 'binary': 'fake-binary', + 'forced_down': True + }, + } + params = {'binary': 'fake-binary', 'host': 'fake-host', + 'forced_down': True} + response = self.put_json('/services/force_down', params) + self.assertTrue(response.json['service']['forced_down']) + self.assertEqual(return_value, response.json) + class TestZunServiceEnforcement(api_base.FunctionalTest):