Browse Source

Add manage/unmanage of shares in DHSS=True

This patch adds Manage/Unmanage of share servers in
Manila. It also updates the Manage Share API to accept
a "share_server_id" parameter, and updates Unmanage
of Share and Snapshots API to allow unmanaging of
shares and snapshots in DHSS=True.

Managed share servers are not deleted automatically
by manila, and if a single share is unmanaged in
DHSS=True, the respective share server will not be
deleted automatically as well.

Managing share servers require that the driver
implements 2 functions:
- get_share_server_network_info: obtain IPs from
  share server.
- manage_server: perform required operations to
  manage and return dict of backend_details.

Unmanaging share servers require that the driver
overrides unmanage_server function.

The IPs obtained from the backend are validated
by the Network plugin, so ports with the exact
IPs must exist in the subnet and admin subnet
associated with the share network specified
when managing the share server (unless
StandaloneNetworkPlugin is used).

It is recommended to rename the backend resource
if possible when managing the share server, to
prevent issues with re-managing a share server
that has already been managed.

This patch bumps the API microversion to 2.49.

APIImpact
DocImpact

Depends-On: I17c74b2aa242918188eeff368232c762a4b31093
Partially-implements: bp manage-unmanage-with-share-servers
Change-Id: I108961e7436ba13550ef2b8f02079c6e599a6166
changes/31/635831/33
Rodrigo Barbieri 2 years ago
parent
commit
d877b61c5e
44 changed files with 3326 additions and 355 deletions
  1. +8
    -1
      contrib/ci/post_test_hook.sh
  2. +4
    -1
      manila/api/openstack/api_version_request.py
  3. +5
    -0
      manila/api/openstack/rest_api_version_history.rst
  4. +14
    -10
      manila/api/v1/share_manage.py
  5. +9
    -9
      manila/api/v1/share_servers.py
  6. +5
    -5
      manila/api/v1/share_unmanage.py
  7. +8
    -2
      manila/api/v2/router.py
  8. +175
    -0
      manila/api/v2/share_servers.py
  9. +11
    -7
      manila/api/v2/share_snapshots.py
  10. +20
    -11
      manila/api/v2/shares.py
  11. +20
    -5
      manila/api/views/share_servers.py
  12. +15
    -2
      manila/db/api.py
  13. +72
    -0
      manila/db/migrations/alembic/versions/6a3fd2984bc31_add_is_auto_deletable_and_identifier_fields_for_share_servers.py
  14. +52
    -4
      manila/db/sqlalchemy/api.py
  15. +9
    -4
      manila/db/sqlalchemy/models.py
  16. +4
    -0
      manila/exception.py
  17. +9
    -0
      manila/network/__init__.py
  18. +121
    -2
      manila/network/neutron/neutron_network_plugin.py
  19. +43
    -0
      manila/network/standalone_network_plugin.py
  20. +30
    -0
      manila/policies/share_server.py
  21. +89
    -0
      manila/share/api.py
  22. +169
    -0
      manila/share/driver.py
  23. +243
    -80
      manila/share/manager.py
  24. +18
    -1
      manila/share/rpcapi.py
  25. +18
    -0
      manila/tests/api/fakes.py
  26. +56
    -0
      manila/tests/api/v1/test_share_manage.py
  27. +56
    -25
      manila/tests/api/v1/test_share_servers.py
  28. +20
    -0
      manila/tests/api/v1/test_share_unmanage.py
  29. +390
    -0
      manila/tests/api/v2/test_share_servers.py
  30. +45
    -0
      manila/tests/api/v2/test_share_snapshots.py
  31. +26
    -0
      manila/tests/api/v2/test_shares.py
  32. +41
    -0
      manila/tests/db/migrations/alembic/migrations_data_checks.py
  33. +149
    -0
      manila/tests/db/sqlalchemy/test_api.py
  34. +2
    -2
      manila/tests/fake_driver.py
  35. +18
    -0
      manila/tests/fake_share.py
  36. +215
    -2
      manila/tests/network/neutron/test_neutron_plugin.py
  37. +64
    -0
      manila/tests/network/test_standalone_network_plugin.py
  38. +87
    -3
      manila/tests/share/drivers/dummy.py
  39. +312
    -0
      manila/tests/share/test_api.py
  40. +572
    -179
      manila/tests/share/test_manager.py
  41. +20
    -0
      manila/tests/share/test_rpcapi.py
  42. +10
    -0
      manila/tests/test_exception.py
  43. +48
    -0
      manila/tests/test_network.py
  44. +24
    -0
      releasenotes/notes/manage-unmanage-share-servers-cd4a6523d8e9fbdf.yaml

+ 8
- 1
contrib/ci/post_test_hook.sh View File

@ -163,6 +163,12 @@ if [[ "$DRIVER" == "generic"* ]]; then
RUN_MANILA_HOST_ASSISTED_MIGRATION_TESTS=True
RUN_MANILA_MANAGE_SNAPSHOT_TESTS=True
RUN_MANILA_CG_TESTS=False
if [[ "$MULTITENANCY_ENABLED" == "True" ]]; then
# NOTE(ganso): The generic driver has not implemented support for
# Manage/unmanage shares/snapshots in DHSS=True
RUN_MANILA_MANAGE_SNAPSHOT_TESTS=False
RUN_MANILA_MANAGE_TESTS=False
fi
if [[ "$POSTGRES_ENABLED" == "True" ]]; then
# Run only CIFS tests on PostgreSQL DB backend
# to reduce amount of tests per job using 'generic' share driver.
@ -241,7 +247,8 @@ elif [[ "$DRIVER" == "dummy" ]]; then
MANILA_TEMPEST_CONCURRENCY=24
MANILA_CONFIGURE_DEFAULT_TYPES=False
RUN_MANILA_SG_TESTS=True
RUN_MANILA_MANAGE_TESTS=False
RUN_MANILA_MANAGE_TESTS=True
RUN_MANILA_MANAGE_SNAPSHOT_TESTS=True
RUN_MANILA_DRIVER_ASSISTED_MIGRATION_TESTS=True
RUN_MANILA_REVERT_TO_SNAPSHOT_TESTS=True
RUN_MANILA_MOUNT_SNAPSHOT_TESTS=True


+ 4
- 1
manila/api/openstack/api_version_request.py View File

