WIP:Add Share Snapshot Metadata Resource

Adds a share snapshot metadata resource including:

New share snapshot metadata database table
Accompanying share snapshot metadata table model
Accompanying database methods to access and manipulate metadata
Accomapnying API methods to access and manipulate metadata
Additional exception

This code will be abstracted to include more user-facing resources.
Can be used with curl commands

This change adds resource metadata tables to the migration including:
Share
Share Instance
Share Instance Export location
Share Access Rules
Snapshots
Snapshots Instance Export location
Snapshots Access Rules
Share Groups
Share Group Snapshots
Security Services
Share Networks
Share Network Subnets

Change-Id: I2e7d6edafc929e1c63425f6f4be02335fc263a34
This commit is contained in:
ashrod98 2021-07-19 18:08:30 +00:00
parent 1a39505039
commit ef0fce81af
9 changed files with 1092 additions and 10 deletions

View File

@ -51,6 +51,7 @@ 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
from manila.api.v2 import share_snapshot_metadata
from manila.api.v2 import share_snapshots
from manila.api.v2 import share_types
from manila.api.v2 import shares
@ -296,6 +297,49 @@ class APIRouter(manila.api.openstack.APIRouter):
action="update_all",
conditions={"method": ["PUT"]})
self.resources["share_snapshot_metadata"] = (
share_snapshot_metadata.create_resource())
share_snapshot_metadata_controller = (
self.resources["share_snapshot_metadata"])
mapper.resource("share_snapshot_metadata", "metadata",
controller=share_snapshot_metadata_controller,
parent_resource=dict(member_name="share_snapshot",
collection_name="share_snapshots"
))
for path_prefix in ['/{project_id}', '']:
# project_id is optional
mapper.connect("metadata",
"%s/snapshots/{share_snapshot_id}/metadata"
% path_prefix,
controller=share_snapshot_metadata_controller,
action="create",
conditions={"method": ["POST"]})
mapper.connect("metadata",
"%s/snapshots/{share_snapshot_id}/metadata"
% path_prefix,
controller=share_snapshot_metadata_controller,
action="update_all",
conditions={"method": ["PUT"]})
mapper.connect("metadata",
"%s/snapshots/{share_snapshot_id}/metadata"
% path_prefix,
controller=share_snapshot_metadata_controller,
action="index",
conditions={"method": ["GET"]})
mapper.connect("metadata",
"%s/snapshots/{share_snapshot_id}/metadata/{key}"
% path_prefix,
controller=share_snapshot_metadata_controller,
action="show",
conditions={"method": ["GET"]})
mapper.connect("metadata",
"%s/snapshots/{share_snapshot_id}/metadata/{key}"
% path_prefix,
controller=share_snapshot_metadata_controller,
action="delete",
conditions={"method": ["DELETE"]})
self.resources["limits"] = limits.create_resource()
mapper.resource("limit", "limits",
controller=self.resources["limits"])

View File

@ -0,0 +1,119 @@
# 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 six.moves import http_client
import webob
from webob import exc
from manila.api.openstack import wsgi
from manila import db
from manila import exception
from manila.i18n import _
from manila import share
class ShareSnapshotMetadataController(wsgi.Controller, wsgi.AdminActionsMixin):
"""The share metadata API V2 controller for the OpenStack API."""
resource_name = 'share_snapshot_metadata'
def __init__(self):
self.share_api = share.API()
super(ShareSnapshotMetadataController, self).__init__()
def _get_metadata(self, context, share_snapshot_id):
try:
db.share_snapshot_get(context, share_snapshot_id)
metadata = db.share_snapshot_metadata_get(context,
share_snapshot_id)
except exception.ShareSnapshotNotFound as e:
raise exc.HTTPNotFound(explanation=e)
return metadata
def _index(self, req, share_snapshot_id):
context = req.environ['manila.context']
self._get_metadata(context, share_snapshot_id)
metadata = db.share_snapshot_metadata_get(
context, share_snapshot_id)
return {'metadata': metadata}
def index(self, req, share_snapshot_id):
"""Returns the list of metadata for a given share snapshot."""
return self._index(req, share_snapshot_id)
def create(self, req, share_snapshot_id, body):
"""Returns the new metadata item created."""
try:
metadata = body['metadata']
except (KeyError, TypeError):
msg = _("Malformed request body")
raise exc.HTTPBadRequest(explanation=msg)
context = req.environ['manila.context']
new_metadata = db.share_snapshot_metadata_update(
context, share_snapshot_id, metadata)
return {'metadata': new_metadata}
def update(self, req, share_snapshot_id, body):
"""Returns the updated metadata items."""
try:
metadata = body['metadata']
except (TypeError, KeyError):
expl = _('Malformed request body')
raise exc.HTTPBadRequest(explanation=expl)
context = req.environ['manila.context']
new_metadata = db.share_snapshot_metadata_update(
context, share_snapshot_id, metadata)
return {'metadata': new_metadata}
def update_all(self, req, share_snapshot_id, body):
"""Deletes existing metadata, and returns the updated metadata."""
try:
metadata = body['metadata']
except (TypeError, KeyError):
expl = _('Malformed request body')
raise exc.HTTPBadRequest(explanation=expl)
context = req.environ['manila.context']
metaref = self._get_metadata(context, share_snapshot_id)
for key in metaref:
db.share_snapshot_metadata_delete(
context, share_snapshot_id, key)
new_metadata = db.share_snapshot_metadata_update(
context, share_snapshot_id, metadata)
return {'metadata': new_metadata}
def show(self, req, share_snapshot_id, key):
"""Return metadata item."""
context = req.environ['manila.context']
item = db.share_snapshot_metadata_get_item(
context, share_snapshot_id, key)
return {'metadata': {key: item}}
def delete(self, req, share_snapshot_id, key):
"""Deletes existing metadata items."""
context = req.environ['manila.context']
db.share_snapshot_metadata_delete(
context, share_snapshot_id, key)
return webob.Response(status_int=http_client.OK)
def create_resource():
return wsgi.Resource(ShareSnapshotMetadataController())

