160 lines
7.0 KiB
Python
160 lines
7.0 KiB
Python
# Copyright (c) 2016 Red Hat, 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 mock
|
|
from oslo_serialization import jsonutils
|
|
from six.moves import http_client
|
|
import webob
|
|
|
|
from cinder.api.v3 import router as router_v3
|
|
from cinder.api.v3 import workers
|
|
from cinder import context
|
|
from cinder import objects
|
|
from cinder import test
|
|
from cinder.tests.unit.api import fakes
|
|
from cinder.tests.unit import fake_constants as fake
|
|
|
|
|
|
SERVICES = (
|
|
[objects.Service(id=1, host='host1', binary='cinder-volume',
|
|
cluster_name='mycluster'),
|
|
objects.Service(id=2, host='host2', binary='cinder-volume',
|
|
cluster_name='mycluster')],
|
|
[objects.Service(id=3, host='host3', binary='cinder-volume',
|
|
cluster_name='mycluster'),
|
|
objects.Service(id=4, host='host4', binary='cinder-volume',
|
|
cluster_name='mycluster')],
|
|
)
|
|
|
|
|
|
def app():
|
|
# no auth, just let environ['cinder.context'] pass through
|
|
api = router_v3.APIRouter()
|
|
mapper = fakes.urlmap.URLMap()
|
|
mapper['/v3'] = api
|
|
return mapper
|
|
|
|
|
|
@ddt.ddt
|
|
class WorkersTestCase(test.TestCase):
|
|
"""Tes Case for the cleanup of Workers entries."""
|
|
def setUp(self):
|
|
super(WorkersTestCase, self).setUp()
|
|
|
|
self.context = context.RequestContext(user_id=None,
|
|
project_id=fake.PROJECT_ID,
|
|
is_admin=True,
|
|
read_deleted='no',
|
|
overwrite=False)
|
|
self.controller = workers.create_resource()
|
|
|
|
def _get_resp_post(self, body, version='3.24', ctxt=None):
|
|
"""Helper to execute a POST workers API call."""
|
|
req = webob.Request.blank('/v3/%s/workers/cleanup' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.headers['OpenStack-API-Version'] = 'volume ' + version
|
|
req.environ['cinder.context'] = ctxt or self.context
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(app())
|
|
return res
|
|
|
|
@mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.work_cleanup')
|
|
def test_cleanup_old_api_version(self, rpc_mock):
|
|
res = self._get_resp_post({}, '3.19')
|
|
self.assertEqual(http_client.NOT_FOUND, res.status_code)
|
|
rpc_mock.assert_not_called()
|
|
|
|
@mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.work_cleanup')
|
|
def test_cleanup_not_authorized(self, rpc_mock):
|
|
ctxt = context.RequestContext(user_id=None,
|
|
project_id=fake.PROJECT_ID,
|
|
is_admin=False,
|
|
read_deleted='no',
|
|
overwrite=False)
|
|
res = self._get_resp_post({}, ctxt=ctxt)
|
|
self.assertEqual(http_client.FORBIDDEN, res.status_code)
|
|
rpc_mock.assert_not_called()
|
|
|
|
@ddt.data({'fake_key': 'value'}, {'binary': 'nova-scheduler'},
|
|
{'disabled': 'sure'}, {'is_up': 'nop'},
|
|
{'resource_type': 'service'}, {'resource_id': 'non UUID'})
|
|
@mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.work_cleanup')
|
|
def test_cleanup_wrong_param(self, body, rpc_mock):
|
|
res = self._get_resp_post(body)
|
|
self.assertEqual(http_client.BAD_REQUEST, res.status_code)
|
|
if 'disabled' in body or 'is_up' in body:
|
|
expected = 'is not a boolean'
|
|
else:
|
|
expected = 'Invalid input'
|
|
self.assertIn(expected, res.json['badRequest']['message'])
|
|
rpc_mock.assert_not_called()
|
|
|
|
def _expected_services(self, cleaning, unavailable):
|
|
def service_view(service):
|
|
return {'id': service.id, 'host': service.host,
|
|
'binary': service.binary,
|
|
'cluster_name': service.cluster_name}
|
|
return {'cleaning': [service_view(s) for s in cleaning],
|
|
'unavailable': [service_view(s) for s in unavailable]}
|
|
|
|
@ddt.data({'service_id': 10}, {'cluster_name': 'cluster_name'},
|
|
{'host': 'hostname'}, {'binary': 'cinder-volume'},
|
|
{'binary': 'cinder-scheduler'}, {'disabled': 'true'},
|
|
{'is_up': 'no'}, {'resource_type': 'Volume'},
|
|
{'resource_id': fake.VOLUME_ID, 'host': 'hostname'})
|
|
@mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.work_cleanup',
|
|
return_value=SERVICES)
|
|
def test_cleanup_params(self, body, rpc_mock):
|
|
res = self._get_resp_post(body)
|
|
self.assertEqual(http_client.ACCEPTED, res.status_code)
|
|
rpc_mock.assert_called_once_with(self.context, mock.ANY)
|
|
cleanup_request = rpc_mock.call_args[0][1]
|
|
for key, value in body.items():
|
|
if key in ('disabled', 'is_up'):
|
|
value = value == 'true'
|
|
self.assertEqual(value, getattr(cleanup_request, key))
|
|
self.assertEqual(self._expected_services(*SERVICES), res.json)
|
|
|
|
@mock.patch('cinder.db.worker_get_all',
|
|
return_value=[mock.Mock(service_id=1, resource_type='Volume')])
|
|
@mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.work_cleanup',
|
|
return_value=SERVICES)
|
|
def test_cleanup_missing_location_ok(self, rpc_mock, worker_mock):
|
|
res = self._get_resp_post({'resource_id': fake.VOLUME_ID})
|
|
self.assertEqual(http_client.ACCEPTED, res.status_code)
|
|
rpc_mock.assert_called_once_with(self.context, mock.ANY)
|
|
cleanup_request = rpc_mock.call_args[0][1]
|
|
self.assertEqual(fake.VOLUME_ID, cleanup_request.resource_id)
|
|
self.assertEqual(1, cleanup_request.service_id)
|
|
self.assertEqual('Volume', cleanup_request.resource_type)
|
|
self.assertEqual(self._expected_services(*SERVICES), res.json)
|
|
|
|
@mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.work_cleanup')
|
|
def test_cleanup_missing_location_fail_none(self, rpc_mock):
|
|
res = self._get_resp_post({'resource_id': fake.VOLUME_ID})
|
|
self.assertEqual(http_client.BAD_REQUEST, res.status_code)
|
|
self.assertIn('Invalid input', res.json['badRequest']['message'])
|
|
rpc_mock.assert_not_called()
|
|
|
|
@mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.work_cleanup',
|
|
return_value=[1, 2])
|
|
def test_cleanup_missing_location_fail_multiple(self, rpc_mock):
|
|
res = self._get_resp_post({'resource_id': fake.VOLUME_ID})
|
|
self.assertEqual(http_client.BAD_REQUEST, res.status_code)
|
|
self.assertIn('Invalid input', res.json['badRequest']['message'])
|
|
rpc_mock.assert_not_called()
|