@ -131,13 +131,16 @@ REST_API_VERSION_HISTORY = """
replica export locations if available.
* 2.48 - Added support for extra-spec "availability_zones" within Share
types along with validation in the API.
* 2.49 - Added Manage/Unmanage Share Server APIs. Updated Manage/Unmanage
Shares and Snapshots APIs to work in
``driver_handles_shares_servers`` enabled mode.
"""
# 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.48"
_MAX_API_VERSION = "2.49"
DEFAULT_API_VERSION = _MIN_API_VERSION


+ 5
- 0
manila/api/openstack/rest_api_version_history.rst View File

@ -270,3 +270,8 @@ user documentation.
'availability_zones' within share types to allow provisioning of shares
only within specific availability zones. The extra-spec allows using
comma separated names of one or more availability zones.
2.49
----
Added Manage/Unmanage Share Server APIs. Updated Manage/Unmanage Shares and
Snapshots APIs to work in ``driver_handles_shares_servers`` enabled mode.

+ 14
- 10
manila/api/v1/share_manage.py View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import six
from webob import exc
from manila.api import common
@ -29,7 +28,7 @@ from manila import utils
class ShareManageMixin(object):
@wsgi.Controller.authorize('manage')
def _manage(self, req, body):
def _manage(self, req, body, allow_dhss_true=False):
context = req.environ['manila.context']
share_data = self._validate_manage_parameters(context, body)
share_data = common.validate_public_share_policy(context, share_data)
@ -56,12 +55,17 @@ class ShareManageMixin(object):
driver_options = share_data.get('driver_options', {})
if allow_dhss_true:
share['share_server_id'] = share_data.get('share_server_id')
try:
share_ref = self.share_api.manage(context, share, driver_options)
except exception.PolicyNotAuthorized as e:
raise exc.HTTPForbidden(explanation=six.text_type(e))
except exception.InvalidShare as e:
raise exc.HTTPConflict(explanation=six.text_type(e))
raise exc.HTTPForbidden(explanation=e)
except (exception.InvalidShare, exception.InvalidShareServer) as e:
raise exc.HTTPConflict(explanation=e)
except exception.InvalidInput as e:
raise exc.HTTPBadRequest(explanation=e)
return self._view_builder.detail(req, share_ref)
@ -90,13 +94,13 @@ class ShareManageMixin(object):
utils.validate_service_host(
context, share_utils.extract_host(data['service_host']))
except exception.ServiceNotFound as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
raise exc.HTTPNotFound(explanation=e)
except exception.PolicyNotAuthorized as e:
raise exc.HTTPForbidden(explanation=six.text_type(e))
raise exc.HTTPForbidden(explanation=e)
except exception.AdminRequired as e:
raise exc.HTTPForbidden(explanation=six.text_type(e))
raise exc.HTTPForbidden(explanation=e)
except exception.ServiceIsDown as e:
raise exc.HTTPBadRequest(explanation=six.text_type(e))
raise exc.HTTPBadRequest(explanation=e)
data['share_type_id'] = self._get_share_type_id(
context, data.get('share_type'))
@ -110,7 +114,7 @@ class ShareManageMixin(object):
share_type)
return stype['id']
except exception.ShareTypeNotFound as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
raise exc.HTTPNotFound(explanation=e)
class ShareManageController(ShareManageMixin, wsgi.Controller):


+ 9
- 9
manila/api/v1/share_servers.py View File