View File

@ -734,7 +734,32 @@ def share_snapshot_instance_export_location_delete(context, el_id):
return IMPL.share_snapshot_instance_export_location_delete(context, el_id)
####################
def share_snapshot_metadata_get(context, share_snapshot_id):
"""Get all metadata for a share snapshot."""
return IMPL.share_snapshot_metadata_get(context, share_snapshot_id)
def share_snapshot_metadata_get_item(context, share_snapshot_id, key):
"""Get all metadata for a share snapshot."""
return IMPL.share_snapshot_metadata_get_item(context,
share_snapshot_id, key)
def share_snapshot_metadata_delete(context, share_snapshot_id, key):
"""Delete the given metadata item."""
IMPL.share_snapshot_metadata_delete(context, share_snapshot_id, key)
def share_snapshot_metadata_update(context, share_snapshot_id, metadata):
"""Update metadata if it exists, otherwise create it."""
return IMPL.share_snapshot_metadata_update(context,
share_snapshot_id, metadata)
###################
def security_service_create(context, values):
"""Create security service DB record."""
return IMPL.security_service_create(context, values)

View File

@ -0,0 +1,423 @@
# 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_metadata
Revision ID: aa53865c45d3
Revises: fbdfabcba377
Create Date: 2021-07-19 15:22:25.274494
"""
# revision identifiers, used by Alembic.
revision = 'aa53865c45d3'
down_revision = 'fbdfabcba377'
from alembic import op
from manila.db.migrations import utils
from oslo_log import log
import sqlalchemy as sql
LOG = log.getLogger(__name__)
# Existing Tables
# share_metadata_table_name = 'share_metadata'
# share_access_rules_metadata_table_name = 'share_access_rules_metadata'
# share_instance_export_locations_metadata_table_name = (
# 'share_instance_export_locations_metadata')
# New Tables
share_instance_metadata_table_name = 'share_instance_metadata'
share_snapshot_metadata_table_name = 'share_snapshot_metadata'
share_snapshot_instance_export_locations_metadata_table_name = (
'share_snapshot_instance_export_locations_metadata')
share_snapshot_access_rules_metadata_table_name = (
'share_snapshot_access_rules_metadata')
share_group_metadata_table_name = 'share_group_metadata'
share_group_snapshots_metadata_table_name = 'share_group_snapshots_metadata'
security_service_metadata_table_name = 'security_service_metadata'
share_networks_metadata_table_name = 'share_networks_metadata'
share_network_subnets_metadata_table_name = 'share_network_subnets_metadata'
def upgrade():
connection = op.get_bind()
try:
op.create_table(
share_snapshot_metadata_table_name,
sql.Column('created_at', sql.DateTime),
sql.Column('updated_at', sql.DateTime),
sql.Column('deleted_at', sql.DateTime),
sql.Column('deleted', sql.Integer, default=0),
sql.Column('share_snapshot_id', sql.String(36),
sql.ForeignKey('share_snapshots.id'), nullable=False),
sql.Column('key', sql.String(255), nullable=False),
sql.Column('value', sql.String(1023), nullable=False),
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
sql.Column('user_modifiable', sql.Boolean, default=True,
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
share_snapshot_metadata_table_name)
raise
try:
op.create_table(
share_snapshot_instance_export_locations_metadata_table_name,
sql.Column('created_at', sql.DateTime),
sql.Column('updated_at', sql.DateTime),
sql.Column('deleted_at', sql.DateTime),
sql.Column('deleted', sql.Integer, default=0),
sql.Column(
'share_snapshot_instance_export_locations_id',
sql.String(36), sql.ForeignKey(
'share_snapshot_instance_export_locations.id'),
nullable=False),
sql.Column('key', sql.String(255), nullable=False),
sql.Column('value', sql.String(1023), nullable=False),
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
sql.Column('user_modifiable', sql.Boolean, default=True,
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
share_snapshot_instance_export_locations_metadata_table_name)
raise
try:
op.create_table(
share_instance_metadata_table_name,
sql.Column('created_at', sql.DateTime),
sql.Column('updated_at', sql.DateTime),
sql.Column('deleted_at', sql.DateTime),
sql.Column('deleted', sql.Integer, default=0),
sql.Column('share_instance_id', sql.String(36), sql.ForeignKey(
'share_instances.id'), nullable=False),
sql.Column('key', sql.String(255), nullable=False),
sql.Column('value', sql.String(1023), nullable=False),
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
sql.Column('user_modifiable', sql.Boolean, default=True,
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
share_instance_metadata_table_name)
raise
try:
op.create_table(
share_snapshot_access_rules_metadata_table_name,
sql.Column('created_at', sql.DateTime),
sql.Column('updated_at', sql.DateTime),
sql.Column('deleted_at', sql.DateTime),
sql.Column('deleted', sql.Integer, default=0),
sql.Column(
'access_id', sql.String(36), sql.ForeignKey(
'share_snapshot_access_map.id'),
nullable=False),
sql.Column('key', sql.String(255), nullable=False),
sql.Column('value', sql.String(1023), nullable=False),
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
sql.Column('user_modifiable', sql.Boolean, default=True,
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
share_snapshot_access_rules_metadata_table_name)
raise
try:
op.create_table(
share_group_metadata_table_name,
sql.Column('created_at', sql.DateTime),
sql.Column('updated_at', sql.DateTime),
sql.Column('deleted_at', sql.DateTime),
sql.Column('deleted', sql.Integer, default=0),
sql.Column(
'share_group_id',
sql.String(36), sql.ForeignKey(
'share_groups.id'), nullable=False),
sql.Column('key', sql.String(255), nullable=False),
sql.Column('value', sql.String(1023), nullable=False),
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
sql.Column('user_modifiable', sql.Boolean, default=True,
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
share_group_metadata_table_name)
raise
try:
op.create_table(
share_group_snapshots_metadata_table_name,
sql.Column('created_at', sql.DateTime),
sql.Column('updated_at', sql.DateTime),
sql.Column('deleted_at', sql.DateTime),
sql.Column('deleted', sql.Integer, default=0),
sql.Column(
'share_group_snapshot_id', sql.String(36),
sql.ForeignKey(
'share_group_snapshots.id'), nullable=False),
sql.Column('key', sql.String(255), nullable=False),
sql.Column('value', sql.String(1023), nullable=False),
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
sql.Column('user_modifiable', sql.Boolean, default=True,
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
share_group_snapshots_metadata_table_name)
raise
try:
op.create_table(
security_service_metadata_table_name,
sql.Column('created_at', sql.DateTime),
sql.Column('updated_at', sql.DateTime),
sql.Column('deleted_at', sql.DateTime),
sql.Column('deleted', sql.Integer, default=0),
sql.Column(
'security_service_id', sql.String(36),
sql.ForeignKey('security_services.id'), nullable=False),
sql.Column('key', sql.String(255), nullable=False),
sql.Column('value', sql.String(1023), nullable=False),
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
sql.Column('user_modifiable', sql.Boolean, default=True,
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
security_service_metadata_table_name)
raise
try:
op.create_table(
share_networks_metadata_table_name,
sql.Column('created_at', sql.DateTime),
sql.Column('updated_at', sql.DateTime),
sql.Column('deleted_at', sql.DateTime),
sql.Column('deleted', sql.Integer, default=0),
sql.Column(
'share_network_id', sql.String(36),
sql.ForeignKey('share_networks.id'), nullable=False),
sql.Column('key', sql.String(255), nullable=False),
sql.Column('value', sql.String(1023), nullable=False),
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
sql.Column('user_modifiable', sql.Boolean, default=True,
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
share_networks_metadata_table_name)
raise
try:
op.create_table(
share_network_subnets_metadata_table_name,
sql.Column('created_at', sql.DateTime),
sql.Column('updated_at', sql.DateTime),
sql.Column('deleted_at', sql.DateTime),
sql.Column('deleted', sql.Integer, default=0),
sql.Column(
'share_network_subnets_id', sql.String(36),
sql.ForeignKey('share_network_subnets.id'), nullable=False),
sql.Column('key', sql.String(255), nullable=False),
sql.Column('value', sql.String(1023), nullable=False),
sql.Column('id', sql.Integer, primary_key=True, nullable=False),
sql.Column('user_modifiable', sql.Boolean, default=True,
nullable=False),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
share_network_subnets_metadata_table_name)
raise
try:
user_modifiable = sql.Column(
'user_modifiable', sql.Boolean, default=True, nullable=False)
op.add_column(
'share_instance_export_locations_metadata', user_modifiable)
share_instance_export_locations_metadata_table_name = utils.load_table(
'share_instance_export_locations_metadata', connection)
op.execute(
share_instance_export_locations_metadata_table_name.update(
).values({'user_modifiable': True}))
except Exception:
LOG.error("Column user_modifiable not created on |%s|!",
share_instance_export_locations_metadata_table_name)
raise
try:
user_modifiable = sql.Column(
'user_modifiable', sql.Boolean, default=True, nullable=False)
op.add_column('share_access_rules_metadata', user_modifiable)
share_access_rules_metadata_table_name = utils.load_table(
'share_access_rules_metadata', connection)
op.execute(
share_access_rules_metadata_table_name.update().values({
'user_modifiable': True,
}))
except Exception:
LOG.error("Column user_modifiable not created on |%s|!",
share_access_rules_metadata_table_name)
raise
try:
user_modifiable = sql.Column(
'user_modifiable', sql.Boolean, default=True, nullable=False)
op.add_column('share_metadata', user_modifiable)
share_metadata_table_name = utils.load_table(
'share_metadata', connection)
op.execute(
share_metadata_table_name.update().values({
'user_modifiable': True,
}))
except Exception:
LOG.error("Column user_modifiable not created on |%s|!",
share_metadata_table_name)
raise
def downgrade():
try:
op.drop_column(
'share_metadata', 'user_modifiable')
except Exception:
LOG.error(
"Column user_modifiable not dropped from %s",
'share_metadata')
raise
try:
op.drop_column(
'share_access_rules_metadata', 'user_modifiable')
except Exception:
LOG.error(
"Column user_modifiable not dropped from %s",
'share_access_rules_metadata')
raise
try:
op.drop_column(
'share_instance_export_locations_metadata',
'user_modifiable')
except Exception:
LOG.error(
"Column user_modifiable not dropped from %s",
'share_instance_export_locations_metadata')
raise
try:
op.drop_table(
share_network_subnets_metadata_table_name)
except Exception:
LOG.error(
"%s table not dropped",
share_network_subnets_metadata_table_name)
raise
try:
op.drop_table(
share_networks_metadata_table_name)
except Exception:
LOG.error(
"%s table not dropped",
share_networks_metadata_table_name)
raise
try:
op.drop_table(
security_service_metadata_table_name)
except Exception:
LOG.error(
"%s table not dropped",
security_service_metadata_table_name)
raise
try:
op.drop_table(
share_group_snapshots_metadata_table_name)
except Exception:
LOG.error(
"%s table not dropped",
share_group_snapshots_metadata_table_name)
raise
try:
op.drop_table(
share_group_metadata_table_name)
except Exception:
LOG.error(
"%s table not dropped",
share_group_metadata_table_name)
raise
try:
op.drop_table(share_snapshot_access_rules_metadata_table_name)
except Exception:
LOG.error(
"%s table not dropped",
share_snapshot_access_rules_metadata_table_name)
raise
try:
op.drop_table(share_instance_metadata_table_name)
except Exception:
LOG.error(
"%s table not dropped",
share_instance_metadata_table_name)
raise
try:
op.drop_table(
share_snapshot_instance_export_locations_metadata_table_name)
except Exception:
LOG.error(
"%s table not dropped",
share_snapshot_instance_export_locations_metadata_table_name)
raise
try:
op.drop_table(share_snapshot_metadata_table_name)
except Exception:
LOG.error("%s table not dropped", share_snapshot_metadata_table_name)
raise

View File

@ -201,6 +201,20 @@ def require_share_exists(f):
return wrapper
def require_share_snapshot_exists(f):
"""Decorator to require the specified share snapshot to exist.
Requires the wrapped function to use context and share_snapshot_id as
their first two arguments.
"""
@wraps(f)
def wrapper(context, share_snapshot_id, *args, **kwargs):
_share_snapshot_metadata_get(context, share_snapshot_id)
return f(context, share_snapshot_id, *args, **kwargs)
wrapper.__name__ = f.__name__
return wrapper
def require_share_instance_exists(f):
"""Decorator to require the specified share instance to exist.
@ -2707,6 +2721,8 @@ def share_snapshot_instance_delete(context, snapshot_instance_id,
snapshot = share_snapshot_get(
context, snapshot_instance_ref['snapshot_id'], session=session)
if len(snapshot.instances) == 0:
session.query(models.ShareSnapshotMetadata).filter_by(
share_snapshot_id=snapshot['id']).soft_delete()
snapshot.soft_delete(session=session)
@ -2807,6 +2823,8 @@ def share_snapshot_create(context, create_values,
create_snapshot_instance=True):
values = copy.deepcopy(create_values)
values = ensure_model_dict_has_id(values)
values['share_snapshot_metadata'] = _metadata_refs(
values.get('metadata'), models.ShareSnapshotMetadata)
snapshot_ref = models.ShareSnapshot()
snapshot_instance_values, snapshot_values = (
@ -2862,6 +2880,7 @@ def share_snapshot_get(context, snapshot_id, session=None):
filter_by(id=snapshot_id).
options(joinedload('share')).
options(joinedload('instances')).
options(joinedload('share_snapshot_metadata')).
first())
if not result:
@ -2903,7 +2922,12 @@ def _share_snapshot_get_all_with_filters(context, project_id=None,
'key': filters['usage'],
'ek': usage_filter_keys}
raise exception.InvalidInput(reason=msg)
if 'metadata' in filters:
for k, v in filters['metadata'].items():
# pylint: disable=no-member
query = query.filter(
or_(models.ShareSnapshot.share_snapshot_metadata.any(
key=k, value=v)))
# Apply sorting
try:
attr = getattr(models.ShareSnapshot, sort_key)
@ -3356,8 +3380,13 @@ def share_metadata_get(context, share_id):
@require_context
@require_share_exists
def share_metadata_delete(context, share_id, key):
(_share_metadata_get_query(context, share_id).
filter_by(key=key).soft_delete())
session = get_session()
meta_ref = _share_metadata_get_query(
context, share_id, session=session
).filter_by(key=key).first()
if not meta_ref:
raise exception.ShareMetadataNotFound()
meta_ref.soft_delete(session=session)
@require_context
@ -3434,6 +3463,102 @@ def _share_metadata_get_item(context, share_id, key, session=None):
return result
############################
# Share Snapshot Metadata functions
############################
@require_context
@require_share_snapshot_exists
def share_snapshot_metadata_get(context, share_snapshot_id):
session = get_session()
return _share_snapshot_metadata_get(context,
share_snapshot_id, session=session)
@require_context
@require_share_snapshot_exists
def share_snapshot_metadata_delete(context, share_snapshot_id, key):
session = get_session()
meta_ref = _share_snapshot_metadata_get_query(
context, share_snapshot_id, session=session
).filter_by(key=key).first()
if not meta_ref:
raise exception.ShareSnapshotMetadataNotFound()
meta_ref.soft_delete(session=session)
@require_context
@require_share_snapshot_exists
def share_snapshot_metadata_update(context, share_snapshot_id,
metadata):
session = get_session()
return _share_snapshot_metadata_update(context, share_snapshot_id,
metadata, session=session)
def share_snapshot_metadata_get_item(context, share_snapshot_id,
key, session=None):
session = get_session()
row = _share_snapshot_metadata_get_query(
context, share_snapshot_id, session=session
).filter_by(key=key).first()
result = {}
result[row['key']] = row['value']
if not result:
raise exception.ShareSnapshotMetadataNotFound()
return result
def _share_snapshot_metadata_get_query(context, share_snapshot_id,
session=None):
session = session or get_session()
return (model_query(context, models.ShareSnapshotMetadata,
session=session,
read_deleted="no").
filter_by(share_snapshot_id=share_snapshot_id).
options(joinedload('share_snapshot')))
def _share_snapshot_metadata_get(context, share_snapshot_id, session=None):
session = session or get_session()
rows = _share_snapshot_metadata_get_query(context, share_snapshot_id,
session=session).all()
result = {}
for row in rows:
result[row['key']] = row['value']
return result
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
def _share_snapshot_metadata_update(context, share_snapshot_id,
metadata, session=None):
if not session:
session = get_session()
with session.begin():
meta_ref = None
# LOG.warning("dict for _update: %s", dict(metadata))
# Now update all existing items with new values, or create new meta
# objects
for meta_key, meta_value in metadata.items():
# update the value whether it exists or not
item = {"value": meta_value}
meta_ref = _share_snapshot_metadata_get_query(
context, share_snapshot_id,
session=session).filter_by(key=meta_key).first()
if not meta_ref:
meta_ref = models.ShareSnapshotMetadata()
item.update({"key": meta_key,
"share_snapshot_id": share_snapshot_id})
meta_ref.update(item)
meta_ref.save(session=session)
return metadata
############################
# Export locations functions
############################

View File

@ -459,16 +459,16 @@ class ShareInstanceExportLocationsMetadata(BASE, ManilaBase):
ForeignKey("share_instance_export_locations.id"), nullable=False)
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(String(36), default='False')
user_modifiable = Column(Boolean, default=True, nullable=False)
export_location = orm.relationship(
ShareInstanceExportLocations,
backref="_el_metadata_bare",
foreign_keys=export_location_id,
lazy='immediate',
primaryjoin="and_("
"%(cls_name)s.export_location_id == "
"ShareInstanceExportLocations.id,"
"%(cls_name)s.deleted == 0)" % {
"cls_name": "ShareInstanceExportLocationsMetadata"})
"ShareInstanceExportLocationsMetadata.export_location_id"
" == ShareInstanceExportLocations.id,"
"ShareInstanceExportLocationsMetadata.deleted == 0)")
@property
def export_location_uuid(self):
@ -531,11 +531,11 @@ class ShareMetadata(BASE, ManilaBase):
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
share_id = Column(String(36), ForeignKey('shares.id'), nullable=False)
user_modifiable = Column(Boolean, default=True, nullable=False)
share = orm.relationship(Share, backref="share_metadata",
foreign_keys=share_id,
primaryjoin='and_('
'ShareMetadata.share_id == Share.id,'
'ShareMetadata.deleted == 0)')
'ShareMetadata.share_id == Share.id)')
class ShareAccessMapping(BASE, ManilaBase):
@ -580,8 +580,10 @@ class ShareAccessRulesMetadata(BASE, ManilaBase):
deleted = Column(String(36), default='False')
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(String(36), default='False')
access_id = Column(String(36), ForeignKey('share_access_map.id'),
nullable=False)
user_modifiable = Column(Boolean, default=True, nullable=False)
access = orm.relationship(
ShareAccessMapping, backref="share_access_rules_metadata",
foreign_keys=access_id,
@ -734,6 +736,25 @@ class ShareSnapshot(BASE, ManilaBase):
'ShareSnapshot.deleted == "False")')
class ShareSnapshotMetadata(BASE, ManilaBase):
"""Represents a metadata key/value pair for a snapshot."""
__tablename__ = 'share_snapshot_metadata'
id = Column(Integer, primary_key=True)
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(Integer, default=0)
share_snapshot_id = Column(String(36), ForeignKey(
'share_snapshots.id'), nullable=False)
user_modifiable = Column(Boolean, default=True, nullable=False)
share_snapshot = orm.relationship(
ShareSnapshot, backref="share_snapshot_metadata",
foreign_keys=share_snapshot_id,
primaryjoin='and_('
'ShareSnapshotMetadata.share_snapshot_id == ShareSnapshot.id,'
'ShareSnapshotMetadata.deleted == 0)')
class ShareSnapshotInstance(BASE, ManilaBase):
"""Represents a snapshot of a share."""
__tablename__ = 'share_snapshot_instances'
@ -850,6 +871,27 @@ class ShareSnapshotAccessMapping(BASE, ManilaBase):
)
class ShareSnapshotAccessRulesMetadata(BASE, ManilaBase):
"""Represents a metadata key/value pair for a share access rule."""
__tablename__ = 'share_snapshot_access_rules_metadata'
id = Column(Integer, primary_key=True)
deleted = Column(String(36), default='False')
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(Integer, default=0)
access_id = Column(String(36), ForeignKey('share_snapshot_access_map.id'),
nullable=False)
user_modifiable = Column(Boolean, default=True, nullable=False)
access = orm.relationship(
ShareSnapshotAccessMapping,
backref="share_snapshot_access_rules_metadata",
foreign_keys=access_id,
primaryjoin='and_('
'ShareSnapshotAccessRulesMetadata.access_id == '
'ShareSnapshotAccessMapping.id,'
'ShareSnapshotAccessRulesMetadata.deleted == "False")')
class ShareSnapshotInstanceAccessMapping(BASE, ManilaBase):
"""Represents access to individual share snapshot instances."""
@ -893,6 +935,31 @@ class ShareSnapshotInstanceExportLocation(BASE, ManilaBase):
deleted = Column(String(36), default='False')
class ShareSnapshotInstanceExportLocationMetadata(BASE, ManilaBase):
"""Represents a metadata key/value pair for snapshot export locations."""
__tablename__ = 'share_snapshot_instance_export_locations_metadata'
id = Column(Integer, primary_key=True)
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(Integer, default=0)
share_snapshot_instance_export_locations_id = (
Column(String(36), ForeignKey(
'share_snapshot_instance_export_locations.id'), nullable=False))
user_modifiable = Column(Boolean, default=True, nullable=False)
share_snapshot_instance_export_locations = orm.relationship(
ShareSnapshotInstanceExportLocation,
backref="share_snapshot_instance_export_locations_metadata",
foreign_keys=share_snapshot_instance_export_locations_id,
primaryjoin='and_('
'ShareSnapshotInstanceExportLocationMetadata.'
'share_snapshot_instance_export_locations_id =='
'ShareSnapshotInstanceExportLocation.id,'
'ShareSnapshotInstanceExportLocationMetadata.deleted == 0)')
class SecurityService(BASE, ManilaBase):
"""Security service information for manila shares."""
@ -911,6 +978,25 @@ class SecurityService(BASE, ManilaBase):
ou = Column(String(255), nullable=True)
class SecurityServiceMetadata(BASE, ManilaBase):
"""Represents a metadata key/value pair for a security service."""
__tablename__ = 'security_service_metadata'
id = Column(Integer, primary_key=True)
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(Integer, default=0)
security_service_id = Column(String(36), ForeignKey(
'security_services.id'), nullable=False)
user_modifiable = Column(Boolean, default=True, nullable=False)
security_service = orm.relationship(
SecurityService, backref="security_service_metadata",
foreign_keys=security_service_id,
primaryjoin='and_('
'SecurityServiceMetadata.security_service_id == SecurityService.id,'
'SecurityServiceMetadata.deleted == 0)')
class ShareNetwork(BASE, ManilaBase):
"""Represents network data used by share."""
__tablename__ = 'share_networks'
@ -964,6 +1050,25 @@ class ShareNetwork(BASE, ManilaBase):
return all(share_servers_support_updating)
class ShareNetworkMetadata(BASE, ManilaBase):
"""Represents a metadata key/value pair for a share network."""
__tablename__ = 'share_networks_metadata'
id = Column(Integer, primary_key=True)
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(Integer, default=0)
share_network_id = Column(String(36), ForeignKey(
'share_networks.id'), nullable=False)
user_modifiable = Column(Boolean, default=True, nullable=False)
share_network = orm.relationship(
ShareNetwork, backref="share_networks_metadata",
foreign_keys=share_network_id,
primaryjoin='and_('
'ShareNetworkMetadata.share_network_id == ShareNetwork.id,'
'ShareNetworkMetadata.deleted == 0)')
class ShareNetworkSubnet(BASE, ManilaBase):
"""Represents a share network subnet used by some resources."""
@ -1015,8 +1120,29 @@ class ShareNetworkSubnet(BASE, ManilaBase):
return self.share_network['name']
class ShareNetworkSubnetMetadata(BASE, ManilaBase):
"""Represents a metadata key/value pair for a share network subnet."""
__tablename__ = 'share_network_subnets_metadata'
id = Column(Integer, primary_key=True)
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(Integer, default=0)
share_network_subnets_id = Column(String(36), ForeignKey(
'share_network_subnets.id'), nullable=False)
user_modifiable = Column(Boolean, default=True, nullable=False)
share_network_subnet = orm.relationship(
ShareNetworkSubnet, backref="share_network_subnets_metadata",
foreign_keys=share_network_subnets_id,
primaryjoin='and_('
'ShareNetworkSubnetMetadata.share_network_subnets_id == '
'ShareNetworkSubnet.id,'
'ShareNetworkSubnetMetadata.deleted == 0)')
class ShareServer(BASE, ManilaBase):
"""Represents share server used by share."""
__tablename__ = 'share_servers'
id = Column(String(36), primary_key=True, nullable=False)
deleted = Column(String(36), default='False')
@ -1195,6 +1321,25 @@ class ShareGroup(BASE, ManilaBase):
return self._availability_zone['name']
class ShareGroupsMetadata(BASE, ManilaBase):
"""Represents a metadata key/value pair for a share group."""
__tablename__ = 'share_groups_metadata'
id = Column(Integer, primary_key=True)
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(Integer, default=0)
share_group_id = Column(String(36), ForeignKey(
'share_groups.id'), nullable=False)
user_modifiable = Column(Boolean, default=True, nullable=False)
share_group = orm.relationship(
ShareGroup, backref="share_groups_metadata",
foreign_keys=share_group_id,
primaryjoin='and_('
'ShareGroupsMetadata.share_group_id == ShareGroup.id,'
'ShareGroupsMetadata.deleted == 0)')
class ShareGroupTypeProjects(BASE, ManilaBase):
"""Represent projects associated share group types."""
__tablename__ = "share_group_type_projects"
@ -1256,6 +1401,26 @@ class ShareGroupSnapshot(BASE, ManilaBase):
)
class ShareGroupSnapshotsMetadata(BASE, ManilaBase):
"""Represents a metadata key/value pair for a share group snapshot."""
__tablename__ = 'share_group_snapshots_metadata'
id = Column(Integer, primary_key=True)
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(Integer, default=0)
share_group_snapshot_id = Column(String(36), ForeignKey(
'share_group_snapshots.id'), nullable=False)
user_modifiable = Column(Boolean, default=True, nullable=False)
share_group_snapshot = orm.relationship(
ShareGroupSnapshot, backref="share_groups_metadata",
foreign_keys=share_group_snapshot_id,
primaryjoin='and_('
'ShareGroupSnapshotsMetadata.share_group_snapshot_id == '
'ShareGroupSnapshot.id,'
'ShareGroupSnapshotsMetadata.deleted == 0)')
class ShareGroupTypeShareTypeMapping(BASE, ManilaBase):
"""Represents the share types supported by a share group type."""
__tablename__ = 'share_group_type_share_type_mappings'

