Merge "Metadata for Share Network Subnet Resource"

This commit is contained in:
Zuul 2023-02-21 01:45:59 +00:00 committed by Gerrit Code Review
commit cf86b23896
23 changed files with 785 additions and 35 deletions

View File

@ -614,7 +614,7 @@ def validate_subnet_create(context, share_network_id, data,
return share_network, existing_subnets
def _check_metadata_properties(metadata=None):
def check_metadata_properties(metadata=None):
if not metadata:
metadata = {}

View File

@ -194,6 +194,7 @@ REST_API_VERSION_HISTORY = """
promote API.
* 2.76 - Added 'default_ad_site' field in security service object.
* 2.77 - Added support for share transfer between different projects.
* 2.78 - Added Share Network Subnet Metadata to Metadata API.
"""
@ -201,7 +202,7 @@ REST_API_VERSION_HISTORY = """
# 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.77"
_MAX_API_VERSION = "2.78"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -422,3 +422,8 @@ ____
2.77
----
Added support for share transfer between different projects.
2.78
----
Added Metadata API methods (GET, PUT, POST, DELETE)
to Share Network Subnets.

View File

@ -134,7 +134,7 @@ class ShareMetadataController(object):
_metadata = orig_meta.copy()
_metadata.update(metadata_copy)
api_common._check_metadata_properties(_metadata)
api_common.check_metadata_properties(_metadata)
db.share_metadata_update(context, share['id'],
_metadata, delete)

View File

@ -27,36 +27,43 @@ class MetadataController(object):
resource_get = {
"share": "share_get",
"share_snapshot": "share_snapshot_get",
"share_network_subnet": "share_network_subnet_get",
}
resource_metadata_get = {
"share": "share_metadata_get",
"share_snapshot": "share_snapshot_metadata_get",
"share_network_subnet": "share_network_subnet_metadata_get",
}
resource_metadata_get_item = {
"share": "share_metadata_get_item",
"share_snapshot": "share_snapshot_metadata_get_item",
"share_network_subnet": "share_network_subnet_metadata_get_item",
}
resource_metadata_update = {
"share": "share_metadata_update",
"share_snapshot": "share_snapshot_metadata_update",
"share_network_subnet": "share_network_subnet_metadata_update",
}
resource_metadata_update_item = {
"share": "share_metadata_update_item",
"share_snapshot": "share_snapshot_metadata_update_item",
"share_network_subnet": "share_network_subnet_metadata_update_item",
}
resource_metadata_delete = {
"share": "share_metadata_delete",
"share_snapshot": "share_snapshot_metadata_delete",
"share_network_subnet": "share_network_subnet_metadata_delete",
}
resource_policy_get = {
'share': 'get',
'share_snapshot': 'get_snapshot',
'share_network_subnet': 'show',
}
def __init__(self):
@ -65,7 +72,7 @@ class MetadataController(object):
def _get_resource(self, context, resource_id,
for_modification=False, parent_id=None):
if self.resource_name in ['share']:
if self.resource_name in ['share', 'share_network_subnet']:
# we would allow retrieving some "public" resources
# across project namespaces excpet share snaphots,
# project_only=True is hard coded
@ -114,7 +121,7 @@ class MetadataController(object):
context = req.environ['manila.context']
try:
metadata = body['metadata']
common._check_metadata_properties(metadata)
common.check_metadata_properties(metadata)
except (KeyError, TypeError):
msg = _("Malformed request body")
raise exc.HTTPBadRequest(explanation=msg)
@ -140,7 +147,7 @@ class MetadataController(object):
context = req.environ['manila.context']
try:
meta_item = body['metadata']
common._check_metadata_properties(meta_item)
common.check_metadata_properties(meta_item)
except (TypeError, KeyError):
expl = _('Malformed request body')
raise exc.HTTPBadRequest(explanation=expl)
@ -171,7 +178,7 @@ class MetadataController(object):
context = req.environ['manila.context']
try:
metadata = body['metadata']
common._check_metadata_properties(metadata)
common.check_metadata_properties(metadata)
except (TypeError, KeyError):
expl = _('Malformed request body')
raise exc.HTTPBadRequest(explanation=expl)

View File

@ -403,6 +403,48 @@ class APIRouter(manila.api.openstack.APIRouter):
action="index",
conditions={"method": ["GET"]})
for path_prefix in ['/{project_id}', '']:
# project_id is optional
mapper.connect("subnets_metadata",
"%s/share-networks/{share_network_id}"
"/subnets/{resource_id}/metadata" % path_prefix,
controller=self.resources["share_network_subnets"],
action="create_metadata",
conditions={"method": ["POST"]})
mapper.connect("subnets_metadata",
"%s/share-networks/{share_network_id}"
"/subnets/{resource_id}/metadata" % path_prefix,
controller=self.resources["share_network_subnets"],
action="update_all_metadata",
conditions={"method": ["PUT"]})
mapper.connect("subnets_metadata",
"%s/share-networks/{share_network_id}"
"/subnets/{resource_id}"
"/metadata/{key}" % path_prefix,
controller=self.resources["share_network_subnets"],
action="update_metadata_item",
conditions={"method": ["POST"]})
mapper.connect("subnets_metadata",
"%s/share-networks/{share_network_id}"
"/subnets/{resource_id}/metadata" % path_prefix,
controller=self.resources["share_network_subnets"],
action="index_metadata",
conditions={"method": ["GET"]})
mapper.connect("subnets_metadata",
"%s/share-networks/{share_network_id}"
"/subnets/{resource_id}"
"/metadata/{key}" % path_prefix,
controller=self.resources["share_network_subnets"],
action="show_metadata",
conditions={"method": ["GET"]})
mapper.connect("subnets_metadata",
"%s/share-networks/{share_network_id}"
"/subnets/{resource_id}"
"/metadata/{key}" % path_prefix,
controller=self.resources["share_network_subnets"],
action="delete_metadata",
conditions={"method": ["DELETE"]})
self.resources["share_servers"] = share_servers.create_resource()
mapper.resource("share_server",
"share-servers",

View File

@ -21,8 +21,10 @@ from oslo_log import log
import webob
from webob import exc
from manila.api import common as api_common
from manila.api.openstack import api_version_request as api_version
from manila.api.openstack import wsgi
from manila.api.v2 import metadata as metadata_controller
from manila.api.views import share_network_subnets as subnet_views
from manila.db import api as db_api
from manila import exception
@ -33,7 +35,8 @@ from manila.share import rpcapi as share_rpcapi
LOG = log.getLogger(__name__)
class ShareNetworkSubnetController(wsgi.Controller):
class ShareNetworkSubnetController(wsgi.Controller,
metadata_controller.MetadataController):
"""The Share Network Subnet API controller for the OpenStack API."""
resource_name = 'share_network_subnet'
@ -116,6 +119,12 @@ class ShareNetworkSubnetController(wsgi.Controller):
msg = _("Share Network Subnet is missing from the request body.")
raise exc.HTTPBadRequest(explanation=msg)
data = body['share-network-subnet']
if req.api_version_request >= api_version.APIVersionRequest("2.78"):
api_common.check_metadata_properties(data.get('metadata'))
else:
data.pop('metadata', None)
data['share_network_id'] = share_network_id
multiple_subnet_support = (req.api_version_request >=
api_version.APIVersionRequest("2.70"))
@ -181,6 +190,49 @@ class ShareNetworkSubnetController(wsgi.Controller):
return self._view_builder.build_share_network_subnet(
req, share_network_subnet)
@wsgi.Controller.api_version("2.78")
@wsgi.Controller.authorize("get_metadata")
def index_metadata(self, req, share_network_id, resource_id):
"""Returns the list of metadata for a given share network subnet."""
return self._index_metadata(req, resource_id,
parent_id=share_network_id)
@wsgi.Controller.api_version("2.78")
@wsgi.Controller.authorize("update_metadata")
def create_metadata(self, req, share_network_id, resource_id, body):
"""Create metadata for a given share network subnet."""
return self._create_metadata(req, resource_id, body,
parent_id=share_network_id)
@wsgi.Controller.api_version("2.78")
@wsgi.Controller.authorize("update_metadata")
def update_all_metadata(self, req, share_network_id, resource_id, body):
"""Update entire metadata for a given share network subnet."""
return self._update_all_metadata(req, resource_id, body,
parent_id=share_network_id)
@wsgi.Controller.api_version("2.78")
@wsgi.Controller.authorize("update_metadata")
def update_metadata_item(self, req, share_network_id, resource_id, body,
key):
"""Update metadata item for a given share network subnet."""
return self._update_metadata_item(req, resource_id, body, key,
parent_id=share_network_id)
@wsgi.Controller.api_version("2.78")
@wsgi.Controller.authorize("get_metadata")
def show_metadata(self, req, share_network_id, resource_id, key):
"""Show metadata for a given share network subnet."""
return self._show_metadata(req, resource_id, key,
parent_id=share_network_id)
@wsgi.Controller.api_version("2.78")
@wsgi.Controller.authorize("delete_metadata")
def delete_metadata(self, req, share_network_id, resource_id, key):
"""Delete metadata for a given share network subnet."""
return self._delete_metadata(req, resource_id, key,
parent_id=share_network_id)
def create_resource():
return wsgi.Resource(ShareNetworkSubnetController())

View File

@ -20,6 +20,9 @@ class ViewBuilder(common.ViewBuilder):
"""Model a server API response as a python dictionary."""
_collection_name = 'share_network_subnets'
_detail_version_modifiers = [
"add_metadata"
]
def build_share_network_subnet(self, request, share_network_subnet):
return {
@ -51,3 +54,7 @@ class ViewBuilder(common.ViewBuilder):
}
self.update_versioned_resource_dict(request, sns, share_network_subnet)
return sns
@common.ViewBuilder.versioned_method("2.78")
def add_metadata(self, context, share_network_subnet_dict, sns):
share_network_subnet_dict['metadata'] = sns.get('subnet_metadata')

View File

@ -23,7 +23,8 @@ class ViewBuilder(common.ViewBuilder):
_detail_version_modifiers = ["add_gateway", "add_mtu", "add_nova_net_id",
"add_subnets",
"add_status_and_sec_service_update_fields",
"add_network_allocation_update_support_field"]
"add_network_allocation_update_support_field",
"add_subnet_with_metadata"]
def build_share_network(self, request, share_network):
"""View of a share network."""
@ -104,7 +105,7 @@ class ViewBuilder(common.ViewBuilder):
self.update_versioned_resource_dict(request, sn, share_network)
return sn
@common.ViewBuilder.versioned_method("2.51")
@common.ViewBuilder.versioned_method("2.51", "2.77")
def add_subnets(self, context, network_dict, network):
subnets = [{
'id': sns.get('id'),
@ -152,3 +153,28 @@ class ViewBuilder(common.ViewBuilder):
self, context, network_dict, network):
network_dict['network_allocation_update_support'] = network.get(
'network_allocation_update_support')
@common.ViewBuilder.versioned_method("2.78")
def add_subnet_with_metadata(self, context, network_dict, network):
subnets = [{
'id': sns.get('id'),
'availability_zone': sns.get('availability_zone'),
'created_at': sns.get('created_at'),
'updated_at': sns.get('updated_at'),
'segmentation_id': sns.get('segmentation_id'),
'neutron_net_id': sns.get('neutron_net_id'),
'neutron_subnet_id': sns.get('neutron_subnet_id'),
'ip_version': sns.get('ip_version'),
'cidr': sns.get('cidr'),
'network_type': sns.get('network_type'),
'mtu': sns.get('mtu'),
'gateway': sns.get('gateway'),
'metadata': sns.get('subnet_metadata'),
} for sns in network.get('share_network_subnets')]
network_dict['share_network_subnets'] = subnets
attr_to_remove = [
'neutron_net_id', 'neutron_subnet_id', 'network_type',
'segmentation_id', 'cidr', 'ip_version', 'gateway', 'mtu']
for attr in attr_to_remove:
network_dict.pop(attr)

View File

@ -1072,10 +1072,11 @@ def share_network_subnet_update(context, network_subnet_id, values):
return IMPL.share_network_subnet_update(context, network_subnet_id, values)
def share_network_subnet_get(context, network_subnet_id, session=None):
def share_network_subnet_get(context, network_subnet_id, session=None,
parent_id=None):
"""Get requested share network subnet DB record."""
return IMPL.share_network_subnet_get(context, network_subnet_id,
session=session)
session=session, parent_id=parent_id)
def share_network_subnet_get_all_with_same_az(context, network_subnet_id,
@ -1118,8 +1119,47 @@ def share_network_subnet_get_all_by_share_server_id(context, share_server_id):
return IMPL.share_network_subnet_get_all_by_share_server_id(
context, share_server_id)
####################
##################
def share_network_subnet_metadata_get(context, share_network_subnet_id,
**kwargs):
"""Get all metadata for a share network subnet."""
return IMPL.share_network_subnet_metadata_get(context,
share_network_subnet_id,
**kwargs)
def share_network_subnet_metadata_get_item(context, share_network_subnet_id,
key):
"""Get metadata item for a share network subnet."""
return IMPL.share_network_subnet_metadata_get_item(context,
share_network_subnet_id,
key)
def share_network_subnet_metadata_delete(context, share_network_subnet_id,
key):
"""Delete the given metadata item."""
IMPL.share_network_subnet_metadata_delete(context, share_network_subnet_id,
key)
def share_network_subnet_metadata_update(context, share_network_subnet_id,
metadata, delete):
"""Update metadata if it exists, otherwise create it."""
return IMPL.share_network_subnet_metadata_update(context,
share_network_subnet_id,
metadata, delete)
def share_network_subnet_metadata_update_item(context, share_network_subnet_id,
metadata):
"""Update metadata item if it exists, otherwise create it."""
return IMPL.share_network_subnet_metadata_update_item(
context, share_network_subnet_id, metadata)
###################
def network_allocation_create(context, values):

View File

@ -0,0 +1,67 @@
# 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 share network subnet metadata
Revision ID: ac0620cbe74d
Revises: 1e2d600bf972
Create Date: 2023-01-07 14:13:25.525968
"""
# revision identifiers, used by Alembic.
revision = 'ac0620cbe74d'
down_revision = '1e2d600bf972'
from alembic import op
from oslo_log import log
import sqlalchemy as sql
LOG = log.getLogger(__name__)
share_network_subnet_metadata_table_name = 'share_network_subnet_metadata'
def upgrade():
context = op.get_context()
mysql_dl = context.bind.dialect.name == 'mysql'
datetime_type = (sql.dialects.mysql.DATETIME(fsp=6)
if mysql_dl else sql.DateTime)
try:
op.create_table(
share_network_subnet_metadata_table_name,
sql.Column('deleted', sql.String(36), default='False'),
sql.Column('created_at', datetime_type),
sql.Column('updated_at', datetime_type),
sql.Column('deleted_at', datetime_type),
sql.Column('share_network_subnet_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),
mysql_engine='InnoDB',
mysql_charset='utf8'
)
except Exception:
LOG.error("Table |%s| not created!",
share_network_subnet_metadata_table_name)
raise
def downgrade():
try:
op.drop_table(share_network_subnet_metadata_table_name)
except Exception:
LOG.error("Table |%s| not dropped!",
share_network_subnet_metadata_table_name)
raise

View File

@ -201,6 +201,20 @@ def require_share_snapshot_exists(f):
return wrapper
def require_share_network_subnet_exists(f):
"""Decorator to require the specified share network subnet to exist.
Requires the wrapped function to use context and share_network_subnet_id
as their first two arguments.
"""
@wraps(f)
def wrapper(context, share_network_subnet_id, *args, **kwargs):
share_network_subnet_get(context, share_network_subnet_id)
return f(context, share_network_subnet_id, *args, **kwargs)
wrapper.__name__ = f.__name__
return wrapper
def require_share_instance_exists(f):
"""Decorator to require the specified share instance to exist.
@ -4611,12 +4625,16 @@ def _network_subnet_get_query(context, session=None):
if session is None:
session = get_session()
return (model_query(context, models.ShareNetworkSubnet, session=session).
options(joinedload('share_servers'), joinedload('share_network')))
options(joinedload('share_servers'),
joinedload('share_network'),
joinedload('share_network_subnet_metadata')))
@require_context
def share_network_subnet_create(context, values):
values = ensure_model_dict_has_id(values)
values['share_network_subnet_metadata'] = _metadata_refs(
values.pop('metadata', {}), models.ShareNetworkSubnetMetadata)
network_subnet_ref = models.ShareNetworkSubnet()
network_subnet_ref.update(values)
@ -4635,6 +4653,8 @@ def share_network_subnet_delete(context, network_subnet_id):
network_subnet_ref = share_network_subnet_get(context,
network_subnet_id,
session=session)
session.query(models.ShareNetworkSubnetMetadata).filter_by(
share_network_subnet_id=network_subnet_id).soft_delete()
network_subnet_ref.soft_delete(session=session, update_status=True)
@ -4652,9 +4672,13 @@ def share_network_subnet_update(context, network_subnet_id, values):
@require_context
def share_network_subnet_get(context, network_subnet_id, session=None):
def share_network_subnet_get(context, network_subnet_id, session=None,
parent_id=None):
kwargs = {'id': network_subnet_id}
if parent_id:
kwargs['share_network_id'] = parent_id
result = (_network_subnet_get_query(context, session)
.filter_by(id=network_subnet_id)
.filter_by(**kwargs)
.first())
if result is None:
raise exception.ShareNetworkSubnetNotFound(
@ -4740,10 +4764,126 @@ def share_network_subnet_get_all_by_share_server_id(context, share_server_id):
return result
###################
@require_context
@require_share_network_subnet_exists
def share_network_subnet_metadata_get(context, share_network_subnet_id):
session = get_session()
return _share_network_subnet_metadata_get(context, share_network_subnet_id,
session=session)
@require_context
@require_share_network_subnet_exists
def share_network_subnet_metadata_delete(context, share_network_subnet_id,
key):
session = get_session()
meta_ref = _share_network_subnet_metadata_get_item(
context, share_network_subnet_id, key, session=session)
meta_ref.soft_delete(session=session)
@require_context
@require_share_network_subnet_exists
def share_network_subnet_metadata_update(context, share_network_subnet_id,
metadata, delete):
session = get_session()
return _share_network_subnet_metadata_update(
context, share_network_subnet_id, metadata, delete, session=session)
def share_network_subnet_metadata_update_item(context, share_network_subnet_id,
item):
session = get_session()
return _share_network_subnet_metadata_update(
context, share_network_subnet_id, item, delete=False, session=session)
def share_network_subnet_metadata_get_item(context, share_network_subnet_id,
key):
session = get_session()
row = _share_network_subnet_metadata_get_item(
context, share_network_subnet_id, key, session=session)
result = {row['key']: row['value']}
return result
def _share_network_subnet_metadata_get_query(context, share_network_subnet_id,
session=None):
session = session or get_session()
return (model_query(context, models.ShareNetworkSubnetMetadata,
session=session,
read_deleted="no").
filter_by(share_network_subnet_id=share_network_subnet_id).
options(joinedload('share_network_subnet')))
def _share_network_subnet_metadata_get(context, share_network_subnet_id,
session=None):
session = session or get_session()
rows = _share_network_subnet_metadata_get_query(
context, share_network_subnet_id, session=session).all()
result = {}
for row in rows:
result[row['key']] = row['value']
return result
def _share_network_subnet_metadata_get_item(context, share_network_subnet_id,
key, session=None):
session = session or get_session()
result = (_share_network_subnet_metadata_get_query(
context, share_network_subnet_id, session=session).
filter_by(key=key).first())
if not result:
raise exception.MetadataItemNotFound
return result
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
def _share_network_subnet_metadata_update(context, share_network_subnet_id,
metadata, delete, session=None):
session = session or get_session()
delete = strutils.bool_from_string(delete)
with session.begin():
if delete:
original_metadata = _share_network_subnet_metadata_get(
context, share_network_subnet_id, session=session)
for meta_key, meta_value in original_metadata.items():
if meta_key not in metadata:
meta_ref = _share_network_subnet_metadata_get_item(
context, share_network_subnet_id, meta_key,
session=session)
meta_ref.soft_delete(session=session)
meta_ref = None
# 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_network_subnet_metadata_get_query(
context, share_network_subnet_id,
session=session).filter_by(
key=meta_key).first()
if not meta_ref:
meta_ref = models.ShareNetworkSubnetMetadata()
item.update(
{"key": meta_key,
"share_network_subnet_id": share_network_subnet_id})
meta_ref.update(item)
meta_ref.save(session=session)
return metadata
#################################
def _server_get_query(context, session=None):
if session is None:
session = get_session()

View File

@ -1001,7 +1001,7 @@ class ShareNetwork(BASE, ManilaBase):
class ShareNetworkSubnet(BASE, ManilaBase):
"""Represents a share network subnet used by some resources."""
_extra_keys = ['availability_zone']
_extra_keys = ['availability_zone', 'subnet_metadata']
__tablename__ = 'share_network_subnets'
id = Column(String(36), primary_key=True, nullable=False)
@ -1056,6 +1056,35 @@ class ShareNetworkSubnet(BASE, ManilaBase):
def share_network_name(self):
return self.share_network['name']
@property
def subnet_metadata(self):
metadata_dict = {}
metadata_list = (
self.share_network_subnet_metadata) # pylint: disable=no-member
for meta in metadata_list:
metadata_dict[meta['key']] = meta['value']
return metadata_dict
class ShareNetworkSubnetMetadata(BASE, ManilaBase):
"""Represents a metadata key/value pair for a subnet."""
__tablename__ = 'share_network_subnet_metadata'
id = Column(Integer, primary_key=True)
key = Column(String(255), nullable=False)
value = Column(String(1023), nullable=False)
deleted = Column(String(36), default='False')
share_network_subnet_id = Column(String(36), ForeignKey(
'share_network_subnets.id'), nullable=False)
share_network_subnet = orm.relationship(
ShareNetworkSubnet,
backref=orm.backref('share_network_subnet_metadata', lazy='immediate'),
foreign_keys=share_network_subnet_id,
primaryjoin='and_('
'ShareNetworkSubnetMetadata.share_network_subnet_id == '
'ShareNetworkSubnet.id,'
'ShareNetworkSubnetMetadata.deleted == "False")')
class ShareServer(BASE, ManilaBase):
"""Represents share server used by share."""

View File

@ -48,6 +48,24 @@ deprecated_subnet_index = policy.DeprecatedRule(
deprecated_reason=DEPRECATED_REASON,
deprecated_since=versionutils.deprecated.WALLABY
)
deprecated_update_subnet_metadata = policy.DeprecatedRule(
name=BASE_POLICY_NAME % 'update_metadata',
check_str=base.RULE_DEFAULT,
deprecated_reason=DEPRECATED_REASON,
deprecated_since='ANTELOPE'
)
deprecated_delete_subnet_metadata = policy.DeprecatedRule(
name=BASE_POLICY_NAME % 'delete_metadata',
check_str=base.RULE_DEFAULT,
deprecated_reason=DEPRECATED_REASON,
deprecated_since='ANTELOPE'
)
deprecated_get_subnet_metadata = policy.DeprecatedRule(
name=BASE_POLICY_NAME % 'get_metadata',
check_str=base.RULE_DEFAULT,
deprecated_reason=DEPRECATED_REASON,
deprecated_since='ANTELOPE'
)
share_network_subnet_policies = [
@ -105,6 +123,63 @@ share_network_subnet_policies = [
],
deprecated_rule=deprecated_subnet_index
),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'update_metadata',
check_str=base.ADMIN_OR_PROJECT_MEMBER,
scope_types=['system', 'project'],
description="Update share network subnet metadata.",
operations=[
{
'method': 'PUT',
'path': '/share-networks/{share_network_id}/subnets/'
'{share_network_subnet_id}/metadata',
},
{
'method': 'POST',
'path': '/share-networks/{share_network_id}/subnets/'
'{share_network_subnet_id}/metadata/{key}',
},
{
'method': 'POST',
'path': '/share-networks/{share_network_id}/subnets/'
'{share_network_subnet_id}/metadata',
},
],
deprecated_rule=deprecated_update_subnet_metadata
),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'delete_metadata',
check_str=base.ADMIN_OR_PROJECT_MEMBER,
scope_types=['system', 'project'],
description="Delete share network subnet metadata.",
operations=[
{
'method': 'DELETE',
'path': '/share-networks/{share_network_id}/subnets/'
'{share_network_subnet_id}/metadata/{key}',
}
],
deprecated_rule=deprecated_delete_subnet_metadata
),
policy.DocumentedRuleDefault(
name=BASE_POLICY_NAME % 'get_metadata',
check_str=base.ADMIN_OR_PROJECT_READER,
scope_types=['system', 'project'],
description="Get share network subnet metadata.",
operations=[
{
'method': 'GET',
'path': '/share-networks/{share_network_id}/subnets/'
'{share_network_subnet_id}/metadata',
},
{
'method': 'GET',
'path': '/share-networks/{share_network_id}/subnets/'
'{share_network_subnet_id}/metadata/{key}',
}
],
deprecated_rule=deprecated_get_subnet_metadata
),
]

View File

@ -223,7 +223,7 @@ class API(base.Base):
az_request_multiple_subnet_support_map=None):
"""Create new share."""
api_common._check_metadata_properties(metadata)
api_common.check_metadata_properties(metadata)
if snapshot_id is not None:
snapshot = self.get_snapshot(context, snapshot_id)
@ -1449,7 +1449,7 @@ class API(base.Base):
force=False, metadata=None):
policy.check_policy(context, 'share', 'create_snapshot', share)
if metadata:
api_common._check_metadata_properties(metadata)
api_common.check_metadata_properties(metadata)
if ((not force) and (share['status'] != constants.STATUS_AVAILABLE)):
msg = _("Source share status must be "
@ -2231,7 +2231,7 @@ class API(base.Base):
msg = _("Invalid share access level: %s.") % access_level
raise exception.InvalidShareAccess(reason=msg)
api_common._check_metadata_properties(metadata)
api_common.check_metadata_properties(metadata)
access_exists = self.db.share_access_check_for_existing_access(
ctx, share['id'], access_type, access_to)
@ -2413,7 +2413,7 @@ class API(base.Base):
def update_share_access_metadata(self, context, access_id, metadata):
"""Updates share access metadata."""
try:
api_common._check_metadata_properties(metadata)
api_common.check_metadata_properties(metadata)
except exception.InvalidMetadata:
raise exception.InvalidMetadata()
except exception.InvalidMetadataSize:

View File

@ -4209,6 +4209,7 @@ class ShareManager(manager.SchedulerDependentManager):
'admin_network_allocations': admin_network_allocations,
'backend_details': share_server.get('backend_details'),
'network_type': share_network_subnet['network_type'],
'subnet_metadata': share_network_subnet['subnet_metadata']
})
return network_info

View File

@ -62,8 +62,10 @@ class ShareNetworkSubnetControllerTest(test.TestCase):
mock.Mock(return_value=fake_az))
self.share_network = db_utils.create_share_network(
name='fake_network', id='fake_sn_id')
self.subnet_metadata = {'fake_key': 'fake_value'}
self.subnet = db_utils.create_share_network_subnet(
share_network_id=self.share_network['id'])
share_network_id=self.share_network['id'],
metadata=self.subnet_metadata)
self.share_server = db_utils.create_share_server(
share_network_subnets=[self.subnet])
self.share = db_utils.create_share()
@ -212,27 +214,35 @@ class ShareNetworkSubnetControllerTest(test.TestCase):
self.mock_policy_check.assert_called_once_with(
context, self.resource_name, 'delete')
def _setup_create_test_request_body(self):
def _setup_create_test_request_body(self, metadata=False):
body = {
'share_network_id': self.share_network['id'],
'availability_zone': fake_az['name'],
'neutron_net_id': 'fake_nn_id',
'neutron_subnet_id': 'fake_nsn_id'
}
if metadata:
body['metadata'] = self.subnet_metadata
return body
@ddt.data({'version': "2.51", 'has_share_servers': False},
{'version': "2.70", 'has_share_servers': False},
{'version': "2.70", 'has_share_servers': True})
{'version': "2.70", 'has_share_servers': True},
{'version': "2.78", 'has_share_servers': False})
@ddt.unpack
def test_subnet_create(self, version, has_share_servers):
req = fakes.HTTPRequest.blank('/subnets', version=version)
multiple_subnet_support = (req.api_version_request >=
api_version.APIVersionRequest("2.70"))
metadata_support = (req.api_version_request >=
api_version.APIVersionRequest("2.78"))
context = req.environ['manila.context']
body = {
'share-network-subnet': self._setup_create_test_request_body()
'share-network-subnet': self._setup_create_test_request_body(
metadata=metadata_support)
}
sn_id = body['share-network-subnet']['share_network_id']
expected_subnet = copy.deepcopy(self.subnet)
if has_share_servers:
@ -251,6 +261,8 @@ class ShareNetworkSubnetControllerTest(test.TestCase):
mock_share_network_subnet_get = self.mock_object(
db_api, 'share_network_subnet_get',
mock.Mock(return_value=expected_subnet))
mock_check_metadata_properties = self.mock_object(
common, 'check_metadata_properties')
fake_data = body['share-network-subnet']
fake_data['share_network_id'] = self.share_network['id']
@ -273,6 +285,8 @@ class ShareNetworkSubnetControllerTest(test.TestCase):
'mtu': expected_subnet.get('mtu'),
'gateway': expected_subnet.get('gateway')
}
if metadata_support:
view_subnet['metadata'] = self.subnet_metadata
self.assertEqual(view_subnet, res['share_network_subnet'])
mock_share_network_subnet_get.assert_called_once_with(
context, expected_subnet['id'])
@ -285,6 +299,8 @@ class ShareNetworkSubnetControllerTest(test.TestCase):
else:
mock_subnet_create.assert_called_once_with(
context, fake_data)
self.assertEqual(metadata_support,
mock_check_metadata_properties.called)
@ddt.data({'exception1': exception.ServiceIsDown(service='fake_srv'),
'exc_raise': exc.HTTPInternalServerError},
@ -475,3 +491,87 @@ class ShareNetworkSubnetControllerTest(test.TestCase):
req,
self.share_network['id'])
mock_sn_get.assert_called_once_with(context, self.share_network['id'])
def test_index_metadata(self):
req = fakes.HTTPRequest.blank('/subnets/', version="2.78")
mock_index = self.mock_object(
self.controller, '_index_metadata',
mock.Mock(return_value='fake_metadata'))
result = self.controller.index_metadata(req, self.share_network['id'],
self.subnet['id'])
self.assertEqual('fake_metadata', result)
mock_index.assert_called_once_with(req, self.subnet['id'],
parent_id=self.share_network['id'])
def test_create_metadata(self):
req = fakes.HTTPRequest.blank('/subnets/', version="2.78")
mock_index = self.mock_object(
self.controller, '_create_metadata',
mock.Mock(return_value='fake_metadata'))
body = 'fake_metadata_body'
result = self.controller.create_metadata(req, self.share_network['id'],
self.subnet['id'], body)
self.assertEqual('fake_metadata', result)
mock_index.assert_called_once_with(req, self.subnet['id'], body,
parent_id=self.share_network['id'])
def test_update_all_metadata(self):
req = fakes.HTTPRequest.blank('/subnets/', version="2.78")
mock_index = self.mock_object(
self.controller, '_update_all_metadata',
mock.Mock(return_value='fake_metadata'))
body = 'fake_metadata_body'
result = self.controller.update_all_metadata(
req, self.share_network['id'], self.subnet['id'], body)
self.assertEqual('fake_metadata', result)
mock_index.assert_called_once_with(req, self.subnet['id'], body,
parent_id=self.share_network['id'])
def test_update_metadata_item(self):
req = fakes.HTTPRequest.blank('/subnets/', version="2.78")
mock_index = self.mock_object(
self.controller, '_update_metadata_item',
mock.Mock(return_value='fake_metadata'))
body = 'fake_metadata_body'
key = 'fake_key'
result = self.controller.update_metadata_item(
req, self.share_network['id'], self.subnet['id'], body, key)
self.assertEqual('fake_metadata', result)
mock_index.assert_called_once_with(req, self.subnet['id'], body, key,
parent_id=self.share_network['id'])
def test_show_metadata(self):
req = fakes.HTTPRequest.blank('/subnets/', version="2.78")
mock_index = self.mock_object(
self.controller, '_show_metadata',
mock.Mock(return_value='fake_metadata'))
key = 'fake_key'
result = self.controller.show_metadata(
req, self.share_network['id'], self.subnet['id'], key)
self.assertEqual('fake_metadata', result)
mock_index.assert_called_once_with(req, self.subnet['id'], key,
parent_id=self.share_network['id'])
def test_delete_metadata(self):
req = fakes.HTTPRequest.blank('/subnets/', version="2.78")
mock_index = self.mock_object(
self.controller, '_delete_metadata',
mock.Mock(return_value='fake_metadata'))
key = 'fake_key'
result = self.controller.delete_metadata(
req, self.share_network['id'], self.subnet['id'], key)
self.assertEqual('fake_metadata', result)
mock_index.assert_called_once_with(req, self.subnet['id'], key,
parent_id=self.share_network['id'])

View File

@ -16,6 +16,7 @@
import ddt
from manila.api.openstack import api_version_request as api_version
from manila.api.views import share_network_subnets
from manila import test
from manila.tests.api import fakes
@ -31,11 +32,13 @@ class ViewBuilderTestCase(test.TestCase):
self.share_network = db_utils.create_share_network(
name='fake_network', id='fake_sn_id')
def _validate_is_detail_return(self, result):
def _validate_is_detail_return(self, result, metadata_support=False):
expected_keys = ['id', 'created_at', 'updated_at', 'neutron_net_id',
'neutron_subnet_id', 'network_type', 'cidr',
'segmentation_id', 'ip_version', 'share_network_id',
'availability_zone', 'gateway', 'mtu']
if metadata_support:
expected_keys.append('metadata')
for key in expected_keys:
self.assertIn(key, result)
@ -58,13 +61,19 @@ class ViewBuilderTestCase(test.TestCase):
result['share_network_subnet']['availability_zone'])
self._validate_is_detail_return(result['share_network_subnet'])
def test_build_share_network_subnets(self):
req = fakes.HTTPRequest.blank('/subnets', version='2.51')
@ddt.data("2.51", "2.78")
def test_build_share_network_subnets(self, microversion):
metadata_support = (api_version.APIVersionRequest(microversion) >=
api_version.APIVersionRequest('2.78'))
req = fakes.HTTPRequest.blank('/subnets', version=microversion)
share_network = db_utils.create_share_network(
name='fake_network', id='fake_sn_id_1')
expected_metadata = {'fake_key': 'fake_value'}
subnet = db_utils.create_share_network_subnet(
share_network_id=share_network['id'])
share_network_id=share_network['id'], metadata=expected_metadata)
result = self.builder.build_share_network_subnets(req, [subnet])
@ -72,4 +81,7 @@ class ViewBuilderTestCase(test.TestCase):
self.assertEqual(1, len(result['share_network_subnets']))
subnet_list = result['share_network_subnets']
for subnet in subnet_list:
self._validate_is_detail_return(subnet)
self._validate_is_detail_return(subnet,
metadata_support=metadata_support)
if metadata_support:
self.assertEqual(expected_metadata, subnet['metadata'])

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
import ddt
import itertools
@ -145,6 +146,9 @@ class ViewBuilderTestCase(test.TestCase):
network_allocation_update_support = (
api_version.APIVersionRequest(microversion) >=
api_version.APIVersionRequest('2.69'))
subnet_metadata_support = (
api_version.APIVersionRequest(microversion) >=
api_version.APIVersionRequest('2.78'))
req = fakes.HTTPRequest.blank('/share-networks', version=microversion)
expected_networks_list = []
@ -158,8 +162,30 @@ class ViewBuilderTestCase(test.TestCase):
'description': share_network.get('description'),
}
if subnets_support:
share_network.update({'share_network_subnets': []})
expected_data.update({'share_network_subnets': []})
expected_subnet = {
'id': 'fake_subnet_id',
'availability_zone': 'fake_az',
'created_at': share_network.get('created_at'),
'updated_at': share_network.get('updated_at'),
'segmentation_id': share_network.get('segmentation_id'),
'neutron_net_id': share_network.get('neutron_net_id'),
'neutron_subnet_id': share_network.get(
'neutron_subnet_id'),
'ip_version': share_network.get('ip_version'),
'cidr': share_network.get('cidr'),
'network_type': share_network.get('network_type'),
'mtu': share_network.get('mtu'),
'gateway': share_network.get('gateway'),
}
subnet = expected_subnet
if subnet_metadata_support:
subnet = copy.deepcopy(expected_subnet)
expected_subnet['metadata'] = {'fake_key': 'fake_value'}
subnet['subnet_metadata'] = expected_subnet['metadata']
expected_data.update(
{'share_network_subnets': [expected_subnet]})
share_network.update({'share_network_subnets': [subnet]})
else:
if default_net_info_support:
network_data = {

View File

@ -3253,3 +3253,58 @@ class AddSnapshotMetadata(BaseMigrationChecks):
def check_downgrade(self, engine):
self.test_case.assertRaises(sa_exc.NoSuchTableError, utils.load_table,
self.new_table_name, engine)
@map_to_migration('ac0620cbe74d')
class AddSubnetMetadata(BaseMigrationChecks):
share_subnet_id = uuidutils.generate_uuid()
new_table_name = 'share_network_subnet_metadata'
def setup_upgrade_data(self, engine):
# Setup Share network.
share_network_data = {
'id': uuidutils.generate_uuid(),
'user_id': 'fake',
'project_id': 'fake'
}
network_table = utils.load_table('share_networks', engine)
engine.execute(network_table.insert(share_network_data))
# Setup share network subnet.
share_network_subnet_data = {
'id': self.share_subnet_id,
'share_network_id': share_network_data['id']
}
network_table = utils.load_table('share_network_subnets', engine)
engine.execute(network_table.insert(share_network_subnet_data))
def check_upgrade(self, engine, data):
data = {
'id': 1,
'key': 't' * 255,
'value': 'v' * 1023,
'share_network_subnet_id': self.share_subnet_id,
'deleted': 'False',
}
new_table = utils.load_table(self.new_table_name, engine)
engine.execute(new_table.insert(data))
item = engine.execute(
new_table.select().where(new_table.c.id == data['id'])).first()
self.test_case.assertTrue(hasattr(item, 'id'))
self.test_case.assertEqual(data['id'], item['id'])
self.test_case.assertTrue(hasattr(item, 'key'))
self.test_case.assertEqual(data['key'], item['key'])
self.test_case.assertTrue(hasattr(item, 'value'))
self.test_case.assertEqual(data['value'], item['value'])
self.test_case.assertTrue(hasattr(item, 'share_network_subnet_id'))
self.test_case.assertEqual(self.share_subnet_id,
item['share_network_subnet_id'])
self.test_case.assertTrue(hasattr(item, 'deleted'))
self.test_case.assertEqual('False', item['deleted'])
def check_downgrade(self, engine):
self.test_case.assertRaises(sa_exc.NoSuchTableError,
utils.load_table,
self.new_table_name, engine)

View File

@ -3149,6 +3149,64 @@ class ShareNetworkSubnetDatabaseAPITestCase(BaseDatabaseAPITestCase):
db_api.share_network_subnet_get_all_by_share_server_id,
self.fake_context, 'share_server_id')
def test_share_network_subnet_metadata_get(self):
metadata = {'a': 'b', 'c': 'd'}
subnet_1 = db_api.share_network_subnet_create(
self.fake_context, self.subnet_dict)
db_api.share_network_subnet_metadata_update(
self.fake_context, share_network_subnet_id=subnet_1['id'],
metadata=metadata, delete=False)
self.assertEqual(
metadata, db_api.share_network_subnet_metadata_get(
self.fake_context, share_network_subnet_id=subnet_1['id']))
def test_share_network_subnet_metadata_get_item(self):
metadata = {'a': 'b', 'c': 'd'}
key = 'a'
shouldbe = {'a': 'b'}
subnet_1 = db_api.share_network_subnet_create(
self.fake_context, self.subnet_dict)
db_api.share_network_subnet_metadata_update(
self.fake_context, share_network_subnet_id=subnet_1['id'],
metadata=metadata, delete=False)
self.assertEqual(
shouldbe, db_api.share_network_subnet_metadata_get_item(
self.fake_context, share_network_subnet_id=subnet_1['id'],
key=key))
def test_share_network_subnet_metadata_update(self):
metadata1 = {'a': '1', 'c': '2'}
metadata2 = {'a': '3', 'd': '5'}
should_be = {'a': '3', 'c': '2', 'd': '5'}
subnet_1 = db_api.share_network_subnet_create(
self.fake_context, self.subnet_dict)
db_api.share_network_subnet_metadata_update(
self.fake_context, share_network_subnet_id=subnet_1['id'],
metadata=metadata1, delete=False)
db_api.share_network_subnet_metadata_update(
self.fake_context, share_network_subnet_id=subnet_1['id'],
metadata=metadata2, delete=False)
self.assertEqual(
should_be, db_api.share_network_subnet_metadata_get(
self.fake_context, share_network_subnet_id=subnet_1['id']))
def test_share_network_subnet_metadata_delete(self):
key = 'a'
metadata = {'a': '1', 'c': '2'}
should_be = {'c': '2'}
subnet_1 = db_api.share_network_subnet_create(
self.fake_context, self.subnet_dict)
db_api.share_network_subnet_metadata_update(
self.fake_context, share_network_subnet_id=subnet_1['id'],
metadata=metadata, delete=False)
db_api.share_network_subnet_metadata_delete(
self.fake_context, share_network_subnet_id=subnet_1['id'],
key=key)
self.assertEqual(
should_be, db_api.share_network_subnet_metadata_get(
self.fake_context, share_network_subnet_id=subnet_1['id']))
@ddt.ddt
class SecurityServiceDatabaseAPITestCase(BaseDatabaseAPITestCase):

View File

@ -3861,7 +3861,8 @@ class ShareManagerTestCase(test.TestCase):
cidr='fake_cidr',
neutron_net_id='fake_neutron_net_id',
neutron_subnet_id='fake_neutron_subnet_id',
network_type='fake_network_type')
network_type='fake_network_type',
subnet_metadata={'fake_key': 'fake_value'})
expected = [dict(
server_id=fake_share_server['id'],
segmentation_id=fake_share_network_subnet['segmentation_id'],
@ -3874,7 +3875,8 @@ class ShareManagerTestCase(test.TestCase):
admin_network_allocations=(
fake_network_allocations_get_for_share_server(label='admin')),
backend_details=fake_share_server['backend_details'],
network_type=fake_share_network_subnet['network_type'])]
network_type=fake_share_network_subnet['network_type'],
subnet_metadata=fake_share_network_subnet['subnet_metadata'])]
network_info = self.share_manager._form_server_setup_info(
self.context, fake_share_server, fake_share_network,

View File

@ -0,0 +1,5 @@
---
features:
- |
Adds share network subnet metadata capabilities including
create, update all, update single, show and delete metadata.