API for ensure shares

Introduces a new API to allow OpenStack administrators to start
the share manager's ensure shares operation.

Also, introduced a new configuration option named
`update_shares_status_on_ensure`, so administrators can define
whether the shares status should be updated during the ensure
shares operation or not.

A new column was added to the services table, named `ensuring`.
Through this field, we can identify whether there is an undergoing
ensure shares operation or not.

Closes-Bug: #1996793
Partially-Implements: bp ensure-shares-api
Change-Id: If7bf059eb8581f20a3ceb7c1af93558774f4ef5e
This commit is contained in:
Carlos Eduardo 2024-07-16 16:01:34 -03:00
parent 4bf505404a
commit 101becf9b4
20 changed files with 556 additions and 34 deletions

View File

@ -203,13 +203,14 @@ REST_API_VERSION_HISTORY = """
* 2.83 - Added 'disabled_reason' field to services.
* 2.84 - Added mount_point_name to shares.
* 2.85 - Added backup_type field to share backups.
* 2.86 - Add ensure share API.
"""
# The minimum and maximum versions of the API supported
# The default api version request is defined to be the
# minimum version of the API supported.
_MIN_API_VERSION = "2.0"
_MAX_API_VERSION = "2.85"
_MAX_API_VERSION = "2.86"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -458,3 +458,7 @@ user documentation.
2.85
----
Added ``backup_type`` field to share backup object.
2.86
----
Added ensure shares API.

View File

@ -102,6 +102,13 @@ class APIRouter(manila.api.openstack.APIRouter):
mapper.resource("service",
"services",
controller=self.resources["services"])
for path_prefix in ['/{project_id}', '']:
# project_id is optional
mapper.connect("services",
"%s/services/ensure-shares" % path_prefix,
controller=self.resources["services"],
action="ensure_shares",
conditions={"method": ["POST"]})
self.resources["quota_sets_legacy"] = (
quota_sets.create_resource_legacy())

View File

