Metadata for Share Network Subnet Resource

This change adds metadata controller for Share Network Subnets
resource. Bumps microversion to 2.78.

The subnet metadata is passed down to the driver together
with the network_info object.

APIImpact
Partially-implements: bp/metadata-for-share-resources

Change-Id: I8d5a03eb127941a84eea5e6e9bdf76b3489f17a8
This commit is contained in:
Felipe Rodrigues 2023-01-07 12:13:02 -03:00
parent b4a0fd9af0
commit 43de2e8fb5
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.