View File

@ -554,6 +554,10 @@ class ShareSnapshotInstanceNotFound(NotFound):
message = _("Snapshot instance %(instance_id)s could not be found.")
class ShareSnapshotMetadataNotFound(NotFound):
message = _("Metadata could not be found.")
class ShareSnapshotNotSupported(ManilaException):
message = _("Share %(share_name)s does not support snapshots.")

View File

@ -0,0 +1,114 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from unittest import mock
import ddt
import webob
from manila.api.v2 import share_snapshot_metadata
from manila.api.v2 import share_snapshots
from manila import context
from manila import policy
from manila import test
from manila.tests.api import fakes
from manila.tests import db_utils
from oslo_utils import uuidutils
@ddt.ddt
class ShareSnapshotMetadataAPITest(test.TestCase):
def _get_request(self, version="2.45", use_admin_context=True):
req = fakes.HTTPRequest.blank(
'/v2/share-access-rules',
version=version, use_admin_context=use_admin_context)
return req
def setUp(self):
super(ShareSnapshotMetadataAPITest, self).setUp()
self.controller = (
share_snapshot_metadata.ShareSnapshotMetadataController())
self.snapshot_controller = (
share_snapshots.ShareSnapshotsController())
self.resource_name = self.controller.resource_name
self.admin_context = context.RequestContext('admin', 'fake', True)
self.member_context = context.RequestContext('fake', 'fake')
self.mock_policy_check = self.mock_object(
policy, 'check_policy', mock.Mock(return_value=True))
self.share = db_utils.create_share()
self.snapshot = db_utils.create_snapshot(
id=uuidutils.generate_uuid(),
share_id=self.share['id'])
@ddt.data({'body': {'metadata': {'key1': 'v1'}}},
{'body': {'metadata': {'test_key1': 'test_v1'}}},
{'body': {'metadata': {'key1': 'v2'}}})
@ddt.unpack
def test_update_metadata(self, body):
url = self._get_request()
update = self.controller.update(url, self.snapshot['id'], body=body)
self.assertEqual(body, update)
show_result = self.snapshot_controller.show(url, self.snapshot['id'])
self.assertEqual(1, len(show_result))
self.assertIn(self.snapshot['id'], show_result['snapshot']['id'])
self.assertEqual(body['metadata'], show_result['snapshot']['metadata'])
def test_delete_metadata(self):
body = {'metadata': {'test_key3': 'test_v3'}}
url = self._get_request()
self.controller.update(url, self.snapshot['id'], body=body)
self.controller.delete(url, self.snapshot['id'], 'test_key3')
show_result = self.snapshot_controller.show(url, self.snapshot['id'])
self.assertEqual(1, len(show_result))
self.assertIn(self.snapshot['id'], show_result['snapshot']['id'])
self.assertNotIn('test_key3', show_result['snapshot']['metadata'])
def test_update_snapshot_metadata_with_snapshot_id_not_found(self):
self.assertRaises(
webob.exc.HTTPNotFound,
self.controller.update,
self._get_request(), 'not_exist_snapshot_id',
{'metadata': {'key1': 'v1'}})
def test_update_snapshot_metadata_with_body_error(self):
self.assertRaises(
webob.exc.HTTPBadRequest,
self.controller.update,
self._get_request(), self.snapshot['id'],
{'metadata_error': {'key1': 'v1'}})
@ddt.data({'metadata': {'key1': 'v1', 'key2': None}},
{'metadata': {None: 'v1', 'key2': 'v2'}},
{'metadata': {'k' * 256: 'v2'}},
{'metadata': {'key1': 'v' * 1024}})
@ddt.unpack
def test_update_metadata_with_invalid_metadata(self, metadata):
self.assertRaises(
webob.exc.HTTPBadRequest,
self.controller.update,
self._get_request(), self.snapshot['id'],
{'metadata': metadata})
def test_delete_snapshot_metadata_not_found(self):
body = {'metadata': {'test_key_exist': 'test_v_exist'}}
update = self.controller.update(
self._get_request(), self.snapshot['id'], body=body)
self.assertEqual(body, update)
self.assertRaises(
webob.exc.HTTPNotFound,
self.controller.delete,
self._get_request(), self.snapshot['id'], 'key1')