@ -14,13 +14,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import http.client as http_client
from oslo_utils import strutils
import webob.exc
from manila.api.openstack import wsgi
from manila.api.views import services as services_views
from manila import db
from manila import exception
from manila.i18n import _
from manila import policy
from manila.services import api as service_api
class ServiceMixin(object):
@ -34,7 +38,7 @@ class ServiceMixin(object):
_view_builder_class = services_views.ViewBuilder
@wsgi.Controller.authorize("index")
def _index(self, req):
def _index(self, req, support_ensure_shares=False):
"""Return a list of all running services."""
context = req.environ['manila.context']
@ -42,7 +46,7 @@ class ServiceMixin(object):
services = []
for service in all_services:
service = {
service_data = {
'id': service['id'],
'binary': service['binary'],
'host': service['host'],
@ -52,7 +56,9 @@ class ServiceMixin(object):
'state': service['state'],
'updated_at': service['updated_at'],
}
services.append(service)
if support_ensure_shares:
service_data['ensuring'] = service['ensuring']
services.append(service_data)
search_opts = [
'host',
@ -141,10 +147,18 @@ class ServiceController(ServiceMixin, wsgi.Controller):
Registered under API URL 'services'.
"""
@wsgi.Controller.api_version('2.7')
def __init__(self):
super().__init__()
self.service_api = service_api.API()
@wsgi.Controller.api_version('2.7', '2.85')
def index(self, req):
return self._index(req)
@wsgi.Controller.api_version('2.86') # noqa
def index(self, req): # pylint: disable=function-redefined # noqa F811
return self._index(req, support_ensure_shares=True)
@wsgi.Controller.api_version('2.7', '2.82')
def update(self, req, id, body):
return self._update(req, id, body, support_disabled_reason=False)
@ -153,6 +167,32 @@ class ServiceController(ServiceMixin, wsgi.Controller):
def update(self, req, id, body): # pylint: disable=function-redefined # noqa F811
return self._update(req, id, body)
@wsgi.Controller.api_version('2.86')
@wsgi.Controller.authorize
def ensure_shares(self, req, body):
"""Starts ensure shares for a given manila-share binary."""
context = req.environ['manila.context']
policy.check_policy(context, 'service', 'ensure_shares')
host = body.get('host', None)
if not host:
raise webob.exc.HTTPBadRequest('Missing host parameter.')
try:
# The only binary supported is Manila share.
service = db.service_get_by_args(context, host, 'manila-share')
except exception.NotFound:
raise webob.exc.HTTPNotFound(
"manila-share binary for '%s' host not found" % id
)
try:
self.service_api.ensure_shares(context, service, host)
except webob.exc.HTTPConflict:
raise
return webob.Response(status_int=http_client.ACCEPTED)
def create_resource_legacy():
return wsgi.Resource(ServiceControllerLegacy())

View File

@ -21,6 +21,7 @@ class ViewBuilder(common.ViewBuilder):
_collection_name = "services"
_detail_version_modifiers = [
"add_disabled_reason_field",
"add_ensuring_field",
]
def summary(self, request, service):
@ -49,3 +50,7 @@ class ViewBuilder(common.ViewBuilder):
service_dict.pop('disabled', None)
service_dict['status'] = service.get('status')
service_dict['disabled_reason'] = service.get('disabled_reason')
@common.ViewBuilder.versioned_method("2.86")
def add_ensuring_field(self, context, service_dict, service):
service_dict['ensuring'] = service.get('ensuring')

View File

@ -147,6 +147,11 @@ global_opts = [
'(element of the list is <driver_updatable_key>, '
'i.e max_files) can be passed to share drivers as part '
'of metadata create/update operations.'),
cfg.BoolOpt('update_shares_status_on_ensure',
default=True,
help='Whether Manila should update the status of all shares '
'within a backend during ongoing ensure_shares '
'run.'),
]
CONF.register_opts(global_opts)

View File

@ -53,6 +53,7 @@ STATUS_AWAITING_TRANSFER = 'awaiting_transfer'
STATUS_BACKUP_CREATING = 'backup_creating'
STATUS_BACKUP_RESTORING = 'backup_restoring'
STATUS_BACKUP_RESTORING_ERROR = 'backup_restoring_error'
STATUS_ENSURING = 'ensuring'
# Transfer resource type
SHARE_RESOURCE_TYPE = 'share'
@ -144,6 +145,7 @@ TRANSITIONAL_STATUSES = (
STATUS_RESTORING, STATUS_REVERTING,
STATUS_SERVER_MIGRATING, STATUS_SERVER_MIGRATING_TO,
STATUS_BACKUP_RESTORING, STATUS_BACKUP_CREATING,
STATUS_ENSURING,
)
INVALID_SHARE_INSTANCE_STATUSES_FOR_ACCESS_RULE_UPDATES = (

View File

@ -0,0 +1,48 @@
# 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.
"""add-ensuring-field-to-services
Revision ID: cdefa6287df8
Revises: 2f27d904214c
Create Date: 2024-07-15 14:29:16.733696
"""
# revision identifiers, used by Alembic.
revision = 'cdefa6287df8'
down_revision = '2f27d904214c'
from alembic import op
from oslo_log import log
import sqlalchemy as sa
LOG = log.getLogger(__name__)
def upgrade():
try:
op.add_column('services', sa.Column(
'ensuring', sa.Boolean,
nullable=False, server_default=sa.sql.false()))
except Exception:
LOG.error("Column services.ensuring not created!")
raise
def downgrade():
try:
op.drop_column('services', 'ensuring')
except Exception:
LOG.error("Column shares.ensuring not dropped!")
raise

View File

@ -72,6 +72,7 @@ class Service(BASE, ManilaBase):
availability_zone_id = Column(String(36),
ForeignKey('availability_zones.id'),
nullable=True)
ensuring = Column(Boolean, default=False)
availability_zone = orm.relationship(
"AvailabilityZone",

View File

@ -34,6 +34,12 @@ deprecated_service_update = policy.DeprecatedRule(
deprecated_reason=DEPRECATED_REASON,
deprecated_since=versionutils.deprecated.WALLABY
)
deprecated_service_ensure = policy.DeprecatedRule(
name=BASE_POLICY_NAME % 'ensure_shares',
check_str=base.RULE_ADMIN_API,
deprecated_reason=DEPRECATED_REASON,
deprecated_since='2024.2/Dalmatian'
)
service_policies = [
@ -79,6 +85,19 @@ service_policies = [
],
deprecated_rule=deprecated_service_update
),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'ensure_shares',
check_str=base.ADMIN,
scope_types=['project'],
description="Run ensure shares for a manila-share binary.",
operations=[
{
'method': 'POST',
'path': '/services/ensure',
}
],
deprecated_rule=deprecated_service_ensure
),
]

View File

38
manila/services/api.py Normal file
View File

@ -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 oslo_config import cfg
from webob import exc
from manila.db import base
from manila.share import rpcapi as share_rpcapi
CONF = cfg.CONF
class API(base.Base):
"""API for handling service actions."""
def __init__(self):
super(API, self).__init__()
self.share_rpcapi = share_rpcapi.ShareAPI()
def ensure_shares(self, context, service, host):
"""Start the ensure shares in a given host."""
if service['state'] != "up":
raise exc.HTTPConflict(
"The service must have its state set to 'up' prior to running "
"ensure shares.")
self.share_rpcapi.ensure_driver_resources(context, host)

View File

@ -264,7 +264,7 @@ def add_hooks(f):
class ShareManager(manager.SchedulerDependentManager):
"""Manages NAS storages."""
RPC_API_VERSION = '1.28'
RPC_API_VERSION = '1.29'
def __init__(self, share_driver=None, service_name=None, *args, **kwargs):
"""Load the driver from args, or from flags."""
@ -401,7 +401,8 @@ class ShareManager(manager.SchedulerDependentManager):
"""
return self.driver.initialized
def ensure_driver_resources(self, ctxt):
def ensure_driver_resources(self, ctxt, skip_backend_info_check=False):
update_instances_status = CONF.update_shares_status_on_ensure
old_backend_info = self.db.backend_info_get(ctxt, self.host)
old_backend_info_hash = (old_backend_info.get('info_hash')
if old_backend_info is not None else None)
@ -409,31 +410,33 @@ class ShareManager(manager.SchedulerDependentManager):
new_backend_info_hash = None
backend_info_implemented = True
update_share_instances = []
try:
new_backend_info = self.driver.get_backend_info(ctxt)
except Exception as e:
if not isinstance(e, NotImplementedError):
LOG.exception(
"The backend %(host)s could not get backend info.",
{'host': self.host})
raise
else:
backend_info_implemented = False
LOG.debug(
("The backend %(host)s does not support get backend"
" info method."),
{'host': self.host})
if not skip_backend_info_check:
try:
new_backend_info = self.driver.get_backend_info(ctxt)
except Exception as e:
if not isinstance(e, NotImplementedError):
LOG.exception(
"The backend %(host)s could not get backend info.",
{'host': self.host})
raise
else:
backend_info_implemented = False
LOG.debug(
("The backend %(host)s does not support get backend"
" info method."),
{'host': self.host})
if new_backend_info:
new_backend_info_hash = hashlib.sha1(str(
sorted(new_backend_info.items())).encode('utf-8')).hexdigest()
if (old_backend_info_hash == new_backend_info_hash and
backend_info_implemented):
LOG.debug(
("Ensure shares is being skipped because the %(host)s's old "
"backend info is the same as its new backend info."),
{'host': self.host})
return
if new_backend_info:
new_backend_info_hash = hashlib.sha1(
str(sorted(new_backend_info.items())).encode(
'utf-8')).hexdigest()
if ((old_backend_info_hash == new_backend_info_hash and
backend_info_implemented) and not skip_backend_info_check):
LOG.debug(
("Ensure shares is being skipped because the %(host)s's "
"old backend info is the same as its new backend info."),
{'host': self.host})
return
share_instances = self.db.share_instance_get_all_by_host(
ctxt, self.host)
@ -467,7 +470,19 @@ class ShareManager(manager.SchedulerDependentManager):
ctxt, share_instance)
update_share_instances.append(share_instance_dict)
do_service_status_update = False
if update_share_instances:
# No reason to update the shares status if nothing will be done.
do_service_status_update = True
service = self.db.service_get_by_args(
ctxt, self.host, 'manila-share')
self.db.service_update(ctxt, service['id'], {'ensuring': True})
if update_instances_status:
for instance in update_share_instances:
self.db.share_instance_update(
ctxt, instance['id'],
{'status': constants.STATUS_ENSURING}
)
try:
update_share_instances = self.driver.ensure_shares(
ctxt, update_share_instances) or {}
@ -494,10 +509,11 @@ class ShareManager(manager.SchedulerDependentManager):
share_instance_update_dict = (
update_share_instances[share_instance['id']]
)
if share_instance_update_dict.get('status'):
backend_provided_status = share_instance_update_dict.get('status')
if backend_provided_status:
self.db.share_instance_update(
ctxt, share_instance['id'],
{'status': share_instance_update_dict.get('status'),
{'status': backend_provided_status,
'host': share_instance['host']}
)
metadata_updates = share_instance_update_dict.get('metadata')
@ -568,6 +584,13 @@ class ShareManager(manager.SchedulerDependentManager):
"Unexpected error occurred while updating "
"access rules for snapshot instance %s.",
snap_instance['id'])
if not backend_provided_status and update_instances_status:
self.db.share_instance_update(
ctxt, share_instance['id'],
{'status': constants.STATUS_AVAILABLE}
)
if do_service_status_update:
self.db.service_update(ctxt, service['id'], {'ensuring': False})
def _ensure_share(self, ctxt, share_instance):
export_locations = None

View File

@ -89,6 +89,7 @@ class ShareAPI(object):
restore_backup() methods
1.27 - Update delete_share_instance() and delete_snapshot() methods
1.28 - Add update_share_from_metadata() method
1.29 - Add ensure_shares()
"""
BASE_RPC_API_VERSION = '1.0'
@ -97,7 +98,7 @@ class ShareAPI(object):
super(ShareAPI, self).__init__()
target = messaging.Target(topic=CONF.share_topic,
version=self.BASE_RPC_API_VERSION)
self.client = rpc.get_client(target, version_cap='1.28')
self.client = rpc.get_client(target, version_cap='1.29')
def create_share_instance(self, context, share_instance, host,
request_spec, filter_properties,
@ -540,3 +541,12 @@ class ShareAPI(object):
'update_share_from_metadata',
share_id=share['id'],
metadata=metadata)
def ensure_driver_resources(self, context, host):
host = utils.extract_host(host)
call_context = self.client.prepare(server=host, version='1.29')
return call_context.cast(
context,
'ensure_driver_resources',
skip_backend_info_check=True
)

View File

@ -17,6 +17,7 @@
import datetime
from unittest import mock
import webob
import ddt
from oslo_utils import timeutils
@ -158,6 +159,7 @@ fake_response_service_list_with_disabled_reason = {'services': [
'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38),
},
]}
ENSURE_SHARES_VERSION = "2.86"
def fake_service_get_all(context):
@ -386,3 +388,99 @@ class ServicesTest(test.TestCase):
self.assertRaises(
exception.VersionNotFoundForAPIMethod, controller().index, req)
def test_ensure_shares_no_host_param(self):
req = fakes.HTTPRequest.blank(
'/fooproject/services/ensure', version=ENSURE_SHARES_VERSION)
body = {}
self.assertRaises(
webob.exc.HTTPBadRequest,
self.controller.ensure_shares,
req,
body
)
def test_ensure_shares_host_not_found(self):
req = fakes.HTTPRequest.blank(
'/fooproject/services/ensure', version=ENSURE_SHARES_VERSION)
req_context = req.environ['manila.context']
body = {'host': 'host1'}
mock_service_get = self.mock_object(
db, 'service_get_by_args',
mock.Mock(side_effect=exception.NotFound())
)
self.assertRaises(
webob.exc.HTTPNotFound,
self.controller.ensure_shares,
req,
body
)
mock_service_get.assert_called_once_with(
req_context,
body['host'],
'manila-share'
)
def test_ensure_shares_conflict(self):
req = fakes.HTTPRequest.blank(
'/fooproject/services/ensure', version=ENSURE_SHARES_VERSION)
req_context = req.environ['manila.context']
body = {'host': 'host1'}
fake_service = {'id': 'fake_service_id'}
mock_service_get = self.mock_object(
db,
'service_get_by_args',
mock.Mock(return_value=fake_service)
)
mock_ensure = self.mock_object(
self.controller.service_api,
'ensure_shares',
mock.Mock(side_effect=webob.exc.HTTPConflict)
)
self.assertRaises(
webob.exc.HTTPConflict,
self.controller.ensure_shares,
req,
body
)
mock_service_get.assert_called_once_with(
req_context,
body['host'],
'manila-share'
)
mock_ensure.assert_called_once_with(
req_context, fake_service, body['host']
)
def test_ensure_shares(self):
req = fakes.HTTPRequest.blank(
'/fooproject/services/ensure', version=ENSURE_SHARES_VERSION)
req_context = req.environ['manila.context']
body = {'host': 'host1'}
fake_service = {'id': 'fake_service_id'}
mock_service_get = self.mock_object(
db,
'service_get_by_args',
mock.Mock(return_value=fake_service)
)
mock_ensure = self.mock_object(
self.controller.service_api, 'ensure_shares',
)
response = self.controller.ensure_shares(req, body)
self.assertEqual(202, response.status_int)
mock_service_get.assert_called_once_with(
req_context,
body['host'],
'manila-share'
)
mock_ensure.assert_called_once_with(
req_context, fake_service, body['host']
)

View File

View File

@ -0,0 +1,61 @@
# 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 unittest import mock
from webob import exc
from manila import context
from manila.services import api as service_api
from manila import test
class ServicesApiTest(test.TestCase):
def setUp(self):
super(ServicesApiTest, self).setUp()
self.context = context.get_admin_context()
self.share_rpcapi = mock.Mock()
self.share_rpcapi.ensure_shares = mock.Mock()
self.services_api = service_api.API()
self.mock_object(
self.services_api, 'share_rpcapi', self.share_rpcapi
)
def test_ensure_shares(self):
host = 'fake_host@fakebackend'
fake_service = {
'id': 'fake_service_id',
'state': 'up'
}
self.services_api.ensure_shares(self.context, fake_service, host)
self.share_rpcapi.ensure_driver_resources.assert_called_once_with(
self.context, host
)
def test_ensure_shares_host_down(self):
host = 'fake_host@fakebackend'
fake_service = {
'id': 'fake_service_id',
'state': 'down'
}
self.assertRaises(
exc.HTTPConflict,
self.services_api.ensure_shares,
self.context,
fake_service,
host
)
self.share_rpcapi.ensure_shares.assert_not_called()

View File

@ -208,6 +208,11 @@ class ShareManagerTestCase(test.TestCase):
'reapply_access_rules': driver_needs_to_reapply_rules,
},
}
fake_service = {'id': 'fake_service_id', 'binary': 'manila-share'}
self.mock_object(self.share_manager.db,
'service_get_by_args',
mock.Mock(return_value=fake_service))
self.mock_object(self.share_manager.db, 'service_update')
mock_backend_info_update = self.mock_object(
self.share_manager.db, 'backend_info_update')
mock_share_get_all_by_host = self.mock_object(
@ -263,6 +268,23 @@ class ShareManagerTestCase(test.TestCase):
mock.call(utils.IsAMatcher(context.RequestContext),
instances[2]),
])
self.share_manager.db.service_get_by_args.assert_called_once_with(
utils.IsAMatcher(context.RequestContext),
self.share_manager.host,
'manila-share'
)
self.share_manager.db.service_update.assert_has_calls([
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': True}
),
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': False}
)
])
if driver_needs_to_reapply_rules:
# don't care if share_instance['access_rules_status'] is "syncing"
mock_reset_rules_method.assert_has_calls([
@ -301,6 +323,11 @@ class ShareManagerTestCase(test.TestCase):
'metadata': metadata_updates,
},
}
fake_service = {'id': 'fake_service_id', 'binary': 'manila-share'}
self.mock_object(self.share_manager.db,
'service_get_by_args',
mock.Mock(return_value=fake_service))
self.mock_object(self.share_manager.db, 'service_update')
mock_backend_info_update = self.mock_object(
self.share_manager.db, 'backend_info_update')
mock_share_get_all_by_host = self.mock_object(
@ -359,6 +386,23 @@ class ShareManagerTestCase(test.TestCase):
])
# none of the share instances in the fake data have syncing rules
mock_reset_rules_method.assert_not_called()
self.share_manager.db.service_get_by_args.assert_called_once_with(
utils.IsAMatcher(context.RequestContext),
self.share_manager.host,
'manila-share'
)
self.share_manager.db.service_update.assert_has_calls([
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': True}
),
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': False}
)
])
def test_init_host_with_no_shares(self):
self.mock_object(self.share_manager.db,
@ -520,6 +564,7 @@ class ShareManagerTestCase(test.TestCase):
}
instances[0]['access_rules_status'] = ''
instances[2]['access_rules_status'] = ''
fake_service = {'id': 'fake_service_id', 'binary': 'manila-share'}
self.mock_object(self.share_manager.db,
'backend_info_get',
mock.Mock(return_value=old_backend_info))
@ -530,6 +575,10 @@ class ShareManagerTestCase(test.TestCase):
mock_share_get_all_by_host = self.mock_object(
self.share_manager.db, 'share_instance_get_all_by_host',
mock.Mock(return_value=instances))
self.mock_object(self.share_manager.db,
'service_get_by_args',
mock.Mock(return_value=fake_service))
self.mock_object(self.share_manager.db, 'service_update')
self.mock_object(self.share_manager.db, 'share_instance_get',
mock.Mock(side_effect=[instances[0], instances[2],
instances[4]]))
@ -607,6 +656,23 @@ class ShareManagerTestCase(test.TestCase):
mock.call(mock.ANY, instances[2]['id'],
share_server=share_server),
]))
self.share_manager.db.service_get_by_args.assert_called_once_with(
utils.IsAMatcher(context.RequestContext),
self.share_manager.host,
'manila-share'
)
self.share_manager.db.service_update.assert_has_calls([
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': True}
),
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': False}
)
])
@ddt.data(("some_hash", {"db_version": "test_version"}),
("ddd86ec90923b686597501e2f2431f3af59238c0",
@ -623,6 +689,7 @@ class ShareManagerTestCase(test.TestCase):
new_backend_info else None)
mock_backend_info_update = self.mock_object(
self.share_manager.db, 'backend_info_update')
fake_service = {'id': 'fake_service_id', 'binary': 'manila-share'}
self.mock_object(
self.share_manager.db, 'backend_info_get',
mock.Mock(return_value=old_backend_info))
@ -630,6 +697,10 @@ class ShareManagerTestCase(test.TestCase):
mock.Mock(return_value=new_backend_info))
self.mock_object(self.share_manager, 'publish_service_capabilities',
mock.Mock())
self.mock_object(self.share_manager.db,
'service_get_by_args',
mock.Mock(return_value=fake_service))
self.mock_object(self.share_manager.db, 'service_update')
mock_ensure_shares = self.mock_object(
self.share_manager.driver, 'ensure_shares')
mock_share_instance_get_all_by_host = self.mock_object(
@ -665,6 +736,7 @@ class ShareManagerTestCase(test.TestCase):
instances = self._setup_init_mocks(setup_access_rules=False)
share_server = fakes.fake_share_server_get()
fake_service = {'id': 'fake_service_id', 'binary': 'manila-share'}
self.mock_object(self.share_manager.db,
'share_instance_get_all_by_host',
mock.Mock(return_value=instances))
@ -683,6 +755,10 @@ class ShareManagerTestCase(test.TestCase):
self.mock_object(self.share_manager, '_get_share_server_dict',
mock.Mock(return_value=share_server))
self.mock_object(self.share_manager, 'publish_service_capabilities')
self.mock_object(self.share_manager.db,
'service_get_by_args',
mock.Mock(return_value=fake_service))
self.mock_object(self.share_manager.db, 'service_update')
self.mock_object(manager.LOG, 'error')
self.mock_object(manager.LOG, 'info')
@ -730,6 +806,23 @@ class ShareManagerTestCase(test.TestCase):
mock.ANY,
{'id': instances[1]['id'], 'status': instances[1]['status']},
)
self.share_manager.db.service_get_by_args.assert_called_once_with(
utils.IsAMatcher(context.RequestContext),
self.share_manager.host,
'manila-share'
)
self.share_manager.db.service_update.assert_has_calls([
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': True}
),
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': False}
)
])
def _get_share_instance_dict(self, share_instance, **kwargs):
# TODO(gouthamr): remove method when the db layer returns primitives
@ -775,11 +868,16 @@ class ShareManagerTestCase(test.TestCase):
raise exception.ManilaException(message="Fake raise")
instances = self._setup_init_mocks(setup_access_rules=False)
fake_service = {'id': 'fake_service_id', 'binary': 'manila-share'}
mock_ensure_share = self.mock_object(
self.share_manager.driver, 'ensure_share')
self.mock_object(self.share_manager.db,
'share_instance_get_all_by_host',
mock.Mock(return_value=instances))
self.mock_object(self.share_manager.db,
'service_get_by_args',
mock.Mock(return_value=fake_service))
self.mock_object(self.share_manager.db, 'service_update')
self.mock_object(self.share_manager.db, 'share_instance_get',
mock.Mock(side_effect=[instances[0], instances[2],
instances[3]]))
@ -808,6 +906,20 @@ class ShareManagerTestCase(test.TestCase):
mock.call(utils.IsAMatcher(context.RequestContext), instances[0]),
mock.call(utils.IsAMatcher(context.RequestContext), instances[2]),
])
self.share_manager.db.service_get_by_args.assert_called_once_with(
utils.IsAMatcher(context.RequestContext), self.share_manager.host,
'manila-share'
)
self.share_manager.db.service_update.assert_has_calls([
mock.call(
utils.IsAMatcher(context.RequestContext), fake_service['id'],
{'ensuring': True}
),
mock.call(
utils.IsAMatcher(context.RequestContext), fake_service['id'],
{'ensuring': False}
)
])
self.share_manager.driver.ensure_shares.assert_called_once_with(
utils.IsAMatcher(context.RequestContext),
[dict_instances[0], dict_instances[2], dict_instances[3]])
@ -854,11 +966,16 @@ class ShareManagerTestCase(test.TestCase):
instances[4]['id']: {'status': 'available'}
}
smanager = self.share_manager
fake_service = {'id': 'fake_service_id', 'binary': 'manila-share'}
self.mock_object(smanager.db, 'share_instance_get_all_by_host',
mock.Mock(return_value=instances))
self.mock_object(self.share_manager.db, 'share_instance_get',
mock.Mock(side_effect=[instances[0], instances[2],
instances[4]]))
self.mock_object(self.share_manager.db,
'service_get_by_args',
mock.Mock(return_value=fake_service))
self.mock_object(self.share_manager.db, 'service_update')
self.mock_object(self.share_manager.driver, 'ensure_share',
mock.Mock(return_value=None))
self.mock_object(self.share_manager.driver, 'ensure_shares',
@ -915,6 +1032,23 @@ class ShareManagerTestCase(test.TestCase):
manager.LOG.exception.assert_has_calls([
mock.call(mock.ANY, mock.ANY),
])
self.share_manager.db.service_get_by_args.assert_called_once_with(
utils.IsAMatcher(context.RequestContext),
self.share_manager.host,
'manila-share'
)
self.share_manager.db.service_update.assert_has_calls([
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': True}
),
mock.call(
utils.IsAMatcher(context.RequestContext),
fake_service['id'],
{'ensuring': False}
)
])
def test_create_share_instance_from_snapshot_with_server(self):
"""Test share can be created from snapshot if server exists."""

View File

@ -147,6 +147,8 @@ class ShareRpcAPITestCase(test.TestCase):
and method in src_dest_share_server_methods):
share_server = expected_msg.pop('dest_share_server', None)
expected_msg['dest_share_server_id'] = share_server['id']
if method == 'ensure_driver_resources':
expected_msg['skip_backend_info_check'] = True
if 'host' in kwargs:
host = kwargs['host']
@ -527,3 +529,11 @@ class ShareRpcAPITestCase(test.TestCase):
dest_host=self.fake_host,
share_network_id='fake_net_id',
new_share_network_subnet_id='new_share_network_subnet_id')
def test_ensure_driver_resources(self):
self._test_share_api(
'ensure_driver_resources',
rpc_method='cast',
version='1.29',
host=self.fake_host,
)

View File

@ -0,0 +1,16 @@
---
features:
- |
A new API to start the ensure shares procedure for Manila has been added.
Through this API, OpenStack administrators will be able to recalculate the
shares' export location without restarting the shares manager service.
Additionally, a new configuration option named
`update_shares_status_on_ensure` is now available to help OpenStack
administrators determine whether the shares' status should be modified
during the ensure shares procedure or not.
upgrade:
- |
When restarting the service on an upgrade, when ensure shares is being run
it will automatically transition the shares status to `ensuring`. In case
you would like to prevent it, please change the value of the
`update_shares_status_on_ensure` configuration option.