@ -14,7 +14,6 @@
# under the License.
from oslo_log import log
import six
from six.moves import http_client
import webob
from webob import exc
@ -33,10 +32,11 @@ LOG = log.getLogger(__name__)
class ShareServerController(wsgi.Controller):
"""The Share Server API controller for the OpenStack API."""
_view_builder_class = share_servers_views.ViewBuilder
resource_name = 'share_server'
def __init__(self):
self.share_api = share.API()
self._view_builder_class = share_servers_views.ViewBuilder
self.resource_name = 'share_server'
super(ShareServerController, self).__init__()
@wsgi.Controller.authorize
@ -62,7 +62,7 @@ class ShareServerController(wsgi.Controller):
s[k] == v or k == 'share_network' and
v in [s.share_network['name'],
s.share_network['id']])]
return self._view_builder.build_share_servers(share_servers)
return self._view_builder.build_share_servers(req, share_servers)
@wsgi.Controller.authorize
def show(self, req, id):
@ -76,8 +76,8 @@ class ShareServerController(wsgi.Controller):
else:
server.share_network_name = server.share_network_id
except exception.ShareServerNotFound as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
return self._view_builder.build_share_server(server)
raise exc.HTTPNotFound(explanation=e)
return self._view_builder.build_share_server(req, server)
@wsgi.Controller.authorize
def details(self, req, id):
@ -86,7 +86,7 @@ class ShareServerController(wsgi.Controller):
try:
share_server = db_api.share_server_get(context, id)
except exception.ShareServerNotFound as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
raise exc.HTTPNotFound(explanation=e)
return self._view_builder.build_share_server_details(
share_server['backend_details'])
@ -98,7 +98,7 @@ class ShareServerController(wsgi.Controller):
try:
share_server = db_api.share_server_get(context, id)
except exception.ShareServerNotFound as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
raise exc.HTTPNotFound(explanation=e)
allowed_statuses = [constants.STATUS_ERROR, constants.STATUS_ACTIVE]
if share_server['status'] not in allowed_statuses:
data = {
@ -112,7 +112,7 @@ class ShareServerController(wsgi.Controller):
try:
self.share_api.delete_share_server(context, share_server)
except exception.ShareServerInUse as e:
raise exc.HTTPConflict(explanation=six.text_type(e))
raise exc.HTTPConflict(explanation=e)
return webob.Response(status_int=http_client.ACCEPTED)


+ 5
- 5
manila/api/v1/share_unmanage.py View File

@ -13,7 +13,6 @@
# under the License.
from oslo_log import log
import six
from six.moves import http_client
import webob
from webob import exc
@ -30,7 +29,7 @@ LOG = log.getLogger(__name__)
class ShareUnmanageMixin(object):
@wsgi.Controller.authorize("unmanage")
def _unmanage(self, req, id, body=None):
def _unmanage(self, req, id, body=None, allow_dhss_true=False):
"""Unmanage a share."""
context = req.environ['manila.context']
@ -42,7 +41,8 @@ class ShareUnmanageMixin(object):
msg = _("Share %s has replicas. It cannot be unmanaged "
"until all replicas are removed.") % share['id']
raise exc.HTTPConflict(explanation=msg)
if share['instance'].get('share_server_id'):
if (not allow_dhss_true and
share['instance'].get('share_server_id')):
msg = _("Operation 'unmanage' is not supported for shares "
"that are created on top of share servers "
"(created with share-networks).")
@ -61,9 +61,9 @@ class ShareUnmanageMixin(object):
raise exc.HTTPForbidden(explanation=msg)
self.share_api.unmanage(context, share)
except exception.NotFound as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
raise exc.HTTPNotFound(explanation=e)
except (exception.InvalidShare, exception.PolicyNotAuthorized) as e:
raise exc.HTTPForbidden(explanation=six.text_type(e))
raise exc.HTTPForbidden(explanation=e)
return webob.Response(status_int=http_client.ACCEPTED)


+ 8
- 2
manila/api/v2/router.py View File

@ -27,7 +27,6 @@ from manila.api.v1 import scheduler_stats
from manila.api.v1 import security_service
from manila.api.v1 import share_manage
from manila.api.v1 import share_metadata
from manila.api.v1 import share_servers
from manila.api.v1 import share_types_extra_specs
from manila.api.v1 import share_unmanage
from manila.api.v2 import availability_zones
@ -47,6 +46,7 @@ from manila.api.v2 import share_instances
from manila.api.v2 import share_networks
from manila.api.v2 import share_replica_export_locations
from manila.api.v2 import share_replicas
from manila.api.v2 import share_servers
from manila.api.v2 import share_snapshot_export_locations
from manila.api.v2 import share_snapshot_instance_export_locations
from manila.api.v2 import share_snapshot_instances
@ -299,12 +299,18 @@ class APIRouter(manila.api.openstack.APIRouter):
self.resources["share_servers"] = share_servers.create_resource()
mapper.resource("share_server",
"share-servers",
controller=self.resources["share_servers"])
controller=self.resources["share_servers"],
member={"action": "POST"})
mapper.connect("details",
"/{project_id}/share-servers/{id}/details",
controller=self.resources["share_servers"],
action="details",
conditions={"method": ["GET"]})
mapper.connect("share_servers",
"/{project_id}/share-servers/manage",
controller=self.resources["share_servers"],
action="manage",
conditions={"method": ["POST"]})
self.resources["types"] = share_types.create_resource()
mapper.resource("type", "types",


+ 175
- 0
manila/api/v2/share_servers.py View File

@ -0,0 +1,175 @@
# Copyright 2019 NetApp, 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.
from oslo_log import log
from six.moves import http_client
import webob
from webob import exc
from manila.api.openstack import wsgi
from manila.api.v1 import share_servers
from manila.common import constants
from manila.db import api as db_api
from manila import exception
from manila.i18n import _
from manila.share import utils as share_utils
from manila import utils
LOG = log.getLogger(__name__)
class ShareServerController(share_servers.ShareServerController,
wsgi.AdminActionsMixin):
"""The Share Server API V2 controller for the OpenStack API."""
valid_statuses = {
'status': {
constants.STATUS_ACTIVE,
constants.STATUS_ERROR,
constants.STATUS_DELETING,
constants.STATUS_CREATING,
constants.STATUS_MANAGING,
constants.STATUS_UNMANAGING,
constants.STATUS_UNMANAGE_ERROR,
constants.STATUS_MANAGE_ERROR,
}
}
def _update(self, context, id, update):
db_api.share_server_update(context, id, update)
@wsgi.Controller.api_version('2.49')
@wsgi.action('reset_status')
def share_server_reset_status(self, req, id, body):
return self._reset_status(req, id, body)
@wsgi.Controller.api_version("2.49")
@wsgi.Controller.authorize('manage_share_server')
@wsgi.response(202)
def manage(self, req, body):
"""Manage a share server."""
context = req.environ['manila.context']
identifier, host, share_network, driver_opts = (
self._validate_manage_share_server_parameters(context, body))
try:
result = self.share_api.manage_share_server(
context, identifier, host, share_network, driver_opts)
except exception.InvalidInput as e:
raise exc.HTTPBadRequest(explanation=e)
result.project_id = share_network["project_id"]
if result.share_network['name']:
result.share_network_name = result.share_network['name']
else:
result.share_network_name = result.share_network_id
return self._view_builder.build_share_server(req, result)
@wsgi.Controller.authorize('unmanage_share_server')
def _unmanage(self, req, id, body=None):
context = req.environ['manila.context']
LOG.debug("Unmanage Share Server with id: %s", id)
# force's default value is False
# force will be True if body is {'unmanage': {'force': True}}
force = (body.get('unmanage') or {}).get('force', False) or False
try:
share_server = db_api.share_server_get(
context, id)
except exception.ShareServerNotFound as e:
raise exc.HTTPNotFound(explanation=e)
allowed_statuses = [constants.STATUS_ERROR, constants.STATUS_ACTIVE,
constants.STATUS_MANAGE_ERROR,
constants.STATUS_UNMANAGE_ERROR]
if share_server['status'] not in allowed_statuses:
data = {
'status': share_server['status'],
'allowed_statuses': ', '.join(allowed_statuses),
}
msg = _("Share server's actual status is %(status)s, allowed "
"statuses for unmanaging are "
"%(allowed_statuses)s.") % data
raise exc.HTTPBadRequest(explanation=msg)
try:
self.share_api.unmanage_share_server(
context, share_server, force=force)
except (exception.ShareServerInUse,
exception.PolicyNotAuthorized) as e:
raise exc.HTTPBadRequest(explanation=e)
return webob.Response(status_int=http_client.ACCEPTED)
@wsgi.Controller.api_version("2.49")
@wsgi.action('unmanage')
def unmanage(self, req, id, body=None):
"""Unmanage a share server."""
return self._unmanage(req, id, body)
def _validate_manage_share_server_parameters(self, context, body):
if not (body and self.is_valid_body(body, 'share_server')):
msg = _("Share Server entity not found in request body")
raise exc.HTTPUnprocessableEntity(explanation=msg)
required_parameters = ('host', 'share_network_id', 'identifier')
data = body['share_server']
for parameter in required_parameters:
if parameter not in data:
msg = _("Required parameter %s not found") % parameter
raise exc.HTTPBadRequest(explanation=msg)
if not data.get(parameter):
msg = _("Required parameter %s is empty") % parameter
raise exc.HTTPBadRequest(explanation=msg)
identifier = data['identifier']
host, share_network_id = data['host'], data['share_network_id']
if share_utils.extract_host(host, 'pool'):
msg = _("Host parameter should not contain pool.")
raise exc.HTTPBadRequest(explanation=msg)
try:
utils.validate_service_host(
context, share_utils.extract_host(host))
except exception.ServiceNotFound as e:
raise exc.HTTPBadRequest(explanation=e)
except exception.PolicyNotAuthorized as e:
raise exc.HTTPForbidden(explanation=e)
except exception.AdminRequired as e:
raise exc.HTTPForbidden(explanation=e)
except exception.ServiceIsDown as e:
raise exc.HTTPBadRequest(explanation=e)
try:
share_network = db_api.share_network_get(
context, share_network_id)
except exception.ShareNetworkNotFound as e:
raise exc.HTTPBadRequest(explanation=e)
driver_opts = data.get('driver_options')
if driver_opts is not None and not isinstance(driver_opts, dict):
msg = _("Driver options must be in dictionary format.")
raise exc.HTTPBadRequest(explanation=msg)
return identifier, host, share_network, driver_opts
def create_resource():
return wsgi.Resource(ShareServerController())

+ 11
- 7
manila/api/v2/share_snapshots.py View File

@ -17,7 +17,6 @@
"""The share snapshots api."""
from oslo_log import log
import six
from six.moves import http_client
import webob
from webob import exc
@ -47,7 +46,7 @@ class ShareSnapshotsController(share_snapshots.ShareSnapshotMixin,
self.share_api = share.API()
@wsgi.Controller.authorize('unmanage_snapshot')
def _unmanage(self, req, id, body=None):
def _unmanage(self, req, id, body=None, allow_dhss_true=False):
"""Unmanage a share snapshot."""
context = req.environ['manila.context']
@ -57,7 +56,7 @@ class ShareSnapshotsController(share_snapshots.ShareSnapshotMixin,
snapshot = self.share_api.get_snapshot(context, id)
share = self.share_api.get(context, snapshot['share_id'])
if share.get('share_server_id'):
if not allow_dhss_true and share.get('share_server_id'):
msg = _("Operation 'unmanage_snapshot' is not supported for "
"snapshots of shares that are created with share"
" servers (created with share-networks).")
@ -76,7 +75,7 @@ class ShareSnapshotsController(share_snapshots.ShareSnapshotMixin,
self.share_api.unmanage_snapshot(context, snapshot, share['host'])
except (exception.ShareSnapshotNotFound, exception.ShareNotFound) as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
raise exc.HTTPNotFound(explanation=e)
return webob.Response(status_int=http_client.ACCEPTED)
@ -128,10 +127,10 @@ class ShareSnapshotsController(share_snapshots.ShareSnapshotMixin,
snapshot_ref = self.share_api.manage_snapshot(context, snapshot,
driver_options)
except (exception.ShareNotFound, exception.ShareSnapshotNotFound) as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
raise exc.HTTPNotFound(explanation=e)
except (exception.InvalidShare,
exception.ManageInvalidShareSnapshot) as e:
raise exc.HTTPConflict(explanation=six.text_type(e))
raise exc.HTTPConflict(explanation=e)
return self._view_builder.detail(req, snapshot_ref)
@ -266,11 +265,16 @@ class ShareSnapshotsController(share_snapshots.ShareSnapshotMixin,
def manage(self, req, body):
return self._manage(req, body)
@wsgi.Controller.api_version('2.12')
@wsgi.Controller.api_version('2.12', '2.48')
@wsgi.action('unmanage')
def unmanage(self, req, id, body=None):
return self._unmanage(req, id, body)
@wsgi.Controller.api_version('2.49') # noqa
@wsgi.action('unmanage') # pylint: disable=function-redefined
def unmanage(self, req, id, body=None):
return self._unmanage(req, id, body, allow_dhss_true=True)
@wsgi.Controller.api_version('2.32')
@wsgi.action('allow_access')
@wsgi.response(202)


+ 20
- 11
manila/api/v2/shares.py View File

@ -14,7 +14,6 @@
# under the License.
from oslo_log import log
import six
from six.moves import http_client
import webob
from webob import exc
@ -150,13 +149,13 @@ class ShareController(shares.ShareMixin,
self.share_api.revert_to_snapshot(context, share, snapshot)
except exception.ShareNotFound as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
raise exc.HTTPNotFound(explanation=e)
except exception.ShareSnapshotNotFound as e:
raise exc.HTTPBadRequest(explanation=six.text_type(e))
raise exc.HTTPBadRequest(explanation=e)
except exception.ShareSizeExceedsAvailableQuota as e:
raise exc.HTTPForbidden(explanation=six.text_type(e))
raise exc.HTTPForbidden(explanation=e)
except exception.ReplicationException as e:
raise exc.HTTPBadRequest(explanation=six.text_type(e))
raise exc.HTTPBadRequest(explanation=e)
return webob.Response(status_int=http_client.ACCEPTED)
@ -278,7 +277,7 @@ class ShareController(shares.ShareMixin,
new_share_network=new_share_network,
new_share_type=new_share_type)
except exception.Conflict as e:
raise exc.HTTPConflict(explanation=six.text_type(e))
raise exc.HTTPConflict(explanation=e)
return webob.Response(status_int=return_code)
@ -407,18 +406,28 @@ class ShareController(shares.ShareMixin,
@wsgi.Controller.api_version('2.7', '2.7')
def manage(self, req, body):
body.get('share', {}).pop('is_public', None)
detail = self._manage(req, body)
detail = self._manage(req, body, allow_dhss_true=False)
return detail
@wsgi.Controller.api_version("2.8") # noqa
@wsgi.Controller.api_version("2.8", "2.48") # noqa
def manage(self, req, body): # pylint: disable=function-redefined
detail = self._manage(req, body)
detail = self._manage(req, body, allow_dhss_true=False)
return detail
@wsgi.Controller.api_version('2.7')
@wsgi.Controller.api_version("2.49") # noqa
def manage(self, req, body): # pylint: disable=function-redefined
detail = self._manage(req, body, allow_dhss_true=True)
return detail
@wsgi.Controller.api_version('2.7', '2.48')
@wsgi.action('unmanage')
def unmanage(self, req, id, body=None):
return self._unmanage(req, id, body)
return self._unmanage(req, id, body, allow_dhss_true=False)
@wsgi.Controller.api_version('2.49') # noqa
@wsgi.action('unmanage') # pylint: disable=function-redefined
def unmanage(self, req, id, body=None):
return self._unmanage(req, id, body, allow_dhss_true=True)
@wsgi.Controller.api_version('2.27')
@wsgi.action('revert')


+ 20
- 5
manila/api/views/share_servers.py View File

@ -20,25 +20,29 @@ class ViewBuilder(common.ViewBuilder):
"""Model a server API response as a python dictionary."""
_collection_name = 'share_servers'
_detail_version_modifiers = [
"add_is_auto_deletable_and_identifier_fields",
]
def build_share_server(self, share_server):
def build_share_server(self, request, share_server):
"""View of a share server."""
return {
'share_server':
self._build_share_server_view(share_server, detailed=True)
self._build_share_server_view(
request, share_server, detailed=True)
}
def build_share_servers(self, share_servers):
def build_share_servers(self, request, share_servers):
return {
'share_servers':
[self._build_share_server_view(share_server)
[self._build_share_server_view(request, share_server)
for share_server in share_servers]
}
def build_share_server_details(self, details):
return {'details': details}
def _build_share_server_view(self, share_server, detailed=False):
def _build_share_server_view(self, request, share_server, detailed=False):
share_server_dict = {
'id': share_server.id,
'project_id': share_server.project_id,
@ -51,4 +55,15 @@ class ViewBuilder(common.ViewBuilder):
if detailed:
share_server_dict['created_at'] = share_server.created_at
share_server_dict['backend_details'] = share_server.backend_details
self.update_versioned_resource_dict(
request, share_server_dict, share_server)
return share_server_dict
@common.ViewBuilder.versioned_method("2.49")
def add_is_auto_deletable_and_identifier_fields(
self, context, share_server_dict, share_server):
share_server_dict['is_auto_deletable'] = (
share_server['is_auto_deletable'])
share_server_dict['identifier'] = share_server['identifier']

+ 15
- 2
manila/db/api.py View File

@ -859,9 +859,16 @@ def network_allocation_delete(context, id):
return IMPL.network_allocation_delete(context, id)
def network_allocation_update(context, id, values):
def network_allocation_update(context, id, values, read_deleted=None):
"""Update a network allocation DB record."""
return IMPL.network_allocation_update(context, id, values)
return IMPL.network_allocation_update(context, id, values,
read_deleted=read_deleted)
def network_allocation_get(context, id, session=None, read_deleted=None):
"""Get a network allocation DB record."""
return IMPL.network_allocation_get(context, id, session,
read_deleted=read_deleted)
def network_allocations_get_for_share_server(context, share_server_id,
@ -899,6 +906,12 @@ def share_server_get(context, id, session=None):
return IMPL.share_server_get(context, id, session=session)
def share_server_search_by_identifier(context, identifier, session=None):
"""Search for share servers based on given identifier."""
return IMPL.share_server_search_by_identifier(
context, identifier, session=session)
def share_server_get_all_by_host_and_share_net_valid(context, host,
share_net_id,
session=None):


+ 72
- 0
manila/db/migrations/alembic/versions/6a3fd2984bc31_add_is_auto_deletable_and_identifier_fields_for_share_servers.py View File

@ -0,0 +1,72 @@
# Copyright 2019 NetApp, 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.
"""Add is_auto_deletable and identifier fields for share servers
Revision ID: 6a3fd2984bc31
Revises: 11ee96se625f3
Create Date: 2018-10-29 11:27:44.194732
"""
# revision identifiers, used by Alembic.
revision = '6a3fd2984bc31'
down_revision = '11ee96se625f3'
from alembic import op
from oslo_log import log
import sqlalchemy as sa
from manila.db.migrations import utils
LOG = log.getLogger(__name__)
def upgrade():
try:
op.add_column('share_servers', sa.Column(
'is_auto_deletable', sa.Boolean, default=True))
op.add_column('share_servers', sa.Column(
'identifier', sa.String(length=255), default=None))
except Exception:
LOG.error("Columns share_servers.is_auto_deletable "
"and/or share_servers.identifier not created!")
raise
try:
connection = op.get_bind()
share_servers_table = utils.load_table('share_servers', connection)
for server in connection.execute(share_servers_table.select()):
connection.execute(
share_servers_table.update().where(
share_servers_table.c.id == server.id,
).values({"identifier": server.id, "is_auto_deletable": True}))
except Exception:
LOG.error(
"Could not initialize share_servers.is_auto_deletable to True"
" and share_servers.identifier with the share server ID!")
raise
def downgrade():
try:
op.drop_column('share_servers', 'is_auto_deletable')
op.drop_column('share_servers', 'identifier')
except Exception:
LOG.error("Columns share_servers.is_auto_deletable and/or "
"share_servers.identifier not dropped!")
raise

+ 52
- 4
manila/db/sqlalchemy/api.py View File

@ -43,6 +43,7 @@ import six
from sqlalchemy import MetaData
from sqlalchemy import or_
from sqlalchemy.orm import joinedload
from sqlalchemy.sql.expression import literal
from sqlalchemy.sql.expression import true
from sqlalchemy.sql import func
@ -3554,6 +3555,50 @@ def share_server_get(context, server_id, session=None):
return result
@require_context
def share_server_search_by_identifier(context, identifier, session=None):
identifier_field = models.ShareServer.identifier
# try if given identifier is a substring of existing entry's identifier
result = (_server_get_query(context, session).filter(
identifier_field.like('%{}%'.format(identifier))).all())
if not result:
# repeat it with underscores instead of hyphens
result = (_server_get_query(context, session).filter(
identifier_field.like('%{}%'.format(
identifier.replace("-", "_")))).all())
if not result:
# repeat it with hypens instead of underscores
result = (_server_get_query(context, session).filter(
identifier_field.like('%{}%'.format(
identifier.replace("_", "-")))).all())
if not result:
# try if an existing identifier is a substring of given identifier
result = (_server_get_query(context, session).filter(
literal(identifier).contains(identifier_field)).all())
if not result:
# repeat it with underscores instead of hyphens
result = (_server_get_query(context, session).filter(
literal(identifier.replace("-", "_")).contains(
identifier_field)).all())
if not result:
# repeat it with hypens instead of underscores
result = (_server_get_query(context, session).filter(
literal(identifier.replace("_", "-")).contains(
identifier_field)).all())
if not result:
raise exception.ShareServerNotFound(share_server_id=identifier)
return result
@require_context
def share_server_get_all_by_host_and_share_net_valid(context, host,
share_net_id,
@ -3595,6 +3640,7 @@ def share_server_get_all_unused_deletable(context, host, updated_before):
constants.STATUS_ERROR,
)
result = (_server_get_query(context)
.filter_by(is_auto_deletable=True)
.filter_by(host=host)
.filter(~models.ShareServer.share_groups.any())
.filter(~models.ShareServer.share_instances.any())
@ -3747,10 +3793,11 @@ def network_allocation_delete(context, id):
@require_context
def network_allocation_get(context, id, session=None):
def network_allocation_get(context, id, session=None, read_deleted="no"):
if session is None:
session = get_session()
result = (model_query(context, models.NetworkAllocation, session=session).
result = (model_query(context, models.NetworkAllocation, session=session,
read_deleted=read_deleted).
filter_by(id=id).first())
if result is None:
raise exception.NotFound()
@ -3791,10 +3838,11 @@ def network_allocations_get_for_share_server(context, share_server_id,
@require_context
def network_allocation_update(context, id, values):
def network_allocation_update(context, id, values, read_deleted=None):
session = get_session()
with session.begin():
alloc_ref = network_allocation_get(context, id, session=session)
alloc_ref = network_allocation_get(context, id, session=session,
read_deleted=read_deleted)
alloc_ref.update(values)
alloc_ref.save(session=session)
return alloc_ref


+ 9
- 4
manila/db/sqlalchemy/models.py View File

@ -951,10 +951,15 @@ class ShareServer(BASE, ManilaBase):
share_network_id = Column(String(36), ForeignKey('share_networks.id'),
nullable=True)
host = Column(String(255), nullable=False)
status = Column(Enum(constants.STATUS_INACTIVE, constants.STATUS_ACTIVE,
constants.STATUS_ERROR, constants.STATUS_DELETING,
constants.STATUS_CREATING, constants.STATUS_DELETED),
default=constants.STATUS_INACTIVE)
is_auto_deletable = Column(Boolean, default=True)
identifier = Column(String(255), nullable=True)
status = Column(Enum(
constants.STATUS_INACTIVE, constants.STATUS_ACTIVE,
constants.STATUS_ERROR, constants.STATUS_DELETING,
constants.STATUS_CREATING, constants.STATUS_DELETED,
constants.STATUS_MANAGING, constants.STATUS_UNMANAGING,
constants.STATUS_UNMANAGE_ERROR, constants.STATUS_MANAGE_ERROR),
default=constants.STATUS_INACTIVE)
network_allocations = orm.relationship(
"NetworkAllocation",
primaryjoin='and_('


+ 4
- 0
manila/exception.py View File

@ -459,6 +459,10 @@ class ManageInvalidShare(InvalidShare):
"invalid share: %(reason)s")
class ManageShareServerError(ManilaException):
message = _("Manage existing share server failed due to: %(reason)s")
class UnmanageInvalidShare(InvalidShare):
message = _("Unmanage existing share failed due to "
"invalid share: %(reason)s")


+ 9
- 0
manila/network/__init__.py View File

@ -105,6 +105,15 @@ class NetworkBaseAPI(db_base.Base):
def deallocate_network(self, context, share_server_id):
pass
@abc.abstractmethod
def manage_network_allocations(self, context, allocations, share_server,
share_network=None):
pass
@abc.abstractmethod
def unmanage_network_allocations(self, context, share_server_id):
pass
@property
def enabled_ip_versions(self):
if not hasattr(self, '_enabled_ip_versions'):


+ 121
- 2
manila/network/neutron/neutron_network_plugin.py View File

@ -161,6 +161,108 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
return ports
def manage_network_allocations(self, context, allocations, share_server,
share_network=None):
self._verify_share_network(share_server['id'], share_network)
self._store_neutron_net_info(context, share_network)
# We begin matching the allocations to known neutron ports and
# finally return the non-consumed allocations
remaining_allocations = list(allocations)
fixed_ip_filter = 'subnet_id=' + share_network['neutron_subnet_id']
port_list = self.neutron_api.list_ports(
network_id=share_network['neutron_net_id'],
device_owner='manila:share',
fixed_ips=fixed_ip_filter)
selected_ports = self._get_ports_respective_to_ips(
remaining_allocations, port_list)
LOG.debug("Found matching allocations in Neutron:"
" %s", six.text_type(selected_ports))
for selected_port in selected_ports:
port_dict = {
'id': selected_port['port']['id'],
'share_server_id': share_server['id'],
'ip_address': selected_port['allocation'],
'gateway': share_network['gateway'],
'mac_address': selected_port['port']['mac_address'],
'status': constants.STATUS_ACTIVE,
'label': self.label,
'network_type': share_network.get('network_type'),
'segmentation_id': share_network.get('segmentation_id'),
'ip_version': share_network['ip_version'],
'cidr': share_network['cidr'],
'mtu': share_network['mtu'],
}
# There should not be existing allocations with the same port_id.
try:
existing_port = self.db.network_allocation_get(
context, selected_port['port']['id'], read_deleted=False)
except exception.NotFound:
pass
else:
msg = _("There were existing conflicting manila network "
"allocations found while trying to manage share "
"server %(new_ss)s. The conflicting port belongs to "
"share server %(old_ss)s.") % {
'new_ss': share_server['id'],
'old_ss': existing_port['share_server_id'],
}
raise exception.ManageShareServerError(reason=msg)
# If there are previously deleted allocations, we undelete them
try:
self.db.network_allocation_get(
context, selected_port['port']['id'], read_deleted=True)
except exception.NotFound:
self.db.network_allocation_create(context, port_dict)
else:
port_dict.pop('id')
port_dict.update({
'deleted_at': None,
'deleted': 'False',
})
self.db.network_allocation_update(
context, selected_port['port']['id'], port_dict,
read_deleted=True)
remaining_allocations.remove(selected_port['allocation'])
return remaining_allocations
def unmanage_network_allocations(self, context, share_server_id):
ports = self.db.network_allocations_get_for_share_server(
context, share_server_id)
for port in ports:
self.db.network_allocation_delete(context, port['id'])
def _get_ports_respective_to_ips(self, allocations, port_list):
selected_ports = []
for port in port_list:
for ip in port['fixed_ips']:
if ip['ip_address'] in allocations:
if not any(port['id'] == p['port']['id']
for p in selected_ports):
selected_ports.append(
{'port': port, 'allocation': ip['ip_address']})
else:
LOG.warning("Port %s has more than one IP that "
"matches allocations, please use ports "
"respective to only one allocation IP.",
port['id'])
return selected_ports
def _get_matched_ip_address(self, fixed_ips, ip_version):
"""Get first ip address which matches the specified ip_version."""
@ -314,8 +416,7 @@ class NeutronSingleNetworkPlugin(NeutronNetworkPlugin):
self.subnet = self.neutron_api.configuration.neutron_subnet_id
self._verify_net_and_subnet()
def allocate_network(self, context, share_server, share_network=None,
**kwargs):
def _select_proper_share_network(self, context, share_network):
if self.label != 'admin':
share_network = self._update_share_network_net_data(
context, share_network)
@ -325,9 +426,27 @@ class NeutronSingleNetworkPlugin(NeutronNetworkPlugin):
'neutron_net_id': self.net,
'neutron_subnet_id': self.subnet,
}
return share_network
def allocate_network(self, context, share_server, share_network=None,
**kwargs):
share_network = self._select_proper_share_network(
context, share_network)
return super(NeutronSingleNetworkPlugin, self).allocate_network(
context, share_server, share_network, **kwargs)
def manage_network_allocations(self, context, allocations, share_server,
share_network=None):
share_network = self._select_proper_share_network(
context, share_network)
return super(NeutronSingleNetworkPlugin,
self).manage_network_allocations(
context, allocations, share_server, share_network)
def _verify_net_and_subnet(self):
data = dict(net=self.net, subnet=self.subnet)
if self.net and self.subnet:


+ 43
- 0
manila/network/standalone_network_plugin.py View File

@ -318,3 +318,46 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI):
context, share_server_id)
for allocation in allocations:
self.db.network_allocation_delete(context, allocation['id'])
def unmanage_network_allocations(self, context, share_server_id):
self.deallocate_network(context, share_server_id)
def manage_network_allocations(self, context, allocations, share_server,
share_network=None):
if self.label != 'admin':
self._verify_share_network(share_server['id'], share_network)
else:
share_network = share_network or {}
self._save_network_info(context, share_network)
# We begin matching the allocations to known neutron ports and
# finally return the non-consumed allocations
remaining_allocations = list(allocations)
ips = [netaddr.IPAddress(allocation) for allocation
in remaining_allocations]
cidrs = [netaddr.IPNetwork(cidr) for cidr in self.allowed_cidrs]
selected_allocations = []
for ip in ips:
if any(ip in cidr for cidr in cidrs):
allocation = six.text_type(ip)
selected_allocations.append(allocation)
for allocation in selected_allocations:
data = {
'share_server_id': share_server['id'],
'ip_address': allocation,
'status': constants.STATUS_ACTIVE,
'label': self.label,
'network_type': share_network['network_type'],
'segmentation_id': share_network['segmentation_id'],
'cidr': share_network['cidr'],
'gateway': share_network['gateway'],
'ip_version': share_network['ip_version'],
'mtu': share_network['mtu'],
}
self.db.network_allocation_create(context, data)
remaining_allocations.remove(allocation)
return remaining_allocations

+ 30
- 0
manila/policies/share_server.py View File

@ -63,6 +63,36 @@ share_server_policies = [
'path': '/share-servers/{server_id}',
}
]),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'manage_share_server',
check_str=base.RULE_ADMIN_API,
description="Manage share server.",
operations=[
{
'method': 'POST',
'path': '/share-servers/manage'
}
]),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'unmanage_share_server',
check_str=base.RULE_ADMIN_API,
description="Unmanage share server.",
operations=[
{
'method': 'POST',
'path': '/share-servers/{share_server_id}/action'
}
]),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'reset_status',
check_str=base.RULE_ADMIN_API,
description="Reset the status of a share server.",
operations=[
{
'method': 'POST',
'path': '/share-servers/{share_server_id}/action'
}
]),
]


+ 89
- 0
manila/share/api.py View File

@ -613,6 +613,36 @@ class API(base.Base):
share_type_id = share_data['share_type_id']
share_type = share_types.get_share_type(context, share_type_id)
share_server_id = share_data.get('share_server_id')
dhss = share_types.parse_boolean_extra_spec(
'driver_handles_share_servers',
share_type['extra_specs']['driver_handles_share_servers'])
if dhss and not share_server_id:
msg = _("Share Server ID parameter is required when managing a "
"share using a share type with "
"driver_handles_share_servers extra-spec set to True.")
raise exception.InvalidInput(reason=msg)
if not dhss and share_server_id:
msg = _("Share Server ID parameter is not expected when managing a"
" share using a share type with "
"driver_handles_share_servers extra-spec set to False.")
raise exception.InvalidInput(reason=msg)
if share_server_id:
try:
share_server = self.db.share_server_get(
context, share_data['share_server_id'])
except exception.ShareServerNotFound:
msg = _("Share Server specified was not found.")
raise exception.InvalidInput(reason=msg)
if share_server['status'] != constants.STATUS_ACTIVE:
msg = _("Share Server specified is not active.")
raise exception.InvalidShareServer(message=msg)
share_data['share_network_id'] = share_server['share_network_id']
share_data.update({
'user_id': context.user_id,
'project_id': context.project_id,
@ -988,6 +1018,65 @@ class API(base.Base):
# and server deletion.
self.share_rpcapi.delete_share_server(context, server)
def manage_share_server(
self, context, identifier, host, share_network, driver_opts):
"""Manage a share server."""
try:
matched_servers = self.db.share_server_search_by_identifier(
context, identifier)
except exception.ShareServerNotFound:
pass
else:
msg = _("Identifier %(identifier)s specified matches existing "
"share servers: %(servers)s.") % {
'identifier': identifier,
'servers': ', '.join(s['identifier'] for s in matched_servers)
}
raise exception.InvalidInput(reason=msg)
values = {
'host': host,
'share_network_id': share_network['id'],
'status': constants.STATUS_MANAGING,
'is_auto_deletable': False,
'identifier': identifier,
}
server = self.db.share_server_create(context, values)
self.share_rpcapi.manage_share_server(
context, server, identifier, driver_opts)
return self.db.share_server_get(context, server['id'])
def unmanage_share_server(self, context, share_server, force=False):
"""Unmanage a share server."""
shares = self.db.share_instances_get_all_by_share_server(
context, share_server['id'])
if shares:
raise exception.ShareServerInUse(
share_server_id=share_server['id'])
share_groups = self.db.share_group_get_all_by_share_server(
context, share_server['id'])
if share_groups:
LOG.error("share server '%(ssid)s' in use by share groups.",
{'ssid': share_server['id']})
raise exception.ShareServerInUse(
share_server_id=share_server['id'])
update_data = {'status': constants.STATUS_UNMANAGING,
'terminated_at': timeutils.utcnow()}
share_server = self.db.share_server_update(
context, share_server['id'], update_data)
self.share_rpcapi.unmanage_share_server(
context, share_server, force=force)
def create_snapshot(self, context, share, name, description,
force=False):
policy.check_policy(context, 'share', 'create_snapshot', share)


+ 169
- 0
manila/share/driver.py View File

@ -933,8 +933,40 @@ class ShareDriver(object):
If they are incompatible, raise a
ManageExistingShareTypeMismatch, specifying a reason for the failure.
This method is invoked when the share is being managed with
a share type that has ``driver_handles_share_servers``
extra-spec set to False.
:param share: Share model
:param driver_options: Driver-specific options provided by admin.
:return: share_update dictionary with required key 'size',
which should contain size of the share.
"""
raise NotImplementedError()
def manage_existing_with_server(
self, share, driver_options, share_server=None):
"""Brings an existing share under Manila management.
If the provided share is not valid, then raise a
ManageInvalidShare exception, specifying a reason for the failure.
If the provided share is not in a state that can be managed, such as
being replicated on the backend, the driver *MUST* raise
ManageInvalidShare exception with an appropriate message.
The share has a share_type, and the driver can inspect that and
compare against the properties of the referenced backend share.
If they are incompatible, raise a
ManageExistingShareTypeMismatch, specifying a reason for the failure.
This method is invoked when the share is being managed with
a share type that has ``driver_handles_share_servers``
extra-spec set to True.
:param share: Share model
:param driver_options: Driver-specific options provided by admin.
:param share_server: Share server model or None.
:return: share_update dictionary with required key 'size',
which should contain size of the share.
"""
@ -945,6 +977,24 @@ class ShareDriver(object):
Does not delete the underlying backend share.
For most drivers, this will not need to do anything. However, some
drivers might use this call as an opportunity to clean up any
Manila-specific configuration that they have associated with the
backend share.
If provided share cannot be unmanaged, then raise an
UnmanageInvalidShare exception, specifying a reason for the failure.
This method is invoked when the share is being unmanaged with
a share type that has ``driver_handles_share_servers``
extra-spec set to False.
"""
def unmanage_with_server(self, share, share_server=None):
"""Removes the specified share from Manila management.
Does not delete the underlying backend share.
For most drivers, this will not need to do anything. However, some
drivers might use this call as an opportunity to clean up any
Manila-specific configuration that they have associated with the
@ -952,6 +1002,10 @@ class ShareDriver(object):
If provided share cannot be unmanaged, then raise an
UnmanageInvalidShare exception, specifying a reason for the failure.
This method is invoked when the share is being unmanaged with
a share type that has ``driver_handles_share_servers``
extra-spec set to True.
"""
def manage_existing_snapshot(self, snapshot, driver_options):
@ -961,6 +1015,10 @@ class ShareDriver(object):
ManageInvalidShareSnapshot exception, specifying a reason for
the failure.
This method is invoked when the snapshot that is being managed
belongs to a share that has its share type with
``driver_handles_share_servers`` extra-spec set to False.
:param snapshot: ShareSnapshotInstance model with ShareSnapshot data.
Example::
@ -988,6 +1046,46 @@ class ShareDriver(object):
"""
raise NotImplementedError()
def manage_existing_snapshot_with_server(self, snapshot, driver_options,
share_server=None):
"""Brings an existing snapshot under Manila management.
If provided snapshot is not valid, then raise a
ManageInvalidShareSnapshot exception, specifying a reason for
the failure.
This method is invoked when the snapshot that is being managed
belongs to a share that has its share type with
``driver_handles_share_servers`` extra-spec set to True.
:param snapshot: ShareSnapshotInstance model with ShareSnapshot data.
Example::
{
'id': <instance id>,
'snapshot_id': < snapshot id>,
'provider_location': <location>,
...
}
:param driver_options: Optional driver-specific options provided
by admin.
Example::
{
'key': 'value',
...
}
:param share_server: Share server model or None.
:return: model_update dictionary with required key 'size',
which should contain size of the share snapshot, and key
'export_locations' containing a list of export locations, if
snapshots can be mounted.
"""
raise NotImplementedError()
def unmanage_snapshot(self, snapshot):
"""Removes the specified snapshot from Manila management.
@ -1001,6 +1099,29 @@ class ShareDriver(object):
If provided share snapshot cannot be unmanaged, then raise an
UnmanageInvalidShareSnapshot exception, specifying a reason for
the failure.
This method is invoked when the snapshot that is being unmanaged
belongs to a share that has its share type with
``driver_handles_share_servers`` extra-spec set to False.
"""
def unmanage_snapshot_with_server(self, snapshot, share_server=None):
"""Removes the specified snapshot from Manila management.
Does not delete the underlying backend share snapshot.
For most drivers, this will not need to do anything. However, some
drivers might use this call as an opportunity to clean up any
Manila-specific configuration that they have associated with the
backend share snapshot.
If provided share snapshot cannot be unmanaged, then raise an
UnmanageInvalidShareSnapshot exception, specifying a reason for
the failure.
This method is invoked when the snapshot that is being unmanaged
belongs to a share that has its share type with
``driver_handles_share_servers`` extra-spec set to