Merge "Support service-enable/disable"
This commit is contained in:
commit
6fe8bba969
|
@ -34,5 +34,7 @@
|
|||
|
||||
|
||||
"zun-service:delete": "rule:admin_api",
|
||||
"zun-service:disable": "rule:admin_api",
|
||||
"zun-service:enable": "rule:admin_api",
|
||||
"zun-service:get_all": "rule:admin_api"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# 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 zun.common.validation import parameter_types
|
||||
|
||||
query_param_enable = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'host': parameter_types.hostname,
|
||||
'binary': {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
},
|
||||
},
|
||||
'additionalProperties': False
|
||||
}
|
||||
|
||||
query_param_disable = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'host': parameter_types.hostname,
|
||||
'binary': {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
},
|
||||
'disabled_reason': {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
},
|
||||
},
|
||||
'additionalProperties': False
|
||||
}
|
|
@ -14,9 +14,11 @@ import pecan
|
|||
|
||||
from zun.api.controllers import base
|
||||
from zun.api.controllers.v1 import collection
|
||||
from zun.api.controllers.v1.schemas import services as schema
|
||||
from zun.api import servicegroup as svcgrp_api
|
||||
from zun.common import exception
|
||||
from zun.common import policy
|
||||
from zun.common import validation
|
||||
from zun import objects
|
||||
|
||||
|
||||
|
@ -51,10 +53,51 @@ class ZunServiceCollection(collection.Collection):
|
|||
class ZunServiceController(base.Controller):
|
||||
"""REST controller for zun-services."""
|
||||
|
||||
_custom_actions = {
|
||||
'enable': ['PUT'],
|
||||
'disable': ['PUT'],
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(ZunServiceController, self).__init__()
|
||||
self.servicegroup_api = svcgrp_api.ServiceGroup()
|
||||
|
||||
def _update(self, context, host, binary, payload):
|
||||
"""Do the actual update"""
|
||||
svc = objects.ZunService.get_by_host_and_binary(
|
||||
context, host, binary)
|
||||
if svc is None:
|
||||
raise exception.ZunServiceNotFound(
|
||||
binary=binary, host=host)
|
||||
else:
|
||||
return svc.update(context, payload)
|
||||
|
||||
def _enable_or_disable(self, context, body, params_to_update):
|
||||
"""Enable/Disable scheduling for a service."""
|
||||
self._update(context, body['host'], body['binary'],
|
||||
params_to_update)
|
||||
res = {
|
||||
'service': {
|
||||
'host': body['host'],
|
||||
'binary': body['binary'],
|
||||
'disabled': params_to_update['disabled'],
|
||||
'disabled_reason': params_to_update['disabled_reason']
|
||||
},
|
||||
}
|
||||
return res
|
||||
|
||||
def _enable(self, context, body):
|
||||
"""Enable scheduling for a service."""
|
||||
return self._enable_or_disable(context, body,
|
||||
{'disabled': False,
|
||||
'disabled_reason': None})
|
||||
|
||||
def _disable(self, context, body, reason=None):
|
||||
"""Disable scheduling for a service with optional log."""
|
||||
return self._enable_or_disable(context, body,
|
||||
{'disabled': True,
|
||||
'disabled_reason': reason})
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
def get_all(self, **kwargs):
|
||||
|
@ -90,3 +133,27 @@ class ZunServiceController(base.Controller):
|
|||
binary=binary, host=host)
|
||||
else:
|
||||
svc.destroy(context)
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
@validation.validate_query_param(pecan.request,
|
||||
schema.query_param_enable)
|
||||
def enable(self, **kwargs):
|
||||
context = pecan.request.context
|
||||
policy.enforce(context, "zun-service:enable",
|
||||
action="zun-service:enable")
|
||||
return self._enable(context, kwargs)
|
||||
|
||||
@pecan.expose('json')
|
||||
@exception.wrap_pecan_controller_exception
|
||||
@validation.validate_query_param(pecan.request,
|
||||
schema.query_param_disable)
|
||||
def disable(self, **kwargs):
|
||||
context = pecan.request.context
|
||||
policy.enforce(context, "zun-service:disable",
|
||||
action="zun-service:disable")
|
||||
if 'disabled_reason' in kwargs:
|
||||
reason = kwargs['disabled_reason']
|
||||
else:
|
||||
reason = None
|
||||
return self._disable(context, kwargs, reason)
|
||||
|
|
|
@ -161,6 +161,16 @@ exec_id = {
|
|||
'pattern': '^[a-f0-9]*$'
|
||||
}
|
||||
|
||||
hostname = {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
# NOTE: 'host' is defined in "services" table, and that
|
||||
# means a hostname. The hostname grammar in RFC952 does
|
||||
# not allow for underscores in hostnames. However, this
|
||||
# schema allows them, because it sometimes occurs in
|
||||
# real systems.
|
||||
'pattern': '^[a-zA-Z0-9-._]*$',
|
||||
}
|
||||
|
||||
SIGNALS = ['None']
|
||||
if sys.version_info >= (3, 5, 0):
|
||||
signals = [n for n in signal.Signals]
|
||||
|
|
|
@ -20,7 +20,8 @@ from zun.objects import base
|
|||
class ZunService(base.ZunPersistentObject, base.ZunObject):
|
||||
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
# Version 1.1: Add update method
|
||||
VERSION = '1.1'
|
||||
|
||||
fields = {
|
||||
'id': fields.IntegerField(),
|
||||
|
@ -150,3 +151,22 @@ class ZunService(base.ZunPersistentObject, base.ZunObject):
|
|||
"""
|
||||
self.report_count += 1
|
||||
self.save()
|
||||
|
||||
@base.remotable
|
||||
def update(self, context, kwargs):
|
||||
"""Update the ZunService, then save it.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: ZunService(context)
|
||||
"""
|
||||
if 'disabled' in kwargs:
|
||||
self.disabled = kwargs['disabled']
|
||||
if 'disabled_reason' in kwargs:
|
||||
self.disabled_reason = kwargs['disabled_reason']
|
||||
if 'forced_down' in kwargs:
|
||||
self.forced_down = kwargs['forced_down']
|
||||
self.save()
|
||||
|
|
|
@ -65,6 +65,40 @@ class TestZunServiceController(api_base.FunctionalTest):
|
|||
elem = response['services'][i]
|
||||
self.assertEqual(elem['id'], i + 1)
|
||||
|
||||
@mock.patch.object(objects.ZunService, 'get_by_host_and_binary')
|
||||
@mock.patch.object(objects.ZunService, 'update')
|
||||
def test_enable(self, mock_update, mock_get_host):
|
||||
return_value = {
|
||||
'service': {
|
||||
'host': 'fake-host',
|
||||
'binary': 'fake-binary',
|
||||
'disabled': False,
|
||||
'disabled_reason': None
|
||||
},
|
||||
}
|
||||
params = {'binary': 'fake-binary', 'host': 'fake-host'}
|
||||
response = self.put_json('/services/enable', params)
|
||||
self.assertFalse(response.json['service']['disabled'])
|
||||
self.assertEqual(return_value, response.json)
|
||||
|
||||
@mock.patch.object(objects.ZunService, 'get_by_host_and_binary')
|
||||
@mock.patch.object(objects.ZunService, 'update')
|
||||
def test_disable(self, mock_update, mock_get_host):
|
||||
return_value = {
|
||||
'service': {
|
||||
'host': 'fake-host',
|
||||
'binary': 'fake-binary',
|
||||
'disabled': True,
|
||||
'disabled_reason': 'abc'
|
||||
},
|
||||
}
|
||||
params = {'binary': 'fake-binary', 'host': 'fake-host',
|
||||
'disabled_reason': 'abc'}
|
||||
response = self.put_json('/services/disable', params)
|
||||
self.assertTrue(response.json['service']['disabled'])
|
||||
self.assertEqual('abc', response.json['service']['disabled_reason'])
|
||||
self.assertEqual(return_value, response.json)
|
||||
|
||||
|
||||
class TestZunServiceEnforcement(api_base.FunctionalTest):
|
||||
|
||||
|
|
|
@ -361,7 +361,7 @@ object_data = {
|
|||
'NUMATopology': '1.0-b54086eda7e4b2e6145ecb6ee2c925ab',
|
||||
'ResourceClass': '1.1-d661c7675b3cd5b8c3618b68ba64324e',
|
||||
'ResourceProvider': '1.0-92b427359d5a4cf9ec6c72cbe630ee24',
|
||||
'ZunService': '1.0-2a19ab9987a746621b2ada02d8aadf22',
|
||||
'ZunService': '1.1-b1549134bfd5271daec417ca8cabc77e',
|
||||
'ComputeNode': '1.5-0d6ff86fbdc03859b77f1092e785ce10',
|
||||
}
|
||||
|
||||
|
|
|
@ -132,3 +132,24 @@ class TestZunServiceObject(base.DbTestCase):
|
|||
self.fake_zun_service['host'],
|
||||
self.fake_zun_service['binary'],
|
||||
{'report_count': last_report_count + 1})
|
||||
|
||||
def test_update(self):
|
||||
with mock.patch.object(self.dbapi,
|
||||
'get_zun_service',
|
||||
autospec=True) as mock_get_zun_service:
|
||||
mock_get_zun_service.return_value = self.fake_zun_service
|
||||
with mock.patch.object(self.dbapi,
|
||||
'update_zun_service',
|
||||
autospec=True) as mock_update_ms:
|
||||
ms = objects.ZunService.get_by_host_and_binary(
|
||||
self.context, 'fake-host', 'fake-bin')
|
||||
kw = {'disabled': True, 'disabled_reason': 'abc'}
|
||||
ms.update(self.context, kw)
|
||||
mock_get_zun_service.assert_called_once_with(
|
||||
'fake-host', 'fake-bin')
|
||||
mock_update_ms.assert_called_once_with(
|
||||
self.fake_zun_service['host'],
|
||||
self.fake_zun_service['binary'],
|
||||
{'disabled': True,
|
||||
'disabled_reason': 'abc'})
|
||||
self.assertEqual(self.context, ms._context)
|
||||
|
|
Loading…
Reference in New Issue