Add possibility to filter back ends by snapshot support
On last midcycle meetup was decided to make snapshots optional feature. Features: - Add new boolean capability 'snapshot_support' to base share driver so every existing share driver will inherit it. Make value of it be calculated from the fact of redefinition of three main driver methods for snapshots 'create_snapshot', 'delete_snapshot' and 'create_share_from_snapshot'. - Set extra spec 'snapshot_support' with share type creation by default to 'True' - Restrict deletion of extra spec 'snapshot_support' that is expected to exist - Allow to redefine new extra spec 'snapshot_support' - Restrict API 'snapshot create' for share created with share type that has extra spec 'snapshot_support' equal to 'False'. - Add migration where new extra spec 'snapshot_support' is added to all share types that do not have it yet. Partially implements bp snapshots-optional Change-Id: I069d9e911c7d7a708fa518b38ed10572a45e5f42
This commit is contained in:
parent
15fb464e5d
commit
af6814f9ca
@ -247,3 +247,23 @@ class ExtraSpecsAdminNegativeTest(base.BaseSharesAdminTest):
|
|||||||
self.assertRaises(lib_exc.BadRequest,
|
self.assertRaises(lib_exc.BadRequest,
|
||||||
self.shares_client.update_share_type_extra_specs,
|
self.shares_client.update_share_type_extra_specs,
|
||||||
st["share_type"]["id"], {"": "new_value"})
|
st["share_type"]["id"], {"": "new_value"})
|
||||||
|
|
||||||
|
@test.attr(type=["gate", "smoke", ])
|
||||||
|
def test_try_delete_spec_driver_handles_share_servers(self):
|
||||||
|
st = self._create_share_type()
|
||||||
|
|
||||||
|
# Try delete extra spec 'driver_handles_share_servers'
|
||||||
|
self.assertRaises(lib_exc.Forbidden,
|
||||||
|
self.shares_client.delete_share_type_extra_spec,
|
||||||
|
st["share_type"]["id"],
|
||||||
|
"driver_handles_share_servers")
|
||||||
|
|
||||||
|
@test.attr(type=["gate", "smoke", ])
|
||||||
|
def test_try_delete_spec_snapshot_support(self):
|
||||||
|
st = self._create_share_type()
|
||||||
|
|
||||||
|
# Try delete extra spec 'snapshot_support'
|
||||||
|
self.assertRaises(lib_exc.Forbidden,
|
||||||
|
self.shares_client.delete_share_type_extra_spec,
|
||||||
|
st["share_type"]["id"],
|
||||||
|
"snapshot_support")
|
||||||
|
@ -444,7 +444,10 @@ class BaseSharesTest(test.BaseTestCase):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def add_required_extra_specs_to_dict(extra_specs=None):
|
def add_required_extra_specs_to_dict(extra_specs=None):
|
||||||
value = six.text_type(CONF.share.multitenancy_enabled)
|
value = six.text_type(CONF.share.multitenancy_enabled)
|
||||||
required = {"driver_handles_share_servers": value}
|
required = {
|
||||||
|
"driver_handles_share_servers": value,
|
||||||
|
"snapshot_support": 'True',
|
||||||
|
}
|
||||||
if extra_specs:
|
if extra_specs:
|
||||||
required.update(extra_specs)
|
required.update(extra_specs)
|
||||||
return required
|
return required
|
||||||
|
@ -36,7 +36,7 @@ ShareGroup = [
|
|||||||
help="The minimum api microversion is configured to be the "
|
help="The minimum api microversion is configured to be the "
|
||||||
"value of the minimum microversion supported by Manila."),
|
"value of the minimum microversion supported by Manila."),
|
||||||
cfg.StrOpt("max_api_microversion",
|
cfg.StrOpt("max_api_microversion",
|
||||||
default="1.2",
|
default="1.3",
|
||||||
help="The maximum api microversion is configured to be the "
|
help="The maximum api microversion is configured to be the "
|
||||||
"value of the latest microversion supported by Manila."),
|
"value of the latest microversion supported by Manila."),
|
||||||
cfg.StrOpt("region",
|
cfg.StrOpt("region",
|
||||||
|
@ -135,8 +135,8 @@ class ShareTypeExtraSpecsController(wsgi.Controller):
|
|||||||
self._check_type(context, type_id)
|
self._check_type(context, type_id)
|
||||||
authorize(context)
|
authorize(context)
|
||||||
|
|
||||||
if id in share_types.get_required_extra_specs():
|
if id in share_types.get_undeletable_extra_specs():
|
||||||
msg = _("Required extra specs can't be deleted.")
|
msg = _("Extra spec '%s' can't be deleted.") % id
|
||||||
raise webob.exc.HTTPForbidden(explanation=msg)
|
raise webob.exc.HTTPForbidden(explanation=msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -47,6 +47,7 @@ REST_API_VERSION_HISTORY = """
|
|||||||
* 1.0 - Initial version. Includes all V1 APIs and extensions in Kilo.
|
* 1.0 - Initial version. Includes all V1 APIs and extensions in Kilo.
|
||||||
* 1.1 - Versions API updated to reflect beginning of microversions epoch.
|
* 1.1 - Versions API updated to reflect beginning of microversions epoch.
|
||||||
* 1.2 - Share create() doesn't ignore availability_zone field of share.
|
* 1.2 - Share create() doesn't ignore availability_zone field of share.
|
||||||
|
* 1.3 - Snapshots become optional feature.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ REST_API_VERSION_HISTORY = """
|
|||||||
# The default api version request is defined to be the
|
# The default api version request is defined to be the
|
||||||
# the minimum version of the API supported.
|
# the minimum version of the API supported.
|
||||||
_MIN_API_VERSION = "1.0"
|
_MIN_API_VERSION = "1.0"
|
||||||
_MAX_API_VERSION = "1.2"
|
_MAX_API_VERSION = "1.3"
|
||||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,3 +35,8 @@ user documentation.
|
|||||||
---
|
---
|
||||||
Share create() method doesn't ignore availability_zone field of provided
|
Share create() method doesn't ignore availability_zone field of provided
|
||||||
share.
|
share.
|
||||||
|
|
||||||
|
1.3
|
||||||
|
---
|
||||||
|
Snapshots become optional and share payload now has
|
||||||
|
boolean attr 'snapshot_support'.
|
||||||
|
@ -149,6 +149,14 @@ class ShareSnapshotsController(wsgi.Controller):
|
|||||||
|
|
||||||
share_id = snapshot['share_id']
|
share_id = snapshot['share_id']
|
||||||
share = self.share_api.get(context, share_id)
|
share = self.share_api.get(context, share_id)
|
||||||
|
|
||||||
|
# Verify that share can be snapshotted
|
||||||
|
if not share['snapshot_support']:
|
||||||
|
msg = _("Snapshot cannot be created from share '%s', because "
|
||||||
|
"share back end does not support it.") % share_id
|
||||||
|
LOG.error(msg)
|
||||||
|
raise exc.HTTPUnprocessableEntity(msg)
|
||||||
|
|
||||||
LOG.info(_LI("Create snapshot from share %s"),
|
LOG.info(_LI("Create snapshot from share %s"),
|
||||||
share_id, context=context)
|
share_id, context=context)
|
||||||
|
|
||||||
|
@ -162,7 +162,17 @@ class ShareController(wsgi.Controller):
|
|||||||
share.update(update_dict)
|
share.update(update_dict)
|
||||||
return self._view_builder.detail(req, share)
|
return self._view_builder.detail(req, share)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version("1.3")
|
||||||
def create(self, req, body):
|
def create(self, req, body):
|
||||||
|
return self._create(req, body)
|
||||||
|
|
||||||
|
@wsgi.Controller.api_version("1.0", "1.2") # noqa
|
||||||
|
def create(self, req, body):
|
||||||
|
share = self._create(req, body)
|
||||||
|
share.pop('snapshot_support', None)
|
||||||
|
return share
|
||||||
|
|
||||||
|
def _create(self, req, body):
|
||||||
"""Creates a new share."""
|
"""Creates a new share."""
|
||||||
context = req.environ['manila.context']
|
context = req.environ['manila.context']
|
||||||
|
|
||||||
|
@ -83,5 +83,20 @@ ACCESS_LEVELS = (
|
|||||||
|
|
||||||
|
|
||||||
class ExtraSpecs(object):
|
class ExtraSpecs(object):
|
||||||
|
|
||||||
|
# Extra specs key names
|
||||||
DRIVER_HANDLES_SHARE_SERVERS = "driver_handles_share_servers"
|
DRIVER_HANDLES_SHARE_SERVERS = "driver_handles_share_servers"
|
||||||
REQUIRED = (DRIVER_HANDLES_SHARE_SERVERS, )
|
SNAPSHOT_SUPPORT = "snapshot_support"
|
||||||
|
|
||||||
|
# Extra specs containers
|
||||||
|
REQUIRED = (
|
||||||
|
DRIVER_HANDLES_SHARE_SERVERS,
|
||||||
|
)
|
||||||
|
UNDELETABLE = (
|
||||||
|
DRIVER_HANDLES_SHARE_SERVERS,
|
||||||
|
SNAPSHOT_SUPPORT,
|
||||||
|
)
|
||||||
|
BOOLEAN = (
|
||||||
|
DRIVER_HANDLES_SHARE_SERVERS,
|
||||||
|
SNAPSHOT_SUPPORT,
|
||||||
|
)
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
# Copyright 2015 Mirantis 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 'snapshot_support' extra spec to share types
|
||||||
|
|
||||||
|
Revision ID: 55761e5f59c5
|
||||||
|
Revises: 1f0bd302c1a6
|
||||||
|
Create Date: 2015-08-13 14:02:54.656864
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '55761e5f59c5'
|
||||||
|
down_revision = '1f0bd302c1a6'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.sql import table
|
||||||
|
|
||||||
|
from manila.common import constants
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
"""Performs DB upgrade to support feature of making snapshots optional.
|
||||||
|
|
||||||
|
Add 'snapshot_support' extra spec to all share types and
|
||||||
|
attr 'snapshot_support' to Share model.
|
||||||
|
"""
|
||||||
|
session = sa.orm.Session(bind=op.get_bind().connect())
|
||||||
|
|
||||||
|
es_table = table(
|
||||||
|
'share_type_extra_specs',
|
||||||
|
sa.Column('created_at', sa.DateTime),
|
||||||
|
sa.Column('deleted', sa.Integer),
|
||||||
|
sa.Column('share_type_id', sa.String(length=36)),
|
||||||
|
sa.Column('spec_key', sa.String(length=255)),
|
||||||
|
sa.Column('spec_value', sa.String(length=255)))
|
||||||
|
|
||||||
|
st_table = table(
|
||||||
|
'share_types',
|
||||||
|
sa.Column('deleted', sa.Integer),
|
||||||
|
sa.Column('id', sa.Integer))
|
||||||
|
|
||||||
|
# NOTE(vponomaryov): field 'deleted' is integer here.
|
||||||
|
existing_extra_specs = session.query(es_table).\
|
||||||
|
filter(es_table.c.spec_key == constants.ExtraSpecs.SNAPSHOT_SUPPORT).\
|
||||||
|
filter(es_table.c.deleted == 0).\
|
||||||
|
all()
|
||||||
|
exclude_st_ids = [es.share_type_id for es in existing_extra_specs]
|
||||||
|
|
||||||
|
# NOTE(vponomaryov): field 'deleted' is string here.
|
||||||
|
share_types = session.query(st_table).\
|
||||||
|
filter(st_table.c.deleted.in_(('0', 'False', ))).\
|
||||||
|
filter(st_table.c.id.notin_(exclude_st_ids)).\
|
||||||
|
all()
|
||||||
|
session.close_all()
|
||||||
|
|
||||||
|
extra_specs = []
|
||||||
|
now = timeutils.utcnow()
|
||||||
|
for st in share_types:
|
||||||
|
extra_specs.append({
|
||||||
|
'spec_key': constants.ExtraSpecs.SNAPSHOT_SUPPORT,
|
||||||
|
'spec_value': 'True',
|
||||||
|
'deleted': 0,
|
||||||
|
'created_at': now,
|
||||||
|
'share_type_id': st.id,
|
||||||
|
})
|
||||||
|
if extra_specs:
|
||||||
|
op.bulk_insert(es_table, extra_specs)
|
||||||
|
|
||||||
|
# NOTE(vponomaryov): shares that were created before applying this
|
||||||
|
# migration can have incorrect value because they were created without
|
||||||
|
# consideration of driver capability to create snapshots.
|
||||||
|
op.add_column('shares',
|
||||||
|
sa.Column('snapshot_support', sa.Boolean, default=True))
|
||||||
|
|
||||||
|
connection = op.get_bind().connect()
|
||||||
|
shares = sa.Table(
|
||||||
|
'shares',
|
||||||
|
sa.MetaData(),
|
||||||
|
autoload=True,
|
||||||
|
autoload_with=connection)
|
||||||
|
|
||||||
|
update = shares.update().where(shares.c.deleted == 'False').values(
|
||||||
|
snapshot_support=True)
|
||||||
|
connection.execute(update)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
"""Performs DB downgrade removing support of 'optional snapshots' feature.
|
||||||
|
|
||||||
|
Remove 'snapshot_support' extra spec from all share types and
|
||||||
|
attr 'snapshot_support' from Share model.
|
||||||
|
"""
|
||||||
|
connection = op.get_bind().connect()
|
||||||
|
extra_specs = sa.Table(
|
||||||
|
'share_type_extra_specs',
|
||||||
|
sa.MetaData(),
|
||||||
|
autoload=True,
|
||||||
|
autoload_with=connection)
|
||||||
|
|
||||||
|
update = extra_specs.update().where(
|
||||||
|
extra_specs.c.spec_key == constants.ExtraSpecs.SNAPSHOT_SUPPORT).where(
|
||||||
|
extra_specs.c.deleted == 0).values(
|
||||||
|
deleted=extra_specs.c.id,
|
||||||
|
deleted_at=timeutils.utcnow(),
|
||||||
|
)
|
||||||
|
connection.execute(update)
|
||||||
|
|
||||||
|
op.drop_column('shares', 'snapshot_support')
|
@ -245,6 +245,7 @@ class Share(BASE, ManilaBase):
|
|||||||
display_name = Column(String(255))
|
display_name = Column(String(255))
|
||||||
display_description = Column(String(255))
|
display_description = Column(String(255))
|
||||||
snapshot_id = Column(String(36))
|
snapshot_id = Column(String(36))
|
||||||
|
snapshot_support = Column(Boolean, default=True)
|
||||||
share_proto = Column(String(255))
|
share_proto = Column(String(255))
|
||||||
share_type_id = Column(String(36), ForeignKey('share_types.id'),
|
share_type_id = Column(String(36), ForeignKey('share_types.id'),
|
||||||
nullable=True)
|
nullable=True)
|
||||||
|
@ -126,12 +126,13 @@ class FilterScheduler(driver.Scheduler):
|
|||||||
extra_specs = share_type.get('extra_specs', {})
|
extra_specs = share_type.get('extra_specs', {})
|
||||||
|
|
||||||
if extra_specs:
|
if extra_specs:
|
||||||
for extra_spec_name in share_types.get_required_extra_specs():
|
for extra_spec_name in share_types.get_boolean_extra_specs():
|
||||||
extra_spec = extra_specs.get(extra_spec_name)
|
extra_spec = extra_specs.get(extra_spec_name)
|
||||||
|
|
||||||
if extra_spec is not None:
|
if extra_spec is not None:
|
||||||
share_type['extra_specs'][extra_spec_name] = (
|
if not extra_spec.startswith("<is>"):
|
||||||
"<is> %s" % extra_spec)
|
extra_spec = "<is> %s" % extra_spec
|
||||||
|
share_type['extra_specs'][extra_spec_name] = extra_spec
|
||||||
|
|
||||||
resource_type = request_spec.get("share_type") or {}
|
resource_type = request_spec.get("share_type") or {}
|
||||||
request_spec.update({'resource_properties': resource_properties})
|
request_spec.update({'resource_properties': resource_properties})
|
||||||
|
@ -123,6 +123,7 @@ class HostState(object):
|
|||||||
self.thin_provisioning_support = False
|
self.thin_provisioning_support = False
|
||||||
self.thick_provisioning_support = False
|
self.thick_provisioning_support = False
|
||||||
self.driver_handles_share_servers = False
|
self.driver_handles_share_servers = False
|
||||||
|
self.snapshot_support = True
|
||||||
|
|
||||||
# PoolState for all pools
|
# PoolState for all pools
|
||||||
self.pools = {}
|
self.pools = {}
|
||||||
@ -275,6 +276,9 @@ class HostState(object):
|
|||||||
pool_cap['driver_handles_share_servers'] = \
|
pool_cap['driver_handles_share_servers'] = \
|
||||||
self.driver_handles_share_servers
|
self.driver_handles_share_servers
|
||||||
|
|
||||||
|
if not pool_cap.get('snapshot_support'):
|
||||||
|
pool_cap['snapshot_support'] = True
|
||||||
|
|
||||||
def update_backend(self, capability):
|
def update_backend(self, capability):
|
||||||
self.share_backend_name = capability.get('share_backend_name')
|
self.share_backend_name = capability.get('share_backend_name')
|
||||||
self.vendor_name = capability.get('vendor_name')
|
self.vendor_name = capability.get('vendor_name')
|
||||||
@ -282,6 +286,7 @@ class HostState(object):
|
|||||||
self.storage_protocol = capability.get('storage_protocol')
|
self.storage_protocol = capability.get('storage_protocol')
|
||||||
self.driver_handles_share_servers = capability.get(
|
self.driver_handles_share_servers = capability.get(
|
||||||
'driver_handles_share_servers')
|
'driver_handles_share_servers')
|
||||||
|
self.snapshot_support = capability.get('snapshot_support')
|
||||||
self.updated = capability['timestamp']
|
self.updated = capability['timestamp']
|
||||||
|
|
||||||
def consume_from_share(self, share):
|
def consume_from_share(self, share):
|
||||||
|
@ -160,6 +160,10 @@ class API(base.Base):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
is_public = strutils.bool_from_string(is_public, strict=True)
|
is_public = strutils.bool_from_string(is_public, strict=True)
|
||||||
|
snapshot_support = strutils.bool_from_string(
|
||||||
|
share_type.get('extra_specs', {}).get(
|
||||||
|
'snapshot_support', True) if share_type else True,
|
||||||
|
strict=True)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise exception.InvalidParameterValue(six.text_type(e))
|
raise exception.InvalidParameterValue(six.text_type(e))
|
||||||
|
|
||||||
@ -167,6 +171,7 @@ class API(base.Base):
|
|||||||
'user_id': context.user_id,
|
'user_id': context.user_id,
|
||||||
'project_id': context.project_id,
|
'project_id': context.project_id,
|
||||||
'snapshot_id': snapshot_id,
|
'snapshot_id': snapshot_id,
|
||||||
|
'snapshot_support': snapshot_support,
|
||||||
'metadata': metadata,
|
'metadata': metadata,
|
||||||
'display_name': name,
|
'display_name': name,
|
||||||
'display_description': description,
|
'display_description': description,
|
||||||
|
@ -445,6 +445,36 @@ class ShareDriver(object):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _has_redefined_driver_methods(self, methods):
|
||||||
|
"""Returns boolean as a result of methods presence and redefinition."""
|
||||||
|
if not isinstance(methods, (set, list, tuple)):
|
||||||
|
methods = (methods, )
|
||||||
|
parent = super(self.__class__, self)
|
||||||
|
for method in methods:
|
||||||
|
# NOTE(vponomaryov): criteria:
|
||||||
|
# - If parent does not have such attr, then was called method of
|
||||||
|
# this class which has no implementation.
|
||||||
|
# - If parent's method is equal to child's method then child did
|
||||||
|
# not redefine it and has no implementation.
|
||||||
|
if (not hasattr(parent, method) or
|
||||||
|
getattr(self, method) == getattr(parent, method)):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def snapshots_are_supported(self):
|
||||||
|
if not hasattr(self, '_snapshots_are_supported'):
|
||||||
|
methods = (
|
||||||
|
"create_snapshot",
|
||||||
|
"delete_snapshot",
|
||||||
|
"create_share_from_snapshot")
|
||||||
|
# NOTE(vponomaryov): calculate default value for
|
||||||
|
# stat 'snapshot_support' based on implementation of
|
||||||
|
# appropriate methods of this base driver class.
|
||||||
|
self._snapshots_are_supported = self._has_redefined_driver_methods(
|
||||||
|
methods)
|
||||||
|
return self._snapshots_are_supported
|
||||||
|
|
||||||
def _update_share_stats(self, data=None):
|
def _update_share_stats(self, data=None):
|
||||||
"""Retrieve stats info from share group.
|
"""Retrieve stats info from share group.
|
||||||
|
|
||||||
@ -469,6 +499,7 @@ class ShareDriver(object):
|
|||||||
reserved_percentage=0,
|
reserved_percentage=0,
|
||||||
QoS_support=False,
|
QoS_support=False,
|
||||||
pools=self.pools or None,
|
pools=self.pools or None,
|
||||||
|
snapshot_support=self.snapshots_are_supported,
|
||||||
)
|
)
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
common.update(data)
|
common.update(data)
|
||||||
|
@ -408,19 +408,6 @@ class GlusterfsShareDriver(driver.ExecuteMixin, driver.GaneshaMixin,
|
|||||||
LOG.error(_LE('Unable to delete share %s'), share['name'])
|
LOG.error(_LE('Unable to delete share %s'), share['name'])
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def create_snapshot(self, context, snapshot, share_server=None):
|
|
||||||
"""TBD: Is called to create snapshot."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def create_share_from_snapshot(self, context, share, snapshot,
|
|
||||||
share_server=None):
|
|
||||||
"""Is called to create share from snapshot."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def delete_snapshot(self, context, snapshot, share_server=None):
|
|
||||||
"""TBD: Is called to remove snapshot."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def ensure_share(self, context, share, share_server=None):
|
def ensure_share(self, context, share, share_server=None):
|
||||||
"""Might not be needed?"""
|
"""Might not be needed?"""
|
||||||
pass
|
pass
|
||||||
|
@ -39,6 +39,9 @@ def create(context, name, extra_specs=None, is_public=True, projects=None):
|
|||||||
extra_specs = extra_specs or {}
|
extra_specs = extra_specs or {}
|
||||||
projects = projects or []
|
projects = projects or []
|
||||||
|
|
||||||
|
if constants.ExtraSpecs.SNAPSHOT_SUPPORT not in list(extra_specs):
|
||||||
|
extra_specs[constants.ExtraSpecs.SNAPSHOT_SUPPORT] = 'True'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
get_valid_required_extra_specs(extra_specs)
|
get_valid_required_extra_specs(extra_specs)
|
||||||
except exception.InvalidExtraSpec as e:
|
except exception.InvalidExtraSpec as e:
|
||||||
@ -196,6 +199,14 @@ def get_required_extra_specs():
|
|||||||
return constants.ExtraSpecs.REQUIRED
|
return constants.ExtraSpecs.REQUIRED
|
||||||
|
|
||||||
|
|
||||||
|
def get_undeletable_extra_specs():
|
||||||
|
return constants.ExtraSpecs.UNDELETABLE
|
||||||
|
|
||||||
|
|
||||||
|
def get_boolean_extra_specs():
|
||||||
|
return constants.ExtraSpecs.BOOLEAN
|
||||||
|
|
||||||
|
|
||||||
def is_valid_required_extra_spec(key, value):
|
def is_valid_required_extra_spec(key, value):
|
||||||
"""Validates required extra_spec value.
|
"""Validates required extra_spec value.
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ def stub_share(id, **kwargs):
|
|||||||
'share_network_id': None,
|
'share_network_id': None,
|
||||||
'share_server_id': 'fake_share_server_id',
|
'share_server_id': 'fake_share_server_id',
|
||||||
'is_public': False,
|
'is_public': False,
|
||||||
|
'snapshot_support': True,
|
||||||
}
|
}
|
||||||
share.update(kwargs)
|
share.update(kwargs)
|
||||||
return share
|
return share
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
import webob
|
import webob
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ from manila.tests.api.contrib import stubs
|
|||||||
from manila.tests.api import fakes
|
from manila.tests.api import fakes
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
class ShareSnapshotApiTest(test.TestCase):
|
class ShareSnapshotApiTest(test.TestCase):
|
||||||
"""Share Snapshot Api Test."""
|
"""Share Snapshot Api Test."""
|
||||||
|
|
||||||
@ -48,7 +50,8 @@ class ShareSnapshotApiTest(test.TestCase):
|
|||||||
}
|
}
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
|
|
||||||
def test_snapshot_create(self):
|
@ddt.data('true', 'True', '<is> True', '1')
|
||||||
|
def test_snapshot_create(self, snapshot_support):
|
||||||
self.mock_object(share_api.API, 'create_snapshot',
|
self.mock_object(share_api.API, 'create_snapshot',
|
||||||
stubs.stub_snapshot_create)
|
stubs.stub_snapshot_create)
|
||||||
body = {
|
body = {
|
||||||
@ -60,7 +63,9 @@ class ShareSnapshotApiTest(test.TestCase):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
req = fakes.HTTPRequest.blank('/snapshots')
|
req = fakes.HTTPRequest.blank('/snapshots')
|
||||||
|
|
||||||
res_dict = self.controller.create(req, body)
|
res_dict = self.controller.create(req, body)
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
'snapshot': {
|
'snapshot': {
|
||||||
'id': 200,
|
'id': 200,
|
||||||
@ -86,6 +91,29 @@ class ShareSnapshotApiTest(test.TestCase):
|
|||||||
}
|
}
|
||||||
self.assertEqual(expected, res_dict)
|
self.assertEqual(expected, res_dict)
|
||||||
|
|
||||||
|
@ddt.data(0, False)
|
||||||
|
def test_snapshot_create_no_support(self, snapshot_support):
|
||||||
|
self.mock_object(share_api.API, 'create_snapshot')
|
||||||
|
self.mock_object(
|
||||||
|
share_api.API,
|
||||||
|
'get',
|
||||||
|
mock.Mock(return_value={'snapshot_support': snapshot_support}))
|
||||||
|
body = {
|
||||||
|
'snapshot': {
|
||||||
|
'share_id': 100,
|
||||||
|
'force': False,
|
||||||
|
'name': 'fake_share_name',
|
||||||
|
'description': 'fake_share_description',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req = fakes.HTTPRequest.blank('/snapshots')
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
webob.exc.HTTPUnprocessableEntity,
|
||||||
|
self.controller.create, req, body)
|
||||||
|
|
||||||
|
self.assertFalse(share_api.API.create_snapshot.called)
|
||||||
|
|
||||||
def test_snapshot_create_no_body(self):
|
def test_snapshot_create_no_body(self):
|
||||||
body = {}
|
body = {}
|
||||||
req = fakes.HTTPRequest.blank('/snapshots')
|
req = fakes.HTTPRequest.blank('/snapshots')
|
||||||
|
@ -27,6 +27,7 @@ def fake_share(**kwargs):
|
|||||||
'share_server_id': 'fake share server id',
|
'share_server_id': 'fake share server id',
|
||||||
'export_location': 'fake_location:/fake_share',
|
'export_location': 'fake_location:/fake_share',
|
||||||
'project_id': 'fake_project_uuid',
|
'project_id': 'fake_project_uuid',
|
||||||
|
'snapshot_support': 'True',
|
||||||
}
|
}
|
||||||
share.update(kwargs)
|
share.update(kwargs)
|
||||||
return db_fakes.FakeModel(share)
|
return db_fakes.FakeModel(share)
|
||||||
|
@ -76,6 +76,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
'host1@AAA': dict(share_backend_name='AAA',
|
'host1@AAA': dict(share_backend_name='AAA',
|
||||||
timestamp=None, reserved_percentage=0,
|
timestamp=None, reserved_percentage=0,
|
||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
|
snapshot_support=True,
|
||||||
pools=[dict(pool_name='pool1',
|
pools=[dict(pool_name='pool1',
|
||||||
total_capacity_gb=51,
|
total_capacity_gb=51,
|
||||||
free_capacity_gb=41,
|
free_capacity_gb=41,
|
||||||
@ -87,6 +88,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
'host2@BBB': dict(share_backend_name='BBB',
|
'host2@BBB': dict(share_backend_name='BBB',
|
||||||
timestamp=None, reserved_percentage=0,
|
timestamp=None, reserved_percentage=0,
|
||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
|
snapshot_support=True,
|
||||||
pools=[dict(pool_name='pool2',
|
pools=[dict(pool_name='pool2',
|
||||||
total_capacity_gb=52,
|
total_capacity_gb=52,
|
||||||
free_capacity_gb=42,
|
free_capacity_gb=42,
|
||||||
@ -98,6 +100,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
'host3@CCC': dict(share_backend_name='CCC',
|
'host3@CCC': dict(share_backend_name='CCC',
|
||||||
timestamp=None, reserved_percentage=0,
|
timestamp=None, reserved_percentage=0,
|
||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
|
snapshot_support=True,
|
||||||
pools=[dict(pool_name='pool3',
|
pools=[dict(pool_name='pool3',
|
||||||
total_capacity_gb=53,
|
total_capacity_gb=53,
|
||||||
free_capacity_gb=43,
|
free_capacity_gb=43,
|
||||||
@ -109,6 +112,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
'host4@DDD': dict(share_backend_name='DDD',
|
'host4@DDD': dict(share_backend_name='DDD',
|
||||||
timestamp=None, reserved_percentage=0,
|
timestamp=None, reserved_percentage=0,
|
||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
|
snapshot_support=True,
|
||||||
pools=[dict(pool_name='pool4a',
|
pools=[dict(pool_name='pool4a',
|
||||||
total_capacity_gb=541,
|
total_capacity_gb=541,
|
||||||
free_capacity_gb=441,
|
free_capacity_gb=441,
|
||||||
@ -128,6 +132,7 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
|||||||
'host5@EEE': dict(share_backend_name='EEE',
|
'host5@EEE': dict(share_backend_name='EEE',
|
||||||
timestamp=None, reserved_percentage=0,
|
timestamp=None, reserved_percentage=0,
|
||||||
driver_handles_share_servers=False,
|
driver_handles_share_servers=False,
|
||||||
|
snapshot_support=True,
|
||||||
pools=[dict(pool_name='pool5a',
|
pools=[dict(pool_name='pool5a',
|
||||||
total_capacity_gb=551,
|
total_capacity_gb=551,
|
||||||
free_capacity_gb=451,
|
free_capacity_gb=451,
|
||||||
|
@ -15,8 +15,12 @@
|
|||||||
"""
|
"""
|
||||||
Tests For Filter Scheduler.
|
Tests For Filter Scheduler.
|
||||||
"""
|
"""
|
||||||
import mock
|
|
||||||
|
|
||||||
|
import ddt
|
||||||
|
import mock
|
||||||
|
from oslo_utils import strutils
|
||||||
|
|
||||||
|
from manila.common import constants
|
||||||
from manila import context
|
from manila import context
|
||||||
from manila import exception
|
from manila import exception
|
||||||
from manila.scheduler import filter_scheduler
|
from manila.scheduler import filter_scheduler
|
||||||
@ -24,7 +28,10 @@ from manila.scheduler import host_manager
|
|||||||
from manila.tests.scheduler import fakes
|
from manila.tests.scheduler import fakes
|
||||||
from manila.tests.scheduler import test_scheduler
|
from manila.tests.scheduler import test_scheduler
|
||||||
|
|
||||||
|
SNAPSHOT_SUPPORT = constants.ExtraSpecs.SNAPSHOT_SUPPORT
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
||||||
"""Test case for Filter Scheduler."""
|
"""Test case for Filter Scheduler."""
|
||||||
|
|
||||||
@ -69,22 +76,60 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
|
|||||||
fake_context, request_spec, {})
|
fake_context, request_spec, {})
|
||||||
self.assertTrue(self.was_admin)
|
self.assertTrue(self.was_admin)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
{'name': 'foo'},
|
||||||
|
{'name': 'foo', 'extra_specs': {}},
|
||||||
|
*[{'name': 'foo', 'extra_specs': {SNAPSHOT_SUPPORT: v}}
|
||||||
|
for v in ('True', '<is> True', 'true', '1')]
|
||||||
|
)
|
||||||
@mock.patch('manila.db.service_get_all_by_topic')
|
@mock.patch('manila.db.service_get_all_by_topic')
|
||||||
def test_schedule_happy_day_share(self, _mock_service_get_all_by_topic):
|
def test__schedule_share_with_snapshot_support(
|
||||||
# Make sure there's nothing glaringly wrong with _schedule()
|
self, share_type, _mock_service_get_all_by_topic):
|
||||||
# by doing a happy day pass through.
|
|
||||||
sched = fakes.FakeFilterScheduler()
|
sched = fakes.FakeFilterScheduler()
|
||||||
sched.host_manager = fakes.FakeHostManager()
|
sched.host_manager = fakes.FakeHostManager()
|
||||||
fake_context = context.RequestContext('user', 'project',
|
fake_context = context.RequestContext('user', 'project',
|
||||||
is_admin=True)
|
is_admin=True)
|
||||||
fakes.mock_host_manager_db_calls(_mock_service_get_all_by_topic)
|
fakes.mock_host_manager_db_calls(_mock_service_get_all_by_topic)
|
||||||
request_spec = {
|
request_spec = {
|
||||||
'share_type': {'name': 'NFS'},
|
'share_type': share_type,
|
||||||
'share_properties': {'project_id': 1, 'size': 1},
|
'share_properties': {'project_id': 1, 'size': 1},
|
||||||
'share_instance_properties': {},
|
'share_instance_properties': {},
|
||||||
}
|
}
|
||||||
|
|
||||||
weighed_host = sched._schedule_share(fake_context, request_spec, {})
|
weighed_host = sched._schedule_share(fake_context, request_spec, {})
|
||||||
|
|
||||||
|
self.assertIsNotNone(weighed_host)
|
||||||
self.assertIsNotNone(weighed_host.obj)
|
self.assertIsNotNone(weighed_host.obj)
|
||||||
|
self.assertTrue(hasattr(weighed_host.obj, SNAPSHOT_SUPPORT))
|
||||||
|
expected_snapshot_support = strutils.bool_from_string(
|
||||||
|
share_type.get('extra_specs', {}).get(
|
||||||
|
SNAPSHOT_SUPPORT, 'True').split()[-1])
|
||||||
|
self.assertEqual(
|
||||||
|
expected_snapshot_support,
|
||||||
|
getattr(weighed_host.obj, SNAPSHOT_SUPPORT))
|
||||||
|
self.assertTrue(_mock_service_get_all_by_topic.called)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
*[{'name': 'foo', 'extra_specs': {SNAPSHOT_SUPPORT: v}}
|
||||||
|
for v in ('False', '<is> False', 'false', '0')]
|
||||||
|
)
|
||||||
|
@mock.patch('manila.db.service_get_all_by_topic')
|
||||||
|
def test__schedule_share_without_snapshot_support(
|
||||||
|
self, share_type, _mock_service_get_all_by_topic):
|
||||||
|
sched = fakes.FakeFilterScheduler()
|
||||||
|
sched.host_manager = fakes.FakeHostManager()
|
||||||
|
fake_context = context.RequestContext('user', 'project',
|
||||||
|
is_admin=True)
|
||||||
|
fakes.mock_host_manager_db_calls(_mock_service_get_all_by_topic)
|
||||||
|
request_spec = {
|
||||||
|
'share_type': share_type,
|
||||||
|
'share_properties': {'project_id': 1, 'size': 1},
|
||||||
|
'share_instance_properties': {'project_id': 1, 'size': 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
weighed_host = sched._schedule_share(fake_context, request_spec, {})
|
||||||
|
|
||||||
|
self.assertIsNone(weighed_host)
|
||||||
self.assertTrue(_mock_service_get_all_by_topic.called)
|
self.assertTrue(_mock_service_get_all_by_topic.called)
|
||||||
|
|
||||||
def test_max_attempts(self):
|
def test_max_attempts(self):
|
||||||
|
@ -195,6 +195,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'vendor_name': None,
|
'vendor_name': None,
|
||||||
'storage_protocol': None,
|
'storage_protocol': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'name': 'host2@back1#BBB',
|
'name': 'host2@back1#BBB',
|
||||||
@ -215,6 +216,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'vendor_name': None,
|
'vendor_name': None,
|
||||||
'storage_protocol': None,
|
'storage_protocol': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'name': 'host2@back2#CCC',
|
'name': 'host2@back2#CCC',
|
||||||
@ -235,6 +237,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'vendor_name': None,
|
'vendor_name': None,
|
||||||
'storage_protocol': None,
|
'storage_protocol': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -277,6 +280,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'vendor_name': None,
|
'vendor_name': None,
|
||||||
'storage_protocol': None,
|
'storage_protocol': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'name': 'host2@BBB#pool2',
|
'name': 'host2@BBB#pool2',
|
||||||
@ -298,6 +302,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'vendor_name': None,
|
'vendor_name': None,
|
||||||
'storage_protocol': None,
|
'storage_protocol': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'name': 'host3@CCC#pool3',
|
'name': 'host3@CCC#pool3',
|
||||||
@ -319,6 +324,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'vendor_name': None,
|
'vendor_name': None,
|
||||||
'storage_protocol': None,
|
'storage_protocol': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'name': 'host4@DDD#pool4a',
|
'name': 'host4@DDD#pool4a',
|
||||||
@ -340,6 +346,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'vendor_name': None,
|
'vendor_name': None,
|
||||||
'storage_protocol': None,
|
'storage_protocol': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'name': 'host4@DDD#pool4b',
|
'name': 'host4@DDD#pool4b',
|
||||||
@ -361,6 +368,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'vendor_name': None,
|
'vendor_name': None,
|
||||||
'storage_protocol': None,
|
'storage_protocol': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -404,6 +412,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'capabilities': {
|
'capabilities': {
|
||||||
'timestamp': None,
|
'timestamp': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
'share_backend_name': 'AAA',
|
'share_backend_name': 'AAA',
|
||||||
'free_capacity_gb': 200,
|
'free_capacity_gb': 200,
|
||||||
'driver_version': None,
|
'driver_version': None,
|
||||||
@ -424,6 +433,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'capabilities': {
|
'capabilities': {
|
||||||
'timestamp': None,
|
'timestamp': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
'share_backend_name': 'BBB',
|
'share_backend_name': 'BBB',
|
||||||
'free_capacity_gb': 100,
|
'free_capacity_gb': 100,
|
||||||
'driver_version': None,
|
'driver_version': None,
|
||||||
@ -470,6 +480,7 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
'pool_name': 'pool2',
|
'pool_name': 'pool2',
|
||||||
'timestamp': None,
|
'timestamp': None,
|
||||||
'driver_handles_share_servers': False,
|
'driver_handles_share_servers': False,
|
||||||
|
'snapshot_support': True,
|
||||||
'share_backend_name': 'BBB',
|
'share_backend_name': 'BBB',
|
||||||
'free_capacity_gb': 42,
|
'free_capacity_gb': 42,
|
||||||
'driver_version': None,
|
'driver_version': None,
|
||||||
|
@ -138,6 +138,7 @@ class EMCShareFrameworkTestCase(test.TestCase):
|
|||||||
data['reserved_percentage'] = 0
|
data['reserved_percentage'] = 0
|
||||||
data['QoS_support'] = False
|
data['QoS_support'] = False
|
||||||
data['pools'] = None
|
data['pools'] = None
|
||||||
|
data['snapshot_support'] = True
|
||||||
self.assertEqual(data, self.driver._stats)
|
self.assertEqual(data, self.driver._stats)
|
||||||
|
|
||||||
def _fake_safe_get(self, value):
|
def _fake_safe_get(self, value):
|
||||||
|
@ -469,6 +469,7 @@ class SopShareDriverTestCase(test.TestCase):
|
|||||||
'total_capacity_gb': 1234,
|
'total_capacity_gb': 1234,
|
||||||
'free_capacity_gb': 2345,
|
'free_capacity_gb': 2345,
|
||||||
'pools': None,
|
'pools': None,
|
||||||
|
'snapshot_support': True,
|
||||||
}
|
}
|
||||||
self.mock_object(self._driver, '_get_sop_filesystem_stats',
|
self.mock_object(self._driver, '_get_sop_filesystem_stats',
|
||||||
mock.Mock(return_value=(1234, 2345)))
|
mock.Mock(return_value=(1234, 2345)))
|
||||||
|
@ -469,6 +469,7 @@ class HP3ParDriverTestCase(test.TestCase):
|
|||||||
'thin_provisioning': True,
|
'thin_provisioning': True,
|
||||||
'dedupe': False,
|
'dedupe': False,
|
||||||
'hpe3par_flash_cache': False,
|
'hpe3par_flash_cache': False,
|
||||||
|
'snapshot_support': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
result = self.driver.get_share_stats(refresh=True)
|
result = self.driver.get_share_stats(refresh=True)
|
||||||
@ -501,6 +502,7 @@ class HP3ParDriverTestCase(test.TestCase):
|
|||||||
'thin_provisioning': True,
|
'thin_provisioning': True,
|
||||||
'total_capacity_gb': 0,
|
'total_capacity_gb': 0,
|
||||||
'vendor_name': 'HP',
|
'vendor_name': 'HP',
|
||||||
|
'snapshot_support': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
result = self.driver.get_share_stats(refresh=True)
|
result = self.driver.get_share_stats(refresh=True)
|
||||||
|
@ -1103,6 +1103,7 @@ class HuaweiShareDriverTestCase(test.TestCase):
|
|||||||
expected['total_capacity_gb'] = 0.0
|
expected['total_capacity_gb'] = 0.0
|
||||||
expected['free_capacity_gb'] = 0.0
|
expected['free_capacity_gb'] = 0.0
|
||||||
expected['QoS_support'] = False
|
expected['QoS_support'] = False
|
||||||
|
expected["snapshot_support"] = False
|
||||||
expected["pools"] = []
|
expected["pools"] = []
|
||||||
pool_thin = dict(
|
pool_thin = dict(
|
||||||
pool_name='OpenStack_Pool',
|
pool_name='OpenStack_Pool',
|
||||||
|
@ -534,6 +534,7 @@ class GlusterfsShareDriverTestCase(test.TestCase):
|
|||||||
'total_capacity_gb': 2,
|
'total_capacity_gb': 2,
|
||||||
'free_capacity_gb': 2,
|
'free_capacity_gb': 2,
|
||||||
'pools': None,
|
'pools': None,
|
||||||
|
'snapshot_support': False,
|
||||||
}
|
}
|
||||||
test_statvfs = mock.Mock(f_frsize=4096, f_blocks=524288,
|
test_statvfs = mock.Mock(f_frsize=4096, f_blocks=524288,
|
||||||
f_bavail=524288)
|
f_bavail=524288)
|
||||||
|
@ -1104,6 +1104,7 @@ class GlusterfsNativeShareDriverTestCase(test.TestCase):
|
|||||||
'total_capacity_gb': 'infinite',
|
'total_capacity_gb': 'infinite',
|
||||||
'free_capacity_gb': 'infinite',
|
'free_capacity_gb': 'infinite',
|
||||||
'pools': None,
|
'pools': None,
|
||||||
|
'snapshot_support': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
self._driver._update_share_stats()
|
self._driver._update_share_stats()
|
||||||
|
@ -118,6 +118,7 @@ class ShareDriverTestCase(test.TestCase):
|
|||||||
'free_capacity_gb', 'total_capacity_gb',
|
'free_capacity_gb', 'total_capacity_gb',
|
||||||
'driver_handles_share_servers',
|
'driver_handles_share_servers',
|
||||||
'reserved_percentage', 'vendor_name', 'storage_protocol',
|
'reserved_percentage', 'vendor_name', 'storage_protocol',
|
||||||
|
'snapshot_support',
|
||||||
]
|
]
|
||||||
share_driver = driver.ShareDriver(True, configuration=conf)
|
share_driver = driver.ShareDriver(True, configuration=conf)
|
||||||
fake_stats = {'fake_key': 'fake_value'}
|
fake_stats = {'fake_key': 'fake_value'}
|
||||||
@ -219,3 +220,88 @@ class ShareDriverTestCase(test.TestCase):
|
|||||||
else:
|
else:
|
||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
share_driver.check_for_setup_error)
|
share_driver.check_for_setup_error)
|
||||||
|
|
||||||
|
def test_snapshot_support_exists(self):
|
||||||
|
driver.CONF.set_default('driver_handles_share_servers', True)
|
||||||
|
fake_method = lambda *args, **kwargs: None
|
||||||
|
child_methods = {
|
||||||
|
"create_snapshot": fake_method,
|
||||||
|
"delete_snapshot": fake_method,
|
||||||
|
"create_share_from_snapshot": fake_method,
|
||||||
|
}
|
||||||
|
child_class_instance = type(
|
||||||
|
"NotRedefined", (driver.ShareDriver, ), child_methods)(True)
|
||||||
|
self.mock_object(child_class_instance, "configuration")
|
||||||
|
|
||||||
|
child_class_instance._update_share_stats()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
True, child_class_instance._stats["snapshot_support"])
|
||||||
|
self.assertTrue(child_class_instance.configuration.safe_get.called)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
(),
|
||||||
|
("create_snapshot"),
|
||||||
|
("delete_snapshot"),
|
||||||
|
("create_share_from_snapshot"),
|
||||||
|
("create_snapshot", "delete_snapshot"),
|
||||||
|
("create_snapshot", "create_share_from_snapshot"),
|
||||||
|
("delete_snapshot", "create_share_from_snapshot"),
|
||||||
|
("create_snapshot", "delete_snapshot",
|
||||||
|
"create_share_from_snapshotFOO"),
|
||||||
|
("create_snapshot", "delete_snapshot",
|
||||||
|
"FOOcreate_share_from_snapshot"),
|
||||||
|
)
|
||||||
|
def test_snapshot_support_absent(self, methods):
|
||||||
|
driver.CONF.set_default('driver_handles_share_servers', True)
|
||||||
|
fake_method = lambda *args, **kwargs: None
|
||||||
|
child_methods = {}
|
||||||
|
for method in methods:
|
||||||
|
child_methods[method] = fake_method
|
||||||
|
child_class_instance = type(
|
||||||
|
"NotRedefined", (driver.ShareDriver, ), child_methods)(True)
|
||||||
|
self.mock_object(child_class_instance, "configuration")
|
||||||
|
|
||||||
|
child_class_instance._update_share_stats()
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
False, child_class_instance._stats["snapshot_support"])
|
||||||
|
self.assertTrue(child_class_instance.configuration.safe_get.called)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_snapshot_support_not_exists_and_set_explicitly(
|
||||||
|
self, snapshots_are_supported):
|
||||||
|
driver.CONF.set_default('driver_handles_share_servers', True)
|
||||||
|
child_class_instance = type(
|
||||||
|
"NotRedefined", (driver.ShareDriver, ), {})(True)
|
||||||
|
self.mock_object(child_class_instance, "configuration")
|
||||||
|
|
||||||
|
child_class_instance._update_share_stats(
|
||||||
|
{"snapshot_support": snapshots_are_supported})
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
snapshots_are_supported,
|
||||||
|
child_class_instance._stats["snapshot_support"])
|
||||||
|
self.assertTrue(child_class_instance.configuration.safe_get.called)
|
||||||
|
|
||||||
|
@ddt.data(True, False)
|
||||||
|
def test_snapshot_support_exists_and_set_explicitly(
|
||||||
|
self, snapshots_are_supported):
|
||||||
|
driver.CONF.set_default('driver_handles_share_servers', True)
|
||||||
|
fake_method = lambda *args, **kwargs: None
|
||||||
|
child_methods = {
|
||||||
|
"create_snapshot": fake_method,
|
||||||
|
"delete_snapshot": fake_method,
|
||||||
|
"create_share_from_snapshot": fake_method,
|
||||||
|
}
|
||||||
|
child_class_instance = type(
|
||||||
|
"NotRedefined", (driver.ShareDriver, ), child_methods)(True)
|
||||||
|
self.mock_object(child_class_instance, "configuration")
|
||||||
|
|
||||||
|
child_class_instance._update_share_stats(
|
||||||
|
{"snapshot_support": snapshots_are_supported})
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
snapshots_are_supported,
|
||||||
|
child_class_instance._stats["snapshot_support"])
|
||||||
|
self.assertTrue(child_class_instance.configuration.safe_get.called)
|
||||||
|
Loading…
Reference in New Issue
Block a user