View File

@ -2841,6 +2841,69 @@ class ShareAPITestCase(test.TestCase):
self.assertEqual(should_be,
db_api.share_metadata_get(self.context, share_id))
def test_share_snapshot_metadata_get(self):
metadata = {'a': 'b', 'c': 'd'}
self.share_1 = db_utils.create_share(
id='fake_share_id_1')
self.snapshot_1 = db_utils.create_snapshot(
id='fake_snapshot_id_1', share_id=self.share_1['id'])
self.assertEqual(
metadata, db_api.share_snapshot_metadata_get(
self.context, share_snapshot_id=self.snapshot_1['id']))
def test_share_snapshot_metadata_get_item(self):
metadata = {'a': 'b', 'c': 'd'}
key = {'a'}
shouldbe = {'a': 'b'}
self.share_1 = db_utils.create_share(
id='fake_share_id_1')
self.snapshot_1 = db_utils.create_snapshot(
id='fake_snapshot_id_1', share_id=self.share_1['id'])
db_api.share_snapshot_metadata_update(
self.context, share_snapshot_id=self.snapshot_1['id'],
metadata=metadata)
self.assertEqual(
shouldbe, db_api.share_snapshot_metadata_get_item(
self.context, share_snapshot_id=self.snapshot_1['id'],
key=key))
def test_share_snapshot_metadata_update(self):
metadata1 = {'a': '1', 'c': '2'}
metadata2 = {'a': '3', 'd': '5'}
should_be = {'a': '3', 'c': '2', 'd': '5'}
self.share_1 = db_utils.create_share(
id='fake_share_id_1')
self.snapshot_1 = db_utils.create_snapshot(
id='fake_snapshot_id_1', share_id=self.share_1['id'])
db_api.share_snapshot_metadata_update(
self.context, share_snapshot_id=self.snapshot_1['id'],
metadata=metadata1)
db_api.share_snapshot_metadata_update(
self.context, share_snapshot_id=self.snapshot_1['id'],
metadata=metadata2)
self.assertEqual(
should_be, db_api.share_metadata_get(
self.context, share_snapshot_id=self.snapshot_1['id']))
def test_share_snapshot_metadata_delete(self):
key = {'a'}
metadata = {'a': '1', 'c': '2'}
should_be = {'c': '2'}
self.share_1 = db_utils.create_share(
id='fake_share_id_1')
self.snapshot_1 = db_utils.create_snapshot(
id='fake_snapshot_id_1', share_id=self.share_1['id'])
db_api.share_snapshot_metadata_update(
self.context, share_snapshot_id=self.snapshot_1['id'],
metadata=metadata)
db_api.share_metadata_delete(
self.context, share_snapshot_id=self.snapshot_1['id'],
key=key)
self.assertEqual(
should_be, db_api.share_metadata_get(
self.context, share_snapshot_id=self.snapshot_1['id']))
def test_extend_invalid_status(self):
invalid_status = 'fake'
share = db_utils.create_share(status=invalid_status)