diff --git a/manila/api/common.py b/manila/api/common.py index 015a7f242c..d0a8cf11b0 100644 --- a/manila/api/common.py +++ b/manila/api/common.py @@ -19,6 +19,7 @@ import re import six import string +from operator import xor from oslo_config import cfg from oslo_log import log from oslo_utils import encodeutils @@ -184,6 +185,14 @@ def dict_to_query_str(params): return param_str.rstrip('&') +def check_net_id_and_subnet_id(body): + if xor('neutron_net_id' in body, 'neutron_subnet_id' in body): + msg = _("When creating a new share network subnet you need to " + "specify both neutron_net_id and neutron_subnet_id or " + "none of them.") + raise webob.exc.HTTPBadRequest(explanation=msg) + + class ViewBuilder(object): """Model API responses as dictionaries.""" diff --git a/manila/api/openstack/api_version_request.py b/manila/api/openstack/api_version_request.py index ce1bb57de9..009fb721ab 100644 --- a/manila/api/openstack/api_version_request.py +++ b/manila/api/openstack/api_version_request.py @@ -137,13 +137,16 @@ REST_API_VERSION_HISTORY = """ * 2.50 - Added update share type API to Share Type APIs. Through this API we can update the ``name``, ``description`` and/or ``share_type_access:is_public`` fields of the share type. + * 2.51 - Added Share Network with multiple Subnets. Updated Share Networks + to handle with one or more subnets in different availability + zones. """ # The minimum and maximum versions of the API supported # The default api version request is defined to be the # minimum version of the API supported. _MIN_API_VERSION = "2.0" -_MAX_API_VERSION = "2.50" +_MAX_API_VERSION = "2.51" DEFAULT_API_VERSION = _MIN_API_VERSION diff --git a/manila/api/openstack/rest_api_version_history.rst b/manila/api/openstack/rest_api_version_history.rst index 97b4a86596..04bf96d8b8 100644 --- a/manila/api/openstack/rest_api_version_history.rst +++ b/manila/api/openstack/rest_api_version_history.rst @@ -281,3 +281,9 @@ user documentation. Added update share type API to Share Type APIs. We can update the ``name``, ``description`` and/or ``share_type_access:is_public`` fields of the share type by the update share type API. + +2.51 +---- + Added to the service the possibility to have multiple subnets per share + network, each of them associated to a different AZ. It is also possible to + configure a default subnet that spans all availability zones. diff --git a/manila/api/v1/security_service.py b/manila/api/v1/security_service.py index 64edcbe538..8e998d13b8 100644 --- a/manila/api/v1/security_service.py +++ b/manila/api/v1/security_service.py @@ -151,9 +151,11 @@ class SecurityServiceController(wsgi.Controller): security_service_id): share_networks = db.share_network_get_all_by_security_service( context, security_service_id) + for sn in share_networks: - if sn['share_servers']: - return True + for sns in sn['share_network_subnets']: + if 'share_servers' in sns and sns['share_servers']: + return True return False def update(self, req, id, body): diff --git a/manila/api/v1/share_servers.py b/manila/api/v1/share_servers.py index d24a7ad7ec..adc8a1a090 100644 --- a/manila/api/v1/share_servers.py +++ b/manila/api/v1/share_servers.py @@ -47,21 +47,34 @@ class ShareServerController(wsgi.Controller): search_opts = {} search_opts.update(req.GET) - share_servers = db_api.share_server_get_all(context) for s in share_servers: - s.project_id = s.share_network['project_id'] - if s.share_network['name']: - s.share_network_name = s.share_network['name'] - else: - s.share_network_name = s.share_network_id + try: + s.share_network_id = s.share_network_subnet['share_network_id'] + share_network = db_api.share_network_get( + context, s.share_network_id) + s.project_id = share_network['project_id'] + if share_network['name']: + s.share_network_name = share_network['name'] + else: + s.share_network_name = share_network['id'] + except exception.ShareNetworkNotFound: + # NOTE(dviroel): The share-network may already be deleted while + # the share-server is in 'deleting' state. In this scenario, + # we will return some empty values. + LOG.debug("Unable to retrieve share network details for share " + "server %(server)s, the network %(network)s was " + "not found.", + {'server': s.id, 'network': s.share_network_id}) + s.project_id = '' + s.share_network_name = '' if search_opts: for k, v in search_opts.items(): share_servers = [s for s in share_servers if (hasattr(s, k) and s[k] == v or k == 'share_network' and - v in [s.share_network['name'], - s.share_network['id']])] + v in [s.share_network_name, + s.share_network_id])] return self._view_builder.build_share_servers(req, share_servers) @wsgi.Controller.authorize @@ -70,13 +83,21 @@ class ShareServerController(wsgi.Controller): context = req.environ['manila.context'] try: server = db_api.share_server_get(context, id) - server.project_id = server.share_network["project_id"] - if server.share_network['name']: - server.share_network_name = server.share_network['name'] + share_network = db_api.share_network_get( + context, server.share_network_subnet['share_network_id']) + server.share_network_id = share_network['id'] + server.project_id = share_network['project_id'] + if share_network['name']: + server.share_network_name = share_network['name'] else: - server.share_network_name = server.share_network_id + server.share_network_name = share_network['id'] except exception.ShareServerNotFound as e: - raise exc.HTTPNotFound(explanation=e) + raise exc.HTTPNotFound(explanation=e.msg) + except exception.ShareNetworkNotFound as e: + msg = _("Share server %s could not be found. Its associated " + "share network does not " + "exist.") % server.share_network_subnet['share_network_id'] + raise exc.HTTPNotFound(explanation=msg) return self._view_builder.build_share_server(req, server) @wsgi.Controller.authorize @@ -86,7 +107,7 @@ class ShareServerController(wsgi.Controller): try: share_server = db_api.share_server_get(context, id) except exception.ShareServerNotFound as e: - raise exc.HTTPNotFound(explanation=e) + raise exc.HTTPNotFound(explanation=e.msg) return self._view_builder.build_share_server_details( share_server['backend_details']) @@ -98,7 +119,7 @@ class ShareServerController(wsgi.Controller): try: share_server = db_api.share_server_get(context, id) except exception.ShareServerNotFound as e: - raise exc.HTTPNotFound(explanation=e) + raise exc.HTTPNotFound(explanation=e.msg) allowed_statuses = [constants.STATUS_ERROR, constants.STATUS_ACTIVE] if share_server['status'] not in allowed_statuses: data = { @@ -112,7 +133,7 @@ class ShareServerController(wsgi.Controller): try: self.share_api.delete_share_server(context, share_server) except exception.ShareServerInUse as e: - raise exc.HTTPConflict(explanation=e) + raise exc.HTTPConflict(explanation=e.msg) return webob.Response(status_int=http_client.ACCEPTED) diff --git a/manila/api/v1/shares.py b/manila/api/v1/shares.py index 0e8a8b4e1e..60ace7ba79 100644 --- a/manila/api/v1/shares.py +++ b/manila/api/v1/shares.py @@ -341,8 +341,15 @@ class ShareMixin(object): context, share_network_id) except exception.ShareNetworkNotFound as e: - raise exc.HTTPNotFound(explanation=six.text_type(e)) + raise exc.HTTPNotFound(explanation=e.msg) kwargs['share_network_id'] = share_network_id + if availability_zone_id: + if not db.share_network_subnet_get_by_availability_zone_id( + context, share_network_id, + availability_zone_id=availability_zone_id): + msg = _("A share network subnet was not found for the " + "requested availability zone.") + raise exc.HTTPBadRequest(explanation=msg) display_name = share.get('display_name') display_description = share.get('display_description') diff --git a/manila/api/v2/router.py b/manila/api/v2/router.py index 310a33d26c..643a93b449 100644 --- a/manila/api/v2/router.py +++ b/manila/api/v2/router.py @@ -43,6 +43,7 @@ from manila.api.v2 import share_group_types from manila.api.v2 import share_groups from manila.api.v2 import share_instance_export_locations from manila.api.v2 import share_instances +from manila.api.v2 import share_network_subnets from manila.api.v2 import share_networks from manila.api.v2 import share_replica_export_locations from manila.api.v2 import share_replicas @@ -296,6 +297,33 @@ class APIRouter(manila.api.openstack.APIRouter): collection={"detail": "GET"}, member={"action": "POST"}) + self.resources["share_network_subnets"] = ( + share_network_subnets.create_resource()) + mapper.connect("share-networks", + "/{project_id}/share-networks/{share_network_id}/" + "subnets", + controller=self.resources["share_network_subnets"], + action="create", + conditions={"method": ["POST"]}) + mapper.connect("share-networks", + "/{project_id}/share-networks/{share_network_id}/" + "subnets/{share_network_subnet_id}", + controller=self.resources["share_network_subnets"], + action="delete", + conditions={"method": ["DELETE"]}) + mapper.connect("share-networks", + "/{project_id}/share-networks/{share_network_id}/" + "subnets/{share_network_subnet_id}", + controller=self.resources["share_network_subnets"], + action="show", + conditions={"method": ["GET"]}) + mapper.connect("share-networks", + "/{project_id}/share-networks/{share_network_id}/" + "subnets", + controller=self.resources["share_network_subnets"], + action="index", + conditions={"method": ["GET"]}) + self.resources["share_servers"] = share_servers.create_resource() mapper.resource("share_server", "share-servers", diff --git a/manila/api/v2/share_network_subnets.py b/manila/api/v2/share_network_subnets.py new file mode 100644 index 0000000000..2651800e8c --- /dev/null +++ b/manila/api/v2/share_network_subnets.py @@ -0,0 +1,202 @@ +# Copyright 2019 NetApp, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from manila.api import common +from oslo_db import exception as db_exception +from oslo_log import log +from six.moves import http_client +import webob +from webob import exc + +from manila.api.openstack import wsgi +from manila.api.views import share_network_subnets as subnet_views +from manila.db import api as db_api +from manila import exception +from manila.i18n import _ +from manila.share import rpcapi as share_rpcapi + +LOG = log.getLogger(__name__) + + +class ShareNetworkSubnetController(wsgi.Controller): + """The Share Network Subnet API controller for the OpenStack API.""" + + resource_name = 'share_network_subnet' + _view_builder_class = subnet_views.ViewBuilder + + def __init__(self): + super(ShareNetworkSubnetController, self).__init__() + self.share_rpcapi = share_rpcapi.ShareAPI() + + @wsgi.Controller.api_version("2.51") + @wsgi.Controller.authorize + def index(self, req, share_network_id): + """Returns a list of share network subnets.""" + context = req.environ['manila.context'] + + try: + share_network = db_api.share_network_get(context, share_network_id) + except exception.ShareNetworkNotFound as e: + raise exc.HTTPNotFound(explanation=e.msg) + + return self._view_builder.build_share_network_subnets( + req, share_network.get('share_network_subnets')) + + def _all_share_servers_are_auto_deletable(self, share_network_subnet): + return all([ss['is_auto_deletable'] for ss + in share_network_subnet['share_servers']]) + + @wsgi.Controller.api_version('2.51') + @wsgi.Controller.authorize + def delete(self, req, share_network_id, share_network_subnet_id): + """Delete specified share network subnet.""" + context = req.environ['manila.context'] + + try: + db_api.share_network_get(context, share_network_id) + except exception.ShareNetworkNotFound as e: + raise exc.HTTPNotFound(explanation=e.msg) + + try: + share_network_subnet = db_api.share_network_subnet_get( + context, share_network_subnet_id) + except exception.ShareNetworkSubnetNotFound as e: + raise exc.HTTPNotFound(explanation=e.msg) + + for share_server in share_network_subnet['share_servers'] or []: + shares = db_api.share_instances_get_all_by_share_server( + context, share_server['id']) + if shares: + msg = _("Cannot delete share network subnet %(id)s, it has " + "one or more shares.") % { + 'id': share_network_subnet_id} + LOG.error(msg) + raise exc.HTTPConflict(explanation=msg) + + # NOTE(silvacarlose): Do not allow the deletion of any share server + # if any of them has the flag is_auto_deletable = False + if not self._all_share_servers_are_auto_deletable( + share_network_subnet): + msg = _("The service cannot determine if there are any " + "non-managed shares on the share network subnet %(id)s," + "so it cannot be deleted. Please contact the cloud " + "administrator to rectify.") % { + 'id': share_network_subnet_id} + LOG.error(msg) + raise exc.HTTPConflict(explanation=msg) + + for share_server in share_network_subnet['share_servers']: + self.share_rpcapi.delete_share_server(context, share_server) + + db_api.share_network_subnet_delete(context, share_network_subnet_id) + return webob.Response(status_int=http_client.ACCEPTED) + + def _validate_subnet(self, context, share_network_id, az=None): + """Validate the az for the given subnet. + + If az is None, the method will search for an existent default subnet. + In case of a given AZ, validates if there's an existent subnet for it. + """ + msg = ("Another share network subnet was found in the " + "specified availability zone. Only one share network " + "subnet is allowed per availability zone for share " + "network %s." % share_network_id) + if az is None: + default_subnet = db_api.share_network_subnet_get_default_subnet( + context, share_network_id) + if default_subnet is not None: + raise exc.HTTPConflict(explanation=msg) + else: + az_subnet = ( + db_api.share_network_subnet_get_by_availability_zone_id( + context, share_network_id, az['id']) + ) + # If the 'availability_zone_id' is not None, we found a conflict, + # otherwise we just have found the default subnet + if az_subnet and az_subnet['availability_zone_id']: + raise exc.HTTPConflict(explanation=msg) + + @wsgi.Controller.api_version("2.51") + @wsgi.Controller.authorize + def create(self, req, share_network_id, body): + """Add a new share network subnet into the share network.""" + context = req.environ['manila.context'] + + if not self.is_valid_body(body, 'share-network-subnet'): + msg = _("Share Network Subnet is missing from the request body.") + raise exc.HTTPBadRequest(explanation=msg) + + data = body['share-network-subnet'] + data['share_network_id'] = share_network_id + + common.check_net_id_and_subnet_id(data) + + try: + db_api.share_network_get(context, share_network_id) + except exception.ShareNetworkNotFound as e: + raise exc.HTTPNotFound(explanation=e.msg) + + availability_zone = data.pop('availability_zone', None) + subnet_az = None + + if availability_zone: + try: + subnet_az = db_api.availability_zone_get(context, + availability_zone) + except exception.AvailabilityZoneNotFound: + msg = _("The provided availability zone %s does not " + "exist.") % availability_zone + raise exc.HTTPBadRequest(explanation=msg) + + self._validate_subnet(context, share_network_id, az=subnet_az) + + try: + data['availability_zone_id'] = ( + subnet_az['id'] if subnet_az is not None else None) + share_network_subnet = db_api.share_network_subnet_create( + context, data) + except db_exception.DBError as e: + msg = _('Could not create the share network subnet.') + LOG.error(e) + raise exc.HTTPInternalServerError(explanation=msg) + share_network_subnet = db_api.share_network_subnet_get( + context, share_network_subnet['id']) + return self._view_builder.build_share_network_subnet( + req, share_network_subnet) + + @wsgi.Controller.api_version('2.51') + @wsgi.Controller.authorize + def show(self, req, share_network_id, share_network_subnet_id): + """Show share network subnet.""" + context = req.environ['manila.context'] + + try: + db_api.share_network_get(context, share_network_id) + except exception.ShareNetworkNotFound as e: + raise exc.HTTPNotFound(explanation=e.msg) + + try: + share_network_subnet = db_api.share_network_subnet_get( + context, share_network_subnet_id) + except exception.ShareNetworkSubnetNotFound as e: + raise exc.HTTPNotFound(explanation=e.msg) + + return self._view_builder.build_share_network_subnet( + req, share_network_subnet) + + +def create_resource(): + return wsgi.Resource(ShareNetworkSubnetController()) diff --git a/manila/api/v2/share_networks.py b/manila/api/v2/share_networks.py index 96e10eb3e4..11f954b150 100644 --- a/manila/api/v2/share_networks.py +++ b/manila/api/v2/share_networks.py @@ -15,6 +15,7 @@ """The shares api.""" +import copy from oslo_db import exception as db_exception from oslo_log import log from oslo_utils import timeutils @@ -66,6 +67,9 @@ class ShareNetworkController(wsgi.Controller): return all([ss['is_auto_deletable'] for ss in share_network['share_servers']]) + def _share_network_contains_subnets(self, share_network): + return len(share_network['share_network_subnets']) > 1 + def delete(self, req, id): """Delete specified share network.""" context = req.environ['manila.context'] @@ -95,18 +99,29 @@ class ShareNetworkController(wsgi.Controller): LOG.error(msg) raise exc.HTTPConflict(explanation=msg) - # NOTE(silvacarlose): Do not allow the deletion of any share server - # if one of them has the flag is_auto_deletable = False - if not self._all_share_servers_are_auto_deletable(share_network): - msg = _("The service cannot determine if there are any " - "non-managed shares on the share network %(id)s, so it " - "cannot be deleted. Please contact the cloud " - "administrator to rectify.") % {'id': id} + # NOTE(silvacarlose): Do not allow the deletion of share networks + # if it still contains two or more subnets + if self._share_network_contains_subnets(share_network): + msg = _("The share network %(id)s has more than one subnet " + "attached. Please remove the subnets untill you have one " + "or no subnets remaining.") % {'id': id} LOG.error(msg) raise exc.HTTPConflict(explanation=msg) - for share_server in share_network['share_servers']: - self.share_rpcapi.delete_share_server(context, share_server) + for subnet in share_network['share_network_subnets']: + if not self._all_share_servers_are_auto_deletable(subnet): + msg = _("The service cannot determine if there are any " + "non-managed shares on the share network subnet " + "%(id)s, so it cannot be deleted. Please contact the " + "cloud administrator to rectify.") % { + 'id': subnet['id']} + LOG.error(msg) + raise exc.HTTPConflict(explanation=msg) + + for subnet in share_network['share_network_subnets']: + for share_server in subnet['share_servers']: + self.share_rpcapi.delete_share_server(context, share_server) + db_api.share_network_delete(context, id) try: @@ -122,6 +137,16 @@ class ShareNetworkController(wsgi.Controller): user_id=share_network['user_id']) return webob.Response(status_int=http_client.ACCEPTED) + def _subnet_has_search_opt(self, key, value, network, exact_value=False): + for subnet in network.get('share_network_subnets') or []: + if subnet.get(key) == value or ( + not exact_value and + value in subnet.get(key.rstrip('~')) + if key.endswith('~') and + subnet.get(key.rstrip('~')) else ()): + return True + return False + def _get_share_networks(self, req, is_detail=True): """Returns a list of share networks.""" context = req.environ['manila.context'] @@ -179,19 +204,30 @@ class ShareNetworkController(wsgi.Controller): value = int(value) if (req.api_version_request >= api_version.APIVersionRequest("2.36")): - networks = [network for network in networks - if network.get(key) == value or - (value in network.get(key.rstrip('~')) - if key.endswith('~') and - network.get(key.rstrip('~')) else ())] + networks = [ + network for network in networks + if network.get(key) == value or + self._subnet_has_search_opt(key, value, network) or + (value in network.get(key.rstrip('~')) + if key.endswith('~') and + network.get(key.rstrip('~')) else ())] else: - networks = [network for network in networks - if network.get(key) == value] + networks = [ + network for network in networks + if network.get(key) == value or + self._subnet_has_search_opt(key, value, network, + exact_value=True)] limited_list = common.limited(networks, req) return self._view_builder.build_share_networks( req, limited_list, is_detail) + def _share_network_subnets_contain_share_servers(self, share_network): + for subnet in share_network['share_network_subnets']: + if subnet['share_servers'] and len(subnet['share_servers']) > 0: + return True + return False + def index(self, req): """Returns a summary list of share networks.""" policy.check_policy(req.environ['manila.context'], RESOURCE_NAME, @@ -223,7 +259,7 @@ class ShareNetworkController(wsgi.Controller): msg = _("nova networking is not supported starting in Ocata.") raise exc.HTTPBadRequest(explanation=msg) - if share_network['share_servers']: + if self._share_network_subnets_contain_share_servers(share_network): for value in update_values: if value not in ['name', 'description']: msg = (_("Cannot update share network %s. It is used by " @@ -231,8 +267,15 @@ class ShareNetworkController(wsgi.Controller): "fields are available for update") % share_network['id']) raise exc.HTTPForbidden(explanation=msg) - try: + if ('neutron_net_id' in update_values or + 'neutron_subnet_id' in update_values): + subnet = db_api.share_network_subnet_get_default_subnet( + context, id) + if subnet: + db_api.share_network_subnet_update(context, + subnet['id'], + update_values) share_network = db_api.share_network_update(context, id, update_values) @@ -250,14 +293,36 @@ class ShareNetworkController(wsgi.Controller): if not body or RESOURCE_NAME not in body: raise exc.HTTPUnprocessableEntity() - values = body[RESOURCE_NAME] - values['project_id'] = context.project_id - values['user_id'] = context.user_id + share_network_values = body[RESOURCE_NAME] + share_network_subnet_values = copy.deepcopy(share_network_values) + share_network_values['project_id'] = context.project_id + share_network_values['user_id'] = context.user_id - if 'nova_net_id' in values: + if 'nova_net_id' in share_network_values: msg = _("nova networking is not supported starting in Ocata.") raise exc.HTTPBadRequest(explanation=msg) + share_network_values.pop('availability_zone', None) + share_network_values.pop('neutron_net_id', None) + share_network_values.pop('neutron_subnet_id', None) + + if req.api_version_request >= api_version.APIVersionRequest("2.51"): + if 'availability_zone' in share_network_subnet_values: + try: + az = db_api.availability_zone_get( + context, + share_network_subnet_values['availability_zone']) + share_network_subnet_values['availability_zone_id'] = ( + az['id']) + share_network_subnet_values.pop('availability_zone') + except exception.AvailabilityZoneNotFound: + msg = (_("The provided availability zone %s does not " + "exist.") + % share_network_subnet_values['availability_zone']) + raise exc.HTTPBadRequest(explanation=msg) + + common.check_net_id_and_subnet_id(share_network_subnet_values) + try: reservations = QUOTAS.reserve(context, share_networks=1) except exception.OverQuota as e: @@ -279,13 +344,32 @@ class ShareNetworkController(wsgi.Controller): raise exception.ShareNetworksLimitExceeded( allowed=quotas['share_networks']) else: + # Tries to create the new share network try: - share_network = db_api.share_network_create(context, values) + share_network = db_api.share_network_create( + context, share_network_values) + except db_exception.DBError as e: + LOG.exception(e) + msg = "Could not create share network." + raise exc.HTTPInternalServerError(explanation=msg) + + share_network_subnet_values['share_network_id'] = ( + share_network['id']) + share_network_subnet_values.pop('id', None) + + # Try to create the share network subnet. If it fails, the service + # must rollback the share network creation. + try: + db_api.share_network_subnet_create( + context, share_network_subnet_values) except db_exception.DBError: - msg = "Could not save supplied data due to database error" - raise exc.HTTPBadRequest(explanation=msg) + db_api.share_network_delete(context, share_network['id']) + msg = _('Could not create share network.') + raise exc.HTTPInternalServerError(explanation=msg) QUOTAS.commit(context, reservations) + share_network = db_api.share_network_get(context, + share_network['id']) return self._view_builder.build_share_network(req, share_network) def action(self, req, id, body): @@ -305,7 +389,7 @@ class ShareNetworkController(wsgi.Controller): context = req.environ['manila.context'] policy.check_policy(context, RESOURCE_NAME, 'add_security_service') share_network = db_api.share_network_get(context, id) - if share_network['share_servers']: + if self._share_network_subnets_contain_share_servers(share_network): msg = _("Cannot add security services. Share network is used.") raise exc.HTTPForbidden(explanation=msg) security_service = db_api.security_service_get( @@ -338,7 +422,8 @@ class ShareNetworkController(wsgi.Controller): context = req.environ['manila.context'] policy.check_policy(context, RESOURCE_NAME, 'remove_security_service') share_network = db_api.share_network_get(context, id) - if share_network['share_servers']: + + if self._share_network_subnets_contain_share_servers(share_network): msg = _("Cannot remove security services. Share network is used.") raise exc.HTTPForbidden(explanation=msg) try: diff --git a/manila/api/v2/share_replicas.py b/manila/api/v2/share_replicas.py index a005be3988..89d388fee3 100644 --- a/manila/api/v2/share_replicas.py +++ b/manila/api/v2/share_replicas.py @@ -116,7 +116,6 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): share_id = body.get('share_replica').get('share_id') availability_zone = body.get('share_replica').get('availability_zone') - share_network_id = body.get('share_replica').get('share_network_id') if not share_id: msg = _("Must provide Share ID to add replica.") @@ -128,6 +127,8 @@ class ShareReplicationController(wsgi.Controller, wsgi.AdminActionsMixin): msg = _("No share exists with ID %s.") raise exc.HTTPNotFound(explanation=msg % share_id) + share_network_id = share_ref.get('share_network_id', None) + try: new_replica = self.share_api.create_share_replica( context, share_ref, availability_zone=availability_zone, diff --git a/manila/api/v2/share_servers.py b/manila/api/v2/share_servers.py index 19e46da2ac..cd9a86c68c 100644 --- a/manila/api/v2/share_servers.py +++ b/manila/api/v2/share_servers.py @@ -31,6 +31,7 @@ LOG = log.getLogger(__name__) class ShareServerController(share_servers.ShareServerController, + wsgi.Controller, wsgi.AdminActionsMixin): """The Share Server API V2 controller for the OpenStack API.""" @@ -55,28 +56,42 @@ class ShareServerController(share_servers.ShareServerController, def share_server_reset_status(self, req, id, body): return self._reset_status(req, id, body) - @wsgi.Controller.api_version("2.49") @wsgi.Controller.authorize('manage_share_server') - @wsgi.response(202) - def manage(self, req, body): + def _manage(self, req, body): """Manage a share server.""" + LOG.debug("Manage Share Server with id: %s", id) + context = req.environ['manila.context'] - identifier, host, share_network, driver_opts = ( + identifier, host, share_network, driver_opts, network_subnet = ( self._validate_manage_share_server_parameters(context, body)) try: result = self.share_api.manage_share_server( - context, identifier, host, share_network, driver_opts) + context, identifier, host, network_subnet, driver_opts) except exception.InvalidInput as e: - raise exc.HTTPBadRequest(explanation=e) + raise exc.HTTPBadRequest(explanation=e.msg) + except exception.PolicyNotAuthorized as e: + raise exc.HTTPForbidden(explanation=e.msg) result.project_id = share_network["project_id"] - if result.share_network['name']: - result.share_network_name = result.share_network['name'] + result.share_network_id = share_network["id"] + if share_network['name']: + result.share_network_name = share_network['name'] else: - result.share_network_name = result.share_network_id + result.share_network_name = share_network['id'] return self._view_builder.build_share_server(req, result) + @wsgi.Controller.api_version('2.51') + @wsgi.response(202) + def manage(self, req, body): + return self._manage(req, body) + + @wsgi.Controller.api_version('2.49') # noqa + @wsgi.response(202) + def manage(self, req, body): # pylint: disable=function-redefined + body.get('share_server', {}).pop('share_network_subnet_id', None) + return self._manage(req, body) + @wsgi.Controller.authorize('unmanage_share_server') def _unmanage(self, req, id, body=None): context = req.environ['manila.context'] @@ -91,7 +106,7 @@ class ShareServerController(share_servers.ShareServerController, share_server = db_api.share_server_get( context, id) except exception.ShareServerNotFound as e: - raise exc.HTTPNotFound(explanation=e) + raise exc.HTTPNotFound(explanation=e.msg) allowed_statuses = [constants.STATUS_ERROR, constants.STATUS_ACTIVE, constants.STATUS_MANAGE_ERROR, @@ -111,7 +126,7 @@ class ShareServerController(share_servers.ShareServerController, context, share_server, force=force) except (exception.ShareServerInUse, exception.PolicyNotAuthorized) as e: - raise exc.HTTPBadRequest(explanation=e) + raise exc.HTTPBadRequest(explanation=e.msg) return webob.Response(status_int=http_client.ACCEPTED) @@ -141,6 +156,25 @@ class ShareServerController(share_servers.ShareServerController, identifier = data['identifier'] host, share_network_id = data['host'], data['share_network_id'] + network_subnet_id = data.get('share_network_subnet_id') + if network_subnet_id: + try: + network_subnet = db_api.share_network_subnet_get( + context, network_subnet_id) + except exception.ShareNetworkSubnetNotFound: + msg = _("The share network subnet %s does not " + "exist.") % network_subnet_id + raise exc.HTTPBadRequest(explanation=msg) + else: + network_subnet = db_api.share_network_subnet_get_default_subnet( + context, share_network_id) + + if network_subnet is None: + msg = _("The share network %s does have a default subnet. Create " + "one or use a specific subnet to manage this share server " + "with API version >= 2.51.") % share_network_id + raise exc.HTTPBadRequest(explanation=msg) + if share_utils.extract_host(host, 'pool'): msg = _("Host parameter should not contain pool.") raise exc.HTTPBadRequest(explanation=msg) @@ -168,7 +202,7 @@ class ShareServerController(share_servers.ShareServerController, msg = _("Driver options must be in dictionary format.") raise exc.HTTPBadRequest(explanation=msg) - return identifier, host, share_network, driver_opts + return identifier, host, share_network, driver_opts, network_subnet def create_resource(): diff --git a/manila/api/views/share_network_subnets.py b/manila/api/views/share_network_subnets.py new file mode 100644 index 0000000000..64e0d62b99 --- /dev/null +++ b/manila/api/views/share_network_subnets.py @@ -0,0 +1,53 @@ +# Copyright 2019 NetApp, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from manila.api import common + + +class ViewBuilder(common.ViewBuilder): + """Model a server API response as a python dictionary.""" + + _collection_name = 'share_network_subnets' + + def build_share_network_subnet(self, request, share_network_subnet): + return { + 'share_network_subnet': self._build_share_network_subnet_view( + request, share_network_subnet)} + + def build_share_network_subnets(self, request, share_network_subnets): + return {'share_network_subnets': + [self._build_share_network_subnet_view( + request, share_network_subnet) + for share_network_subnet in share_network_subnets]} + + def _build_share_network_subnet_view(self, request, share_network_subnet): + sns = { + 'id': share_network_subnet.get('id'), + 'availability_zone': share_network_subnet.get('availability_zone'), + 'share_network_id': share_network_subnet.get('share_network_id'), + 'share_network_name': share_network_subnet.share_network['name'], + 'created_at': share_network_subnet.get('created_at'), + 'segmentation_id': share_network_subnet.get('segmentation_id'), + 'neutron_subnet_id': share_network_subnet.get('neutron_subnet_id'), + 'updated_at': share_network_subnet.get('updated_at'), + 'neutron_net_id': share_network_subnet.get('neutron_net_id'), + 'ip_version': share_network_subnet.get('ip_version'), + 'cidr': share_network_subnet.get('cidr'), + 'network_type': share_network_subnet.get('network_type'), + 'mtu': share_network_subnet.get('mtu'), + 'gateway': share_network_subnet.get('gateway') + } + self.update_versioned_resource_dict(request, sns, share_network_subnet) + return sns diff --git a/manila/api/views/share_networks.py b/manila/api/views/share_networks.py index 2f5d356078..c05bba3957 100644 --- a/manila/api/views/share_networks.py +++ b/manila/api/views/share_networks.py @@ -20,7 +20,8 @@ class ViewBuilder(common.ViewBuilder): """Model a server API response as a python dictionary.""" _collection_name = 'share_networks' - _detail_version_modifiers = ["add_gateway", "add_mtu", "add_nova_net_id"] + _detail_version_modifiers = ["add_gateway", "add_mtu", "add_nova_net_id", + "add_subnets"] def build_share_network(self, request, share_network): """View of a share network.""" @@ -34,6 +35,20 @@ class ViewBuilder(common.ViewBuilder): request, share_network, is_detail) for share_network in share_networks]} + def _update_share_network_info(self, request, share_network): + for sns in share_network.get('share_network_subnets') or []: + if sns.get('is_default') and sns.get('is_default') is True: + share_network.update({ + 'neutron_net_id': sns.get('neutron_net_id'), + 'neutron_subnet_id': sns.get('neutron_subnet_id'), + 'network_type': sns.get('network_type'), + 'segmentation_id': sns.get('segmentation_id'), + 'cidr': sns.get('cidr'), + 'ip_version': sns.get('ip_version'), + 'gateway': sns.get('gateway'), + 'mtu': sns.get('mtu'), + }) + def _build_share_network_view(self, request, share_network, is_detail=True): sn = { @@ -41,6 +56,7 @@ class ViewBuilder(common.ViewBuilder): 'name': share_network.get('name'), } if is_detail: + self._update_share_network_info(request, share_network) sn.update({ 'project_id': share_network.get('project_id'), 'created_at': share_network.get('created_at'), @@ -57,6 +73,30 @@ class ViewBuilder(common.ViewBuilder): self.update_versioned_resource_dict(request, sn, share_network) return sn + @common.ViewBuilder.versioned_method("2.51") + def add_subnets(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'), + } 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) + @common.ViewBuilder.versioned_method("2.18") def add_gateway(self, context, network_dict, network): network_dict['gateway'] = network.get('gateway') diff --git a/manila/api/views/share_servers.py b/manila/api/views/share_servers.py index e0c3252c7a..2b7a59a238 100644 --- a/manila/api/views/share_servers.py +++ b/manila/api/views/share_servers.py @@ -22,6 +22,7 @@ class ViewBuilder(common.ViewBuilder): _collection_name = 'share_servers' _detail_version_modifiers = [ "add_is_auto_deletable_and_identifier_fields", + "add_share_network_subnet_id_field" ] def build_share_server(self, request, share_server): @@ -56,11 +57,17 @@ class ViewBuilder(common.ViewBuilder): share_server_dict['created_at'] = share_server.created_at share_server_dict['backend_details'] = share_server.backend_details - self.update_versioned_resource_dict( - request, share_server_dict, share_server) + self.update_versioned_resource_dict( + request, share_server_dict, share_server) return share_server_dict + @common.ViewBuilder.versioned_method("2.51") + def add_share_network_subnet_id_field( + self, context, share_server_dict, share_server): + share_server_dict['share_network_subnet_id'] = ( + share_server['share_network_subnet_id']) + @common.ViewBuilder.versioned_method("2.49") def add_is_auto_deletable_and_identifier_fields( self, context, share_server_dict, share_server): diff --git a/manila/db/api.py b/manila/db/api.py index b1e9023cb1..e087ebb241 100644 --- a/manila/db/api.py +++ b/manila/db/api.py @@ -848,6 +848,55 @@ def count_share_networks(context, project_id, user_id=None, ################## +def share_network_subnet_create(context, values): + """Create a share network subnet DB record.""" + return IMPL.share_network_subnet_create(context, values) + + +def share_network_subnet_delete(context, network_subnet_id): + """Delete a share network subnet DB record.""" + return IMPL.share_network_subnet_delete(context, network_subnet_id) + + +def share_network_subnet_update(context, network_subnet_id, values): + """Update a share network subnet DB record.""" + return IMPL.share_network_subnet_update(context, network_subnet_id, values) + + +def share_network_subnet_get(context, network_subnet_id, session=None): + """Get requested share network subnet DB record.""" + return IMPL.share_network_subnet_get(context, network_subnet_id, + session=session) + + +def share_network_subnet_get_all(context): + """Get all share network subnet DB record.""" + return IMPL.share_network_subnet_get_all(context) + + +def share_network_subnet_get_by_availability_zone_id(context, share_network_id, + availability_zone_id): + """Get a share network subnet DB record. + + This method returns a subnet DB record for a given share network id and + an availability zone. If the 'availability_zone_id' is 'None', a record may + be returned and it will represent the default share network subnet. + Be aware that if there is no subnet for a specific availability zone id, + this method will return the default share network subnet, if it exists. + """ + return IMPL.share_network_subnet_get_by_availability_zone_id( + context, share_network_id, availability_zone_id) + + +def share_network_subnet_get_default_subnet(context, share_network_id): + """Get the default share network subnet DB record.""" + return IMPL.share_network_subnet_get_default_subnet(context, + share_network_id) + + +################## + + def network_allocation_create(context, values): """Create a network allocation DB record.""" return IMPL.network_allocation_create(context, values) diff --git a/manila/db/migrations/alembic/versions/805685098bd2_add_share_network_subnets_table_and_modify_share_servers_table.py b/manila/db/migrations/alembic/versions/805685098bd2_add_share_network_subnets_table_and_modify_share_servers_table.py new file mode 100644 index 0000000000..983155b52f --- /dev/null +++ b/manila/db/migrations/alembic/versions/805685098bd2_add_share_network_subnets_table_and_modify_share_servers_table.py @@ -0,0 +1,231 @@ +# Copyright 2019 NetApp, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""add_share_network_subnets_table_and_modify_share_networks_and_servers + +Revision ID: 805685098bd2 +Revises: 6a3fd2984bc31 +Create Date: 2019-05-09 16:28:41.919714 + +""" + +# revision identifiers, used by Alembic. +revision = '805685098bd2' +down_revision = '6a3fd2984bc31' + +from alembic import op +from manila.db.migrations import utils +from oslo_log import log +from oslo_utils import uuidutils + +import sqlalchemy as sa + +LOG = log.getLogger(__name__) + + +def upgrade(): + # New table + try: + share_networks_fk_name = ( + "fk_share_network_subnets_share_network_id_share_networks") + availability_zones_fk_name = ( + "fk_share_network_subnets_availaility_zone_id_availability_zones") + share_network_subnets_table = op.create_table( + 'share_network_subnets', + sa.Column('id', sa.String(36), primary_key=True, nullable=False), + sa.Column('neutron_net_id', sa.String(36), nullable=True), + sa.Column('neutron_subnet_id', sa.String(36), nullable=True), + sa.Column('network_type', sa.String(32), nullable=True), + sa.Column('cidr', sa.String(64), nullable=True), + sa.Column('segmentation_id', sa.Integer, nullable=True), + sa.Column('gateway', sa.String(64), nullable=True), + sa.Column('mtu', sa.Integer, nullable=True), + sa.Column('share_network_id', sa.String(36), sa.ForeignKey( + 'share_networks.id', name=share_networks_fk_name)), + sa.Column('ip_version', sa.Integer, nullable=True), + sa.Column('availability_zone_id', sa.String(36), + sa.ForeignKey('availability_zones.id', + name=availability_zones_fk_name)), + sa.Column('created_at', sa.DateTime), + sa.Column('updated_at', sa.DateTime), + sa.Column('deleted_at', sa.DateTime), + sa.Column('deleted', sa.String(36), default='False'), + mysql_engine='InnoDB', + mysql_charset='utf8' + ) + except Exception: + LOG.error("Table |%s| not created!", 'share_network_subnets') + raise + + share_serves_fk_name = ( + "fk_share_servers_share_network_subnet_id_share_network_subnets") + op.add_column( + 'share_servers', + sa.Column( + 'share_network_subnet_id', sa.String(36), + sa.ForeignKey('share_network_subnets.id', + name=share_serves_fk_name), + ) + ) + + connection = op.get_bind() + + share_networks_table = utils.load_table('share_networks', connection) + share_servers_table = utils.load_table('share_servers', connection) + + share_network_subnets = [] + + # Get all share_networks and move all their data to share network subnet + for share_network in connection.execute(share_networks_table.select()): + share_network_subnet = { + 'id': uuidutils.generate_uuid(), + 'neutron_net_id': share_network.neutron_net_id, + 'neutron_subnet_id': share_network.neutron_subnet_id, + 'network_type': share_network.network_type, + 'cidr': share_network.cidr, + 'segmentation_id': share_network.segmentation_id, + 'gateway': share_network.gateway, + 'mtu': share_network.mtu, + 'share_network_id': share_network.id, + 'ip_version': share_network.ip_version, + 'created_at': share_network.created_at, + 'updated_at': share_network.updated_at, + 'deleted_at': share_network.deleted_at, + 'deleted': share_network.deleted, + } + share_network_subnets.append(share_network_subnet) + + # Insertions for the new share network subnets + op.bulk_insert(share_network_subnets_table, share_network_subnets) + + # Updates the field share server table with the share network subnet id + for sns in share_network_subnets: + share_servers = connection.execute(share_servers_table.select().where( + share_servers_table.c.share_network_id == sns['share_network_id'] + )) + updated_data = {'share_network_subnet_id': sns['id']} + _update_share_servers(share_servers, updated_data, share_servers_table) + + if connection.engine.name == 'mysql': + # Drops necessary constraint from share servers table. Only mysql + # needs constraint handling. Postgresql/sqlite don't + op.drop_constraint("share_servers_ibfk_1", "share_servers", + type_="foreignkey") + + op.drop_column('share_servers', 'share_network_id') + op.drop_column('share_networks', 'neutron_net_id') + op.drop_column('share_networks', 'neutron_subnet_id') + op.drop_column('share_networks', 'network_type') + op.drop_column('share_networks', 'segmentation_id') + op.drop_column('share_networks', 'gateway') + op.drop_column('share_networks', 'mtu') + op.drop_column('share_networks', 'cidr') + op.drop_column('share_networks', 'ip_version') + + +def _update_share_servers(share_servers, updated_data, share_servers_table): + for share_server in share_servers: + # pylint: disable=no-value-for-parameter + op.execute( + share_servers_table.update().where( + share_servers_table.c.id == share_server.id, + ).values(updated_data) + ) + + +def retrieve_default_subnet(subnets): + # NOTE (silvacarlose): A default subnet is that one which doesn't contain + # an availability zone. If all the share networks contain an az, we can + # retrieve whichever share network, then we pick up the first. + for subnet in subnets: + if subnet.availability_zone_id is None: + return subnet + + return subnets[0] if subnets is not None else None + + +def downgrade(): + connection = op.get_bind() + + # Include again the removed fields in the share network table + op.add_column('share_networks', + sa.Column('neutron_net_id', sa.String(36), nullable=True)) + op.add_column('share_networks', + sa.Column('neutron_subnet_id', sa.String(36), nullable=True)) + op.add_column('share_networks', + sa.Column('network_type', sa.String(32), nullable=True)) + op.add_column('share_networks', + sa.Column('cidr', sa.String(64), nullable=True)) + op.add_column('share_networks', + sa.Column('gateway', sa.String(64), nullable=True)) + op.add_column('share_networks', + sa.Column('mtu', sa.Integer, nullable=True)) + op.add_column('share_networks', + sa.Column('segmentation_id', sa.Integer, nullable=True)) + op.add_column('share_networks', + sa.Column('ip_version', sa.Integer, nullable=True)) + + # Include again the removed field in the share server table + op.add_column('share_servers', + sa.Column('share_network_id', sa.String(36), + sa.ForeignKey('share_networks.id', + name="share_servers_ibfk_1"))) + + share_networks_table = utils.load_table('share_networks', connection) + share_servers_table = utils.load_table('share_servers', connection) + subnets_table = utils.load_table('share_network_subnets', connection) + + for share_network in connection.execute(share_networks_table.select()): + network_subnets = connection.execute(subnets_table.select().where( + subnets_table.c.share_network_id == share_network.id)) + default_subnet = retrieve_default_subnet(network_subnets) + + if default_subnet is not None: + op.execute( + # pylint: disable=no-value-for-parameter + share_networks_table.update().where( + share_networks_table.c.id == share_network.id, + ).values({ + 'neutron_net_id': default_subnet.neutron_net_id, + 'neutron_subnet_id': default_subnet.neutron_subnet_id, + 'network_type': default_subnet.network_type, + 'cidr': default_subnet.cidr, + 'gateway': default_subnet.gateway, + 'mtu': default_subnet.mtu, + 'segmentation_id': default_subnet.segmentation_id, + 'ip_version': default_subnet.ip_version, + }) + ) + + for network_subnet in network_subnets: + share_servers = connection.execute( + share_servers_table.select().where( + share_servers_table.c.share_network_subnet_id == + network_subnet.id)) + updated_data = {'share_network_id': share_network.id} + _update_share_servers(share_servers, updated_data, + share_servers_table) + + share_serves_fk_name = ( + "fk_share_servers_share_network_subnet_id_share_network_subnets") + if connection.engine.name == 'mysql': + op.drop_constraint(share_serves_fk_name, "share_servers", + type_="foreignkey") + op.drop_column('share_servers', 'share_network_subnet_id') + try: + op.drop_table('share_network_subnets') + except Exception: + LOG.error("Failed to drop 'share_network_subnets' table!") + raise diff --git a/manila/db/sqlalchemy/api.py b/manila/db/sqlalchemy/api.py index c85417e795..36c21b8baa 100644 --- a/manila/db/sqlalchemy/api.py +++ b/manila/db/sqlalchemy/api.py @@ -42,7 +42,7 @@ from oslo_utils import uuidutils import six from sqlalchemy import MetaData from sqlalchemy import or_ -from sqlalchemy.orm import joinedload +from sqlalchemy.orm import joinedload, load_only from sqlalchemy.sql.expression import literal from sqlalchemy.sql.expression import true from sqlalchemy.sql import func @@ -3396,7 +3396,7 @@ def _network_get_query(context, session=None): return (model_query(context, models.ShareNetwork, session=session). options(joinedload('share_instances'), joinedload('security_services'), - joinedload('share_servers'))) + joinedload('share_network_subnets'))) @require_context @@ -3454,8 +3454,8 @@ def share_network_get_all_by_security_service(context, security_service_id): join(models.ShareNetworkSecurityServiceAssociation, models.ShareNetwork.id == models.ShareNetworkSecurityServiceAssociation.share_network_id). - filter_by(security_service_id=security_service_id, deleted=0). - options(joinedload('share_servers')).all()) + filter_by(security_service_id=security_service_id, deleted=0) + .all()) @require_context @@ -3534,13 +3534,103 @@ def count_share_networks(context, project_id, user_id=None, ################### +@require_context +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'))) + + +@require_context +def share_network_subnet_create(context, values): + values = ensure_model_dict_has_id(values) + + network_subnet_ref = models.ShareNetworkSubnet() + network_subnet_ref.update(values) + session = get_session() + with session.begin(): + network_subnet_ref.save(session=session) + return share_network_subnet_get( + context, network_subnet_ref['id'], session=session) + + +@require_context +def share_network_subnet_delete(context, network_subnet_id): + + session = get_session() + with session.begin(): + network_subnet_ref = share_network_subnet_get(context, + network_subnet_id, + session=session) + network_subnet_ref.soft_delete(session=session, update_status=True) + + +@require_context +@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True) +def share_network_subnet_update(context, network_subnet_id, values): + session = get_session() + with session.begin(): + network_subnet_ref = share_network_subnet_get(context, + network_subnet_id, + session=session) + network_subnet_ref.update(values) + network_subnet_ref.save(session=session) + return network_subnet_ref + + +@require_context +def share_network_subnet_get(context, network_subnet_id, session=None): + result = (_network_subnet_get_query(context, session) + .filter_by(id=network_subnet_id) + .first()) + if result is None: + raise exception.ShareNetworkSubnetNotFound( + share_network_subnet_id=network_subnet_id) + return result + + +@require_context +def share_network_subnet_get_all(context): + return _network_subnet_get_query(context).all() + + +@require_context +def share_network_subnet_get_all_by_share_network(context, network_id): + return _network_subnet_get_query(context).filter_by( + share_network_id=network_id).all() + + +@require_context +def share_network_subnet_get_by_availability_zone_id( + context, share_network_id, availability_zone_id): + result = (_network_subnet_get_query(context).filter_by( + share_network_id=share_network_id, + availability_zone_id=availability_zone_id).first()) + # If a specific subnet wasn't found, try get the default one + if availability_zone_id and not result: + return (_network_subnet_get_query(context).filter_by( + share_network_id=share_network_id, + availability_zone_id=None).first()) + return result + + +@require_context +def share_network_subnet_get_default_subnet(context, share_network_id): + return share_network_subnet_get_by_availability_zone_id( + context, share_network_id, availability_zone_id=None) + + +################### + + def _server_get_query(context, session=None): if session is None: session = get_session() return (model_query(context, models.ShareServer, session=session). options(joinedload('share_instances'), joinedload('network_allocations'), - joinedload('share_network'))) + joinedload('share_network_subnet'))) @require_context @@ -3632,11 +3722,22 @@ def share_server_search_by_identifier(context, identifier, session=None): def share_server_get_all_by_host_and_share_net_valid(context, host, share_net_id, session=None): - result = (_server_get_query(context, session).filter_by(host=host) - .filter_by(share_network_id=share_net_id) + # Get subnets by share_net_id + subnets = (_network_subnet_get_query(context, session) + .filter_by(share_network_id=share_net_id) + .options(load_only("id")) + .all()) + subnet_ids = [s.id for s in subnets] + + # Retrieve servers by the retrieved subnet ids + result = (_server_get_query(context, session) + .filter_by(host=host) + .filter(models.ShareServer.share_network_subnet_id.in_( + subnet_ids)) .filter(models.ShareServer.status.in_( - (constants.STATUS_CREATING, - constants.STATUS_ACTIVE))).all()) + (constants.STATUS_CREATING, + constants.STATUS_ACTIVE))).all()) + if not result: filters_description = ('share_network_id is "%(share_net_id)s",' ' host is "%(host)s" and status in' diff --git a/manila/db/sqlalchemy/models.py b/manila/db/sqlalchemy/models.py index 0f08cc795b..7577a0e51a 100644 --- a/manila/db/sqlalchemy/models.py +++ b/manila/db/sqlalchemy/models.py @@ -916,14 +916,6 @@ class ShareNetwork(BASE, ManilaBase): deleted = Column(String(36), default='False') project_id = Column(String(255), nullable=False) user_id = Column(String(255), nullable=False) - neutron_net_id = Column(String(36), nullable=True) - neutron_subnet_id = Column(String(36), nullable=True) - network_type = Column(String(32), nullable=True) - segmentation_id = Column(Integer, nullable=True) - cidr = Column(String(64), nullable=True) - gateway = Column(String(64), nullable=True) - mtu = Column(Integer, nullable=True) - ip_version = Column(Integer, nullable=True) name = Column(String(255), nullable=True) description = Column(String(255), nullable=True) security_services = orm.relationship( @@ -945,19 +937,68 @@ class ShareNetwork(BASE, ManilaBase): primaryjoin='and_(' 'ShareNetwork.id == ShareInstance.share_network_id,' 'ShareInstance.deleted == "False")') + share_network_subnets = orm.relationship( + "ShareNetworkSubnet", backref='share_network', lazy='immediate', + primaryjoin='and_' + '(ShareNetwork.id == ShareNetworkSubnet.share_network_id,' + 'ShareNetworkSubnet.deleted == "False")') + + +class ShareNetworkSubnet(BASE, ManilaBase): + """Represents a share network subnet used by some resources.""" + + _extra_keys = ['availability_zone'] + + __tablename__ = 'share_network_subnets' + id = Column(String(36), primary_key=True, nullable=False) + neutron_net_id = Column(String(36), nullable=True) + neutron_subnet_id = Column(String(36), nullable=True) + network_type = Column(String(32), nullable=True) + cidr = Column(String(64), nullable=True) + segmentation_id = Column(Integer, nullable=True) + gateway = Column(String(64), nullable=True) + mtu = Column(Integer, nullable=True) + deleted = Column(String(36), default='False') + share_network_id = Column(String(36), ForeignKey('share_networks.id'), + nullable=False) + ip_version = Column(Integer, nullable=True) + availability_zone_id = Column( + String(36), ForeignKey('availability_zones.id'), nullable=True) + share_servers = orm.relationship( - "ShareServer", backref='share_network', - primaryjoin='and_(ShareNetwork.id == ShareServer.share_network_id,' + "ShareServer", backref='share_network_subnet', + lazy='immediate', + primaryjoin='and_(ShareNetworkSubnet.id ' + '== ShareServer.share_network_subnet_id,' 'ShareServer.deleted == "False")') + _availability_zone = orm.relationship( + "AvailabilityZone", + lazy='immediate', + foreign_keys=availability_zone_id, + primaryjoin=( + "and_(" + "ShareNetworkSubnet.availability_zone_id == AvailabilityZone.id, " + "AvailabilityZone.deleted == 'False')")) + + @property + def availability_zone(self): + if self._availability_zone: + return self._availability_zone['name'] + + @property + def is_default(self): + return self.availability_zone_id is None + 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') - share_network_id = Column(String(36), ForeignKey('share_networks.id'), - nullable=True) + share_network_subnet_id = Column( + String(36), ForeignKey('share_network_subnets.id'), + nullable=True) host = Column(String(255), nullable=False) is_auto_deletable = Column(Boolean, default=True) identifier = Column(String(255), nullable=True) diff --git a/manila/exception.py b/manila/exception.py index 769fb02fc0..1f2cd20f47 100644 --- a/manila/exception.py +++ b/manila/exception.py @@ -228,6 +228,11 @@ class ShareNetworkNotFound(NotFound): message = _("Share network %(share_network_id)s could not be found.") +class ShareNetworkSubnetNotFound(NotFound): + message = _("Share network subnet %(share_network_subnet_id)s could not be" + " found.") + + class ShareServerNotFound(NotFound): message = _("Share server %(share_server_id)s could not be found.") diff --git a/manila/network/__init__.py b/manila/network/__init__.py index bdc7d9173e..343fb6cc53 100644 --- a/manila/network/__init__.py +++ b/manila/network/__init__.py @@ -85,6 +85,14 @@ class NetworkBaseAPI(db_base.Base): "'%s'.") % share_server_id raise exception.NetworkBadConfigurationException(reason=msg) + def _verify_share_network_subnet(self, share_server_id, + share_network_subnet): + if share_network_subnet is None: + msg = _("'Share network subnet' is not provided for setting up " + "network interfaces for 'Share server' " + "'%s'.") % share_server_id + raise exception.NetworkBadConfigurationException(reason=msg) + def update_network_allocation(self, context, share_server): """Update network allocation. @@ -98,7 +106,7 @@ class NetworkBaseAPI(db_base.Base): @abc.abstractmethod def allocate_network(self, context, share_server, share_network=None, - **kwargs): + share_network_subnet=None, **kwargs): pass @abc.abstractmethod @@ -106,8 +114,9 @@ class NetworkBaseAPI(db_base.Base): pass @abc.abstractmethod - def manage_network_allocations(self, context, allocations, share_server, - share_network=None): + def manage_network_allocations( + self, context, allocations, share_server, share_network=None, + share_network_subnet=None): pass @abc.abstractmethod diff --git a/manila/network/neutron/neutron_network_plugin.py b/manila/network/neutron/neutron_network_plugin.py index e356f42b26..b7706bd53e 100644 --- a/manila/network/neutron/neutron_network_plugin.py +++ b/manila/network/neutron/neutron_network_plugin.py @@ -125,19 +125,21 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): **self._neutron_api_kwargs) return self._neutron_api - def _store_neutron_net_info(self, context, share_network): - self._save_neutron_network_data(context, share_network) - self._save_neutron_subnet_data(context, share_network) + def _store_neutron_net_info(self, context, share_network_subnet): + self._save_neutron_network_data(context, share_network_subnet) + self._save_neutron_subnet_data(context, share_network_subnet) def allocate_network(self, context, share_server, share_network=None, - **kwargs): + share_network_subnet=None, **kwargs): """Allocate network resources using given network information. Create neutron ports for a given neutron network and subnet, create manila db records for allocated neutron ports. :param context: RequestContext object + :param share_server: share server data :param share_network: share network data + :param share_network_subnet: share network subnet data :param kwargs: allocations parameters given by the back-end driver. Supported params: 'count' - how many allocations should be created @@ -149,32 +151,37 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): raise exception.NetworkBadConfigurationException(reason=msg) self._verify_share_network(share_server['id'], share_network) - self._store_neutron_net_info(context, share_network) + self._verify_share_network_subnet(share_server['id'], + share_network_subnet) + self._store_neutron_net_info(context, share_network_subnet) allocation_count = kwargs.get('count', 1) device_owner = kwargs.get('device_owner', 'share') ports = [] for __ in range(0, allocation_count): - ports.append(self._create_port(context, share_server, - share_network, device_owner)) + ports.append(self._create_port( + context, share_server, share_network, + share_network_subnet, device_owner)) return ports - def manage_network_allocations(self, context, allocations, share_server, - share_network=None): + def manage_network_allocations( + self, context, allocations, share_server, share_network=None, + share_network_subnet=None): - self._verify_share_network(share_server['id'], share_network) - self._store_neutron_net_info(context, share_network) + self._verify_share_network_subnet(share_server['id'], + share_network_subnet) + self._store_neutron_net_info(context, share_network_subnet) # We begin matching the allocations to known neutron ports and # finally return the non-consumed allocations remaining_allocations = list(allocations) - - fixed_ip_filter = 'subnet_id=' + share_network['neutron_subnet_id'] + fixed_ip_filter = ('subnet_id=' + + share_network_subnet['neutron_subnet_id']) port_list = self.neutron_api.list_ports( - network_id=share_network['neutron_net_id'], + network_id=share_network_subnet['neutron_net_id'], device_owner='manila:share', fixed_ips=fixed_ip_filter) @@ -189,15 +196,15 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): 'id': selected_port['port']['id'], 'share_server_id': share_server['id'], 'ip_address': selected_port['allocation'], - 'gateway': share_network['gateway'], + 'gateway': share_network_subnet['gateway'], 'mac_address': selected_port['port']['mac_address'], 'status': constants.STATUS_ACTIVE, 'label': self.label, - 'network_type': share_network.get('network_type'), - 'segmentation_id': share_network.get('segmentation_id'), - 'ip_version': share_network['ip_version'], - 'cidr': share_network['cidr'], - 'mtu': share_network['mtu'], + 'network_type': share_network_subnet.get('network_type'), + 'segmentation_id': share_network_subnet.get('segmentation_id'), + 'ip_version': share_network_subnet['ip_version'], + 'cidr': share_network_subnet['cidr'], + 'mtu': share_network_subnet['mtu'], } # There should not be existing allocations with the same port_id. @@ -296,37 +303,38 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): for port in ports: self._delete_port(context, port) - def _get_port_create_args(self, share_server, share_network, + def _get_port_create_args(self, share_server, share_network_subnet, device_owner): return { - "network_id": share_network['neutron_net_id'], - "subnet_id": share_network['neutron_subnet_id'], + "network_id": share_network_subnet['neutron_net_id'], + "subnet_id": share_network_subnet['neutron_subnet_id'], "device_owner": 'manila:' + device_owner, "device_id": share_server.get('id'), } - def _create_port(self, context, share_server, share_network, device_owner): - create_args = self._get_port_create_args(share_server, share_network, - device_owner) + def _create_port(self, context, share_server, share_network, + share_network_subnet, device_owner): + create_args = self._get_port_create_args( + share_server, share_network_subnet, device_owner) port = self.neutron_api.create_port( share_network['project_id'], **create_args) - ip_address = self._get_matched_ip_address(port['fixed_ips'], - share_network['ip_version']) + ip_address = self._get_matched_ip_address( + port['fixed_ips'], share_network_subnet['ip_version']) port_dict = { 'id': port['id'], 'share_server_id': share_server['id'], 'ip_address': ip_address, - 'gateway': share_network['gateway'], + 'gateway': share_network_subnet['gateway'], 'mac_address': port['mac_address'], 'status': constants.STATUS_ACTIVE, 'label': self.label, - 'network_type': share_network.get('network_type'), - 'segmentation_id': share_network.get('segmentation_id'), - 'ip_version': share_network['ip_version'], - 'cidr': share_network['cidr'], - 'mtu': share_network['mtu'], + 'network_type': share_network_subnet.get('network_type'), + 'segmentation_id': share_network_subnet.get('segmentation_id'), + 'ip_version': share_network_subnet['ip_version'], + 'cidr': share_network_subnet['cidr'], + 'mtu': share_network_subnet['mtu'], } return self.db.network_allocation_create(context, port_dict) @@ -344,19 +352,19 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): extensions = self.neutron_api.list_extensions() return neutron_constants.PROVIDER_NW_EXT in extensions - def _is_neutron_multi_segment(self, share_network, net_info=None): + def _is_neutron_multi_segment(self, share_network_subnet, net_info=None): if net_info is None: net_info = self.neutron_api.get_network( - share_network['neutron_net_id']) + share_network_subnet['neutron_net_id']) return 'segments' in net_info - def _save_neutron_network_data(self, context, share_network): + def _save_neutron_network_data(self, context, share_network_subnet): net_info = self.neutron_api.get_network( - share_network['neutron_net_id']) + share_network_subnet['neutron_net_id']) segmentation_id = None network_type = None - if self._is_neutron_multi_segment(share_network, net_info): + if self._is_neutron_multi_segment(share_network_subnet, net_info): # we have a multi segment network and need to identify the # lowest segment used for binding phy_nets = [] @@ -383,26 +391,26 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): 'segmentation_id': segmentation_id, 'mtu': net_info['mtu'], } - share_network.update(provider_nw_dict) + share_network_subnet.update(provider_nw_dict) if self.label != 'admin': - self.db.share_network_update( - context, share_network['id'], provider_nw_dict) + self.db.share_network_subnet_update( + context, share_network_subnet['id'], provider_nw_dict) - def _save_neutron_subnet_data(self, context, share_network): + def _save_neutron_subnet_data(self, context, share_network_subnet): subnet_info = self.neutron_api.get_subnet( - share_network['neutron_subnet_id']) + share_network_subnet['neutron_subnet_id']) subnet_values = { 'cidr': subnet_info['cidr'], 'gateway': subnet_info['gateway_ip'], 'ip_version': subnet_info['ip_version'] } - share_network.update(subnet_values) + share_network_subnet.update(subnet_values) if self.label != 'admin': - self.db.share_network_update( - context, share_network['id'], subnet_values) + self.db.share_network_subnet_update( + context, share_network_subnet['id'], subnet_values) class NeutronSingleNetworkPlugin(NeutronNetworkPlugin): @@ -416,36 +424,47 @@ class NeutronSingleNetworkPlugin(NeutronNetworkPlugin): self.subnet = self.neutron_api.configuration.neutron_subnet_id self._verify_net_and_subnet() - def _select_proper_share_network(self, context, share_network): + def _select_proper_share_network_subnet(self, context, + share_network_subnet): if self.label != 'admin': - share_network = self._update_share_network_net_data( - context, share_network) + share_network_subnet = self._update_share_network_net_data( + context, share_network_subnet) else: - share_network = { + share_network_subnet = { 'project_id': self.neutron_api.admin_project_id, 'neutron_net_id': self.net, 'neutron_subnet_id': self.subnet, } - return share_network + return share_network_subnet def allocate_network(self, context, share_server, share_network=None, - **kwargs): + share_network_subnet=None, **kwargs): - share_network = self._select_proper_share_network( - context, share_network) + share_network_subnet = self._select_proper_share_network_subnet( + context, share_network_subnet) + # Update share network project_id info if needed + if share_network_subnet.get('project_id', None) is not None: + share_network['project_id'] = share_network_subnet.pop( + 'project_id') return super(NeutronSingleNetworkPlugin, self).allocate_network( - context, share_server, share_network, **kwargs) + context, share_server, share_network, share_network_subnet, + **kwargs) - def manage_network_allocations(self, context, allocations, share_server, - share_network=None): - - share_network = self._select_proper_share_network( - context, share_network) + def manage_network_allocations( + self, context, allocations, share_server, share_network=None, + share_network_subnet=None): + share_network_subnet = self._select_proper_share_network_subnet( + context, share_network_subnet) + # Update share network project_id info if needed + if share_network and share_network_subnet.get('project_id', None): + share_network['project_id'] = ( + share_network_subnet.pop('project_id')) return super(NeutronSingleNetworkPlugin, self).manage_network_allocations( - context, allocations, share_server, share_network) + context, allocations, share_server, share_network, + share_network_subnet) def _verify_net_and_subnet(self): data = dict(net=self.net, subnet=self.subnet) @@ -460,33 +479,33 @@ class NeutronSingleNetworkPlugin(NeutronNetworkPlugin): "Neutron net and subnet are expected to be both set. " "Got: net=%(net)s and subnet=%(subnet)s." % data) - def _update_share_network_net_data(self, context, share_network): + def _update_share_network_net_data(self, context, share_network_subnet): upd = dict() - if not share_network.get('neutron_net_id') == self.net: - if share_network.get('neutron_net_id') is not None: + if not share_network_subnet.get('neutron_net_id') == self.net: + if share_network_subnet.get('neutron_net_id') is not None: raise exception.NetworkBadConfigurationException( "Using neutron net id different from None or value " "specified in the config is forbidden for " "NeutronSingleNetworkPlugin. Allowed values: (%(net)s, " "None), received value: %(err)s" % { "net": self.net, - "err": share_network.get('neutron_net_id')}) + "err": share_network_subnet.get('neutron_net_id')}) upd['neutron_net_id'] = self.net - if not share_network.get('neutron_subnet_id') == self.subnet: - if share_network.get('neutron_subnet_id') is not None: + if not share_network_subnet.get('neutron_subnet_id') == self.subnet: + if share_network_subnet.get('neutron_subnet_id') is not None: raise exception.NetworkBadConfigurationException( "Using neutron subnet id different from None or value " "specified in the config is forbidden for " "NeutronSingleNetworkPlugin. Allowed values: (%(snet)s, " "None), received value: %(err)s" % { "snet": self.subnet, - "err": share_network.get('neutron_subnet_id')}) + "err": share_network_subnet.get('neutron_subnet_id')}) upd['neutron_subnet_id'] = self.subnet if upd: - share_network = self.db.share_network_update( - context, share_network['id'], upd) - return share_network + share_network_subnet = self.db.share_network_subnet_update( + context, share_network_subnet['id'], upd) + return share_network_subnet class NeutronBindNetworkPlugin(NeutronNetworkPlugin): @@ -541,11 +560,11 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin): "ports": inactive_ports} raise exception.NetworkBindException(msg) - def _get_port_create_args(self, share_server, share_network, + def _get_port_create_args(self, share_server, share_network_subnet, device_owner): arguments = super( NeutronBindNetworkPlugin, self)._get_port_create_args( - share_network, share_network, device_owner) + share_server, share_network_subnet, device_owner) arguments['host_id'] = self.config.neutron_host_id arguments['binding:vnic_type'] = self.config.neutron_vnic_type if self.binding_profiles: @@ -561,7 +580,7 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin): "local_link_information": local_links} return arguments - def _save_neutron_network_data(self, context, share_network): + def _save_neutron_network_data(self, context, share_network_subnet): """Store the Neutron network info. In case of dynamic multi segments the segment is determined while @@ -571,36 +590,37 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin): Instead, multi segments network will wait until ports are bound and then store network information (see allocate_network()). """ - if self._is_neutron_multi_segment(share_network): + if self._is_neutron_multi_segment(share_network_subnet): # In case of dynamic multi segment the segment is determined while # binding the port, only mtu is known and already needed - self._save_neutron_network_mtu(context, share_network) + self._save_neutron_network_mtu(context, share_network_subnet) return super(NeutronBindNetworkPlugin, self)._save_neutron_network_data( - context, share_network) + context, share_network_subnet) - def _save_neutron_network_mtu(self, context, share_network): + def _save_neutron_network_mtu(self, context, share_network_subnet): """Store the Neutron network mtu. In case of dynamic multi segments only the mtu needs storing before binding the port. """ net_info = self.neutron_api.get_network( - share_network['neutron_net_id']) + share_network_subnet['neutron_net_id']) mtu_dict = { 'mtu': net_info['mtu'], } - share_network.update(mtu_dict) + share_network_subnet.update(mtu_dict) if self.label != 'admin': - self.db.share_network_update( - context, share_network['id'], mtu_dict) + self.db.share_network_subnet_update( + context, share_network_subnet['id'], mtu_dict) def allocate_network(self, context, share_server, share_network=None, - **kwargs): + share_network_subnet=None, **kwargs): ports = super(NeutronBindNetworkPlugin, self).allocate_network( - context, share_server, share_network, **kwargs) + context, share_server, share_network, share_network_subnet, + **kwargs) # If vnic type is 'normal' we expect a neutron agent to bind the # ports. This action requires a vnic to be spawned by the driver. # Therefore we do not wait for the port binding here, but @@ -609,16 +629,18 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin): # order to update the ports with the correct binding. if self.config.neutron_vnic_type != 'normal': self._wait_for_ports_bind(ports, share_server) - if self._is_neutron_multi_segment(share_network): + if self._is_neutron_multi_segment(share_network_subnet): # update segment information after port bind super(NeutronBindNetworkPlugin, - self)._save_neutron_network_data(context, share_network) + self)._save_neutron_network_data(context, + share_network_subnet) for num, port in enumerate(ports): port_info = { - 'network_type': share_network['network_type'], - 'segmentation_id': share_network['segmentation_id'], - 'cidr': share_network['cidr'], - 'ip_version': share_network['ip_version'], + 'network_type': share_network_subnet['network_type'], + 'segmentation_id': + share_network_subnet['segmentation_id'], + 'cidr': share_network_subnet['cidr'], + 'ip_version': share_network_subnet['ip_version'], } ports[num] = self.db.network_allocation_update( context, port['id'], port_info) diff --git a/manila/network/standalone_network_plugin.py b/manila/network/standalone_network_plugin.py index c6eca27d68..528855451e 100644 --- a/manila/network/standalone_network_plugin.py +++ b/manila/network/standalone_network_plugin.py @@ -264,8 +264,8 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): 'available': len(ips)} raise exception.NetworkBadConfigurationException(reason=msg) - def _save_network_info(self, context, share_network): - """Update share-network with plugin specific data.""" + def _save_network_info(self, context, share_network_subnet): + """Update share-network-subnet with plugin specific data.""" data = { 'network_type': self.network_type, 'segmentation_id': self.segmentation_id, @@ -274,14 +274,15 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): 'ip_version': self.ip_version, 'mtu': self.mtu, } - share_network.update(data) + share_network_subnet.update(data) if self.label != 'admin': - self.db.share_network_update(context, share_network['id'], data) + self.db.share_network_subnet_update( + context, share_network_subnet['id'], data) @utils.synchronized( "allocate_network_for_standalone_network_plugin", external=True) def allocate_network(self, context, share_server, share_network=None, - **kwargs): + share_network_subnet=None, **kwargs): """Allocate network resources using one dedicated network. This one has interprocess lock to avoid concurrency in creation of @@ -289,10 +290,11 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): """ allocation_count = kwargs.get('count', 1) if self.label != 'admin': - self._verify_share_network(share_server['id'], share_network) + self._verify_share_network(share_server['id'], + share_network_subnet) else: - share_network = share_network or {} - self._save_network_info(context, share_network) + share_network_subnet = share_network_subnet or {} + self._save_network_info(context, share_network_subnet) allocations = [] ip_addresses = self._get_available_ips(context, allocation_count) for ip_address in ip_addresses: @@ -301,12 +303,12 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): 'ip_address': ip_address, 'status': constants.STATUS_ACTIVE, 'label': self.label, - 'network_type': share_network['network_type'], - 'segmentation_id': share_network['segmentation_id'], - 'cidr': share_network['cidr'], - 'gateway': share_network['gateway'], - 'ip_version': share_network['ip_version'], - 'mtu': share_network['mtu'], + 'network_type': share_network_subnet['network_type'], + 'segmentation_id': share_network_subnet['segmentation_id'], + 'cidr': share_network_subnet['cidr'], + 'gateway': share_network_subnet['gateway'], + 'ip_version': share_network_subnet['ip_version'], + 'mtu': share_network_subnet['mtu'], } allocations.append( self.db.network_allocation_create(context, data)) @@ -323,12 +325,14 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): self.deallocate_network(context, share_server_id) def manage_network_allocations(self, context, allocations, share_server, - share_network=None): + share_network=None, + share_network_subnet=None): if self.label != 'admin': - self._verify_share_network(share_server['id'], share_network) + self._verify_share_network_subnet(share_server['id'], + share_network_subnet) else: - share_network = share_network or {} - self._save_network_info(context, share_network) + share_network_subnet = share_network_subnet or {} + self._save_network_info(context, share_network_subnet) # We begin matching the allocations to known neutron ports and # finally return the non-consumed allocations @@ -350,12 +354,12 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): 'ip_address': allocation, 'status': constants.STATUS_ACTIVE, 'label': self.label, - 'network_type': share_network['network_type'], - 'segmentation_id': share_network['segmentation_id'], - 'cidr': share_network['cidr'], - 'gateway': share_network['gateway'], - 'ip_version': share_network['ip_version'], - 'mtu': share_network['mtu'], + 'network_type': share_network_subnet['network_type'], + 'segmentation_id': share_network_subnet['segmentation_id'], + 'cidr': share_network_subnet['cidr'], + 'gateway': share_network_subnet['gateway'], + 'ip_version': share_network_subnet['ip_version'], + 'mtu': share_network_subnet['mtu'], } self.db.network_allocation_create(context, data) remaining_allocations.remove(allocation) diff --git a/manila/policies/__init__.py b/manila/policies/__init__.py index 83ee1636c5..943acabee8 100644 --- a/manila/policies/__init__.py +++ b/manila/policies/__init__.py @@ -34,6 +34,7 @@ from manila.policies import share_group_types_spec from manila.policies import share_instance from manila.policies import share_instance_export_location from manila.policies import share_network +from manila.policies import share_network_subnet from manila.policies import share_replica from manila.policies import share_replica_export_location from manila.policies import share_server @@ -70,6 +71,7 @@ def list_rules(): share_replica.list_rules(), share_replica_export_location.list_rules(), share_network.list_rules(), + share_network_subnet.list_rules(), security_service.list_rules(), share_export_location.list_rules(), share_instance.list_rules(), diff --git a/manila/policies/share_network_subnet.py b/manila/policies/share_network_subnet.py new file mode 100644 index 0000000000..abc8f5f39a --- /dev/null +++ b/manila/policies/share_network_subnet.py @@ -0,0 +1,70 @@ +# Copyright 2019 NetApp, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_policy import policy + +from manila.policies import base + +BASE_POLICY_NAME = 'share_network_subnet:%s' + + +share_network_subnet_policies = [ + policy.DocumentedRuleDefault( + name=BASE_POLICY_NAME % 'create', + check_str=base.RULE_DEFAULT, + description="Create a new share network subnet.", + operations=[ + { + 'method': 'POST', + 'path': '/share-networks/{share_network_id}/subnets' + } + ]), + policy.DocumentedRuleDefault( + name=BASE_POLICY_NAME % 'delete', + check_str=base.RULE_DEFAULT, + description="Delete a share network subnet.", + operations=[ + { + 'method': 'DELETE', + 'path': '/share-networks/{share_network_id}/subnets/' + '{share_network_subnet_id}' + } + ]), + policy.DocumentedRuleDefault( + name=BASE_POLICY_NAME % 'show', + check_str=base.RULE_DEFAULT, + description="Shows a share network subnet.", + operations=[ + { + 'method': 'GET', + 'path': '/share-networks/{share_network_id}/subnets/' + '{share_network_subnet_id}' + } + ]), + policy.DocumentedRuleDefault( + name=BASE_POLICY_NAME % 'index', + check_str=base.RULE_DEFAULT, + description="Get all share network subnets.", + operations=[ + { + 'method': 'GET', + 'path': '/share-networks/{share_network_id}/subnets' + } + ]), +] + + +def list_rules(): + return share_network_subnet_policies diff --git a/manila/share/api.py b/manila/share/api.py index ced4346b09..035e51162b 100644 --- a/manila/share/api.py +++ b/manila/share/api.py @@ -66,6 +66,16 @@ class API(base.Base): self.share_rpcapi = share_rpcapi.ShareAPI() self.access_helper = access.ShareInstanceAccess(self.db, None) + def _get_all_availability_zones_with_subnets(self, context, + share_network_id): + compatible_azs = [] + for az in self.db.availability_zone_get_all(context): + if self.db.share_network_subnet_get_by_availability_zone_id( + context, share_network_id=share_network_id, + availability_zone_id=az['id']): + compatible_azs.append(az['name']) + return compatible_azs + def create(self, context, share_proto, size, name, description, snapshot_id=None, availability_zone=None, metadata=None, share_network_id=None, share_type=None, is_public=False, @@ -227,6 +237,25 @@ class API(base.Base): options['source_share_group_snapshot_member_id'] = ( share_group_snapshot_member['id']) + # NOTE(dviroel): If a target availability zone was not provided, the + # scheduler will receive a list with all availability zones that + # contains a subnet within the selected share network. + if share_network_id and not availability_zone: + azs_with_subnet = self._get_all_availability_zones_with_subnets( + context, share_network_id) + if not availability_zones: + availability_zones = azs_with_subnet + else: + availability_zones = ( + [az for az in availability_zones if az in azs_with_subnet]) + if not availability_zones: + msg = _( + "The share network is not supported within any requested " + "availability zone. Check the share type's " + "'availability_zones' extra-spec and the availability " + "zones of the share network subnets") + raise exception.InvalidInput(message=msg) + try: share = self.db.share_create(context, options, create_share_instance=False) @@ -484,11 +513,45 @@ class API(base.Base): 'az': availability_zone} raise exception.InvalidShare(message=msg % payload) + if share_network_id: + if availability_zone: + try: + az = self.db.availability_zone_get(context, + availability_zone) + except exception.AvailabilityZoneNotFound: + msg = _("Share replica cannot be created because the " + "specified availability zone does not exist.") + raise exception.InvalidInput(message=msg) + if self.db.share_network_subnet_get_by_availability_zone_id( + context, share_network_id, az.get('id')) is None: + msg = _("Share replica cannot be created because the " + "share network is not available within the " + "specified availability zone.") + raise exception.InvalidShare(message=msg) + else: + # NOTE(dviroel): If a target availability zone was not + # provided, the scheduler will receive a list with all + # availability zones that contains subnets within the + # selected share network. + azs_subnet = self._get_all_availability_zones_with_subnets( + context, share_network_id) + if not type_azs: + type_azs = azs_subnet + else: + type_azs = ( + [az for az in type_azs if az in azs_subnet]) + if not type_azs: + msg = _( + "The share network is not supported within any " + "requested availability zone. Check the share type's " + "'availability_zones' extra-spec and the availability " + "zones of the share network subnets") + raise exception.InvalidInput(message=msg) + if share['replication_type'] == constants.REPLICATION_TYPE_READABLE: cast_rules_to_readonly = True else: cast_rules_to_readonly = False - request_spec, share_replica = ( self.create_share_instance_and_get_request_spec( context, share, availability_zone=availability_zone, @@ -641,7 +704,9 @@ class API(base.Base): if share_server['status'] != constants.STATUS_ACTIVE: msg = _("Share Server specified is not active.") raise exception.InvalidShareServer(message=msg) - share_data['share_network_id'] = share_server['share_network_id'] + subnet = self.db.share_network_subnet_get( + context, share_server['share_network_subnet_id']) + share_data['share_network_id'] = subnet['share_network_id'] share_data.update({ 'user_id': context.user_id, @@ -1019,7 +1084,7 @@ class API(base.Base): self.share_rpcapi.delete_share_server(context, server) def manage_share_server( - self, context, identifier, host, share_network, driver_opts): + self, context, identifier, host, share_net_subnet, driver_opts): """Manage a share server.""" try: @@ -1037,7 +1102,7 @@ class API(base.Base): values = { 'host': host, - 'share_network_id': share_network['id'], + 'share_network_subnet_id': share_net_subnet['id'], 'status': constants.STATUS_MANAGING, 'is_auto_deletable': False, 'identifier': identifier, diff --git a/manila/share/driver.py b/manila/share/driver.py index ec8611d91e..3e483a5f9f 100644 --- a/manila/share/driver.py +++ b/manila/share/driver.py @@ -843,14 +843,15 @@ class ShareDriver(object): share_server) def allocate_network(self, context, share_server, share_network, - count=None, **kwargs): + share_network_subnet, count=None, **kwargs): """Allocate network resources using given network information.""" if count is None: count = self.get_network_allocations_number() if count: kwargs.update(count=count) self.network_api.allocate_network( - context, share_server, share_network, **kwargs) + context, share_server, share_network=share_network, + share_network_subnet=share_network_subnet, **kwargs) def allocate_admin_network(self, context, share_server, count=None, **kwargs): diff --git a/manila/share/manager.py b/manila/share/manager.py index 75a00144fc..a51a60e871 100644 --- a/manila/share/manager.py +++ b/manila/share/manager.py @@ -23,6 +23,7 @@ import copy import datetime import functools import hashlib +from operator import xor from oslo_config import cfg from oslo_log import log @@ -45,7 +46,7 @@ from manila.message import message_field from manila import quota from manila.share import access from manila.share import api -import manila.share.configuration +from manila.share import configuration from manila.share import drivers_private_data from manila.share import migration from manila.share import rpcapi as share_rpcapi @@ -213,7 +214,7 @@ class ShareManager(manager.SchedulerDependentManager): def __init__(self, share_driver=None, service_name=None, *args, **kwargs): """Load the driver from args, or from flags.""" - self.configuration = manila.share.configuration.Configuration( + self.configuration = configuration.Configuration( share_manager_opts, config_group=service_name) super(ShareManager, self).__init__(service_name='share', @@ -525,13 +526,11 @@ class ShareManager(manager.SchedulerDependentManager): " should be provided. ") raise ValueError(msg) - parent_share_server = None - def error(msg, *args): LOG.error(msg, *args) self.db.share_instance_update(context, share_instance['id'], {'status': constants.STATUS_ERROR}) - + parent_share_server = None if snapshot: parent_share_server_id = ( snapshot['share']['instance']['share_server_id']) @@ -553,9 +552,23 @@ class ShareManager(manager.SchedulerDependentManager): raise exception.InvalidShareServer( share_server_id=parent_share_server ) - - if parent_share_server and not share_network_id: - share_network_id = parent_share_server['share_network_id'] + share_network_subnet_id = None + if share_network_id: + share_network_subnet = ( + self.db.share_network_subnet_get_by_availability_zone_id( + context, share_network_id, + availability_zone_id=share_instance.get( + 'availability_zone_id'))) + if not share_network_subnet: + raise exception.ShareNetworkSubnetNotFound( + share_network_subnet_id=None) + share_network_subnet_id = share_network_subnet['id'] + elif parent_share_server: + share_network_subnet_id = ( + parent_share_server['share_network_subnet_id']) + share_network_subnet = self.db.share_network_subnet_get( + context, share_network_subnet_id) + share_network_id = share_network_subnet['share_network_id'] def get_available_share_servers(): if parent_share_server: @@ -566,7 +579,7 @@ class ShareManager(manager.SchedulerDependentManager): context, self.host, share_network_id) ) - @utils.synchronized("share_manager_%s" % share_network_id, + @utils.synchronized("share_manager_%s" % share_network_subnet_id, external=True) def _wrapped_provide_share_server_for_share(): try: @@ -595,7 +608,7 @@ class ShareManager(manager.SchedulerDependentManager): context, { 'host': self.host, - 'share_network_id': share_network_id, + 'share_network_subnet_id': share_network_subnet_id, 'status': constants.STATUS_CREATING, } ) @@ -689,7 +702,9 @@ class ShareManager(manager.SchedulerDependentManager): return share_server['id'] - def _provide_share_server_for_share_group(self, context, share_network_id, + def _provide_share_server_for_share_group(self, context, + share_network_id, + share_network_subnet_id, share_group_ref, share_group_snapshot=None): """Gets or creates share_server and updates share group with its id. @@ -707,6 +722,10 @@ class ShareManager(manager.SchedulerDependentManager): :param context: Current context :param share_network_id: Share network where existing share server should be found or created. + :param share_network_subnet_id: Share network subnet where + existing share server should be found + or created. If not specified, the + default subnet will be used. :param share_group_ref: Share Group model :param share_group_snapshot: Optional -- ShareGroupSnapshot model. If supplied, driver will use it to choose @@ -757,7 +776,7 @@ class ShareManager(manager.SchedulerDependentManager): context, { 'host': self.host, - 'share_network_id': share_network_id, + 'share_network_subnet_id': share_network_subnet_id, 'status': constants.STATUS_CREATING } ) @@ -1831,7 +1850,6 @@ class ShareManager(manager.SchedulerDependentManager): request_spec=None, filter_properties=None): """Create a share replica.""" context = context.elevated() - share_replica = self.db.share_replica_get( context, share_replica_id, with_share_data=True, with_share_server=True) @@ -1847,7 +1865,6 @@ class ShareManager(manager.SchedulerDependentManager): self.db.share_replicas_get_available_active_replica( context, share_replica['share_id'], with_share_data=True, with_share_server=True)) - if not _active_replica: self.db.share_replica_update( context, share_replica['id'], @@ -1868,9 +1885,8 @@ class ShareManager(manager.SchedulerDependentManager): # We need the share_network_id in case of # driver_handles_share_server=True share_network_id = share_replica.get('share_network_id', None) - - if (share_network_id and - not self.driver.driver_handles_share_servers): + if xor(bool(share_network_id), + self.driver.driver_handles_share_servers): self.db.share_replica_update( context, share_replica['id'], {'status': constants.STATUS_ERROR, @@ -1883,8 +1899,8 @@ class ShareManager(manager.SchedulerDependentManager): resource_id=share_replica['id'], detail=message_field.Detail.UNEXPECTED_NETWORK) raise exception.InvalidDriverMode( - "Driver does not expect share-network to be provided " - "with current configuration.") + "The share-network value provided does not match with the " + "current driver configuration.") if share_network_id: try: @@ -2608,12 +2624,14 @@ class ShareManager(manager.SchedulerDependentManager): msg = ("Since share %(share)s has been un-managed from share " "server %(server)s. This share server must be removed " "manually, either by un-managing or by deleting it. The " - "share network %(network)s cannot be deleted unless this " - "share server has been removed.") + "share network subnet %(subnet)s and share network " + "%(network) cannot be deleted unless this share server has " + "been removed.") msg_args = { 'share': share_id, 'server': share_server['id'], - 'network': share_server['share_network_id'] + 'subnet': share_server['share_network_subnet_id'], + 'network': share_instance['share_network_id'] } LOG.warning(msg, msg_args) @@ -2698,10 +2716,11 @@ class ShareManager(manager.SchedulerDependentManager): server = self.db.share_server_get(context, share_server_id) - share_network = self.db.share_network_get( - context, server['share_network_id']) - try: + share_network_subnet = self.db.share_network_subnet_get( + context, server['share_network_subnet_id']) + share_network = self.db.share_network_get( + context, share_network_subnet['share_network_id']) number_allocations = ( self.driver.get_network_allocations_number()) @@ -2733,7 +2752,7 @@ class ShareManager(manager.SchedulerDependentManager): self.driver.network_api. manage_network_allocations( context, remaining_allocations, server, - share_network)) + share_network, share_network_subnet)) # We require that all allocations are managed, else we # may have problems deleting this share server @@ -3578,7 +3597,8 @@ class ShareManager(manager.SchedulerDependentManager): self._report_driver_status(context) self._publish_service_capabilities(context) - def _form_server_setup_info(self, context, share_server, share_network): + def _form_server_setup_info(self, context, share_server, share_network, + share_network_subnet): # Network info is used by driver for setting up share server # and getting server info on share creation. network_allocations = self.db.network_allocations_get_for_share_server( @@ -3592,30 +3612,34 @@ class ShareManager(manager.SchedulerDependentManager): # They should be removed right after no one uses them. network_info = { 'server_id': share_server['id'], - 'segmentation_id': share_network['segmentation_id'], - 'cidr': share_network['cidr'], - 'neutron_net_id': share_network['neutron_net_id'], - 'neutron_subnet_id': share_network['neutron_subnet_id'], + 'segmentation_id': share_network_subnet['segmentation_id'], + 'cidr': share_network_subnet['cidr'], + 'neutron_net_id': share_network_subnet['neutron_net_id'], + 'neutron_subnet_id': share_network_subnet['neutron_subnet_id'], 'security_services': share_network['security_services'], 'network_allocations': network_allocations, 'admin_network_allocations': admin_network_allocations, 'backend_details': share_server.get('backend_details'), - 'network_type': share_network['network_type'], + 'network_type': share_network_subnet['network_type'], } return network_info def _setup_server(self, context, share_server, metadata=None): try: + share_network_subnet = share_server['share_network_subnet'] + share_network_subnet_id = share_network_subnet['id'] + share_network_id = share_network_subnet['share_network_id'] share_network = self.db.share_network_get( - context, share_server['share_network_id']) - self.driver.allocate_network(context, share_server, share_network) + context, share_network_id) + self.driver.allocate_network(context, share_server, share_network, + share_network_subnet) self.driver.allocate_admin_network(context, share_server) - # Get share_network again in case it was updated. - share_network = self.db.share_network_get( - context, share_server['share_network_id']) + # Get share_network_subnet in case it was updated. + share_network_subnet = self.db.share_network_subnet_get( + context, share_network_subnet_id) network_info = self._form_server_setup_info( - context, share_server, share_network) + context, share_server, share_network, share_network_subnet) self._validate_segmentation_id(network_info) # NOTE(vponomaryov): Save security services data to share server @@ -3735,7 +3759,7 @@ class ShareManager(manager.SchedulerDependentManager): def delete_share_server(self, context, share_server): @utils.synchronized( - "share_manager_%s" % share_server['share_network_id']) + "share_manager_%s" % share_server['share_network_subnet_id']) def _wrapped_delete_share_server(): # NOTE(vponomaryov): Verify that there are no dependent shares. # Without this verification we can get here exception in next case: @@ -3944,7 +3968,8 @@ class ShareManager(manager.SchedulerDependentManager): if parent_share_server_id and self.driver.driver_handles_share_servers: share_server = self.db.share_server_get(context, parent_share_server_id) - share_network_id = share_server['share_network_id'] + share_network_subnet = share_server['share_network_subnet'] + share_network_id = share_network_subnet['share_network_id'] if share_network_id and not self.driver.driver_handles_share_servers: self.db.share_group_update( @@ -3954,10 +3979,17 @@ class ShareManager(manager.SchedulerDependentManager): raise exception.InvalidInput(reason=msg) if not share_server and share_network_id: + + availability_zone_id = self._get_az_for_share_group( + context, share_group_ref) + subnet = self.db.share_network_subnet_get_by_availability_zone_id( + context, share_network_id, availability_zone_id) + try: share_server, share_group_ref = ( self._provide_share_server_for_share_group( - context, share_network_id, share_group_ref, + context, share_network_id, subnet.get('id'), + share_group_ref, share_group_snapshot=snap_ref, ) ) diff --git a/manila/tests/api/fakes.py b/manila/tests/api/fakes.py index e150c7f662..fd6336d56a 100644 --- a/manila/tests/api/fakes.py +++ b/manila/tests/api/fakes.py @@ -270,20 +270,6 @@ fixture_valid_reset_status_body = ( ({'reset_status': {'status': 'migrating_to'}}, '2.7'), ) -share_network_id = '5dfe0898-e2a1-4740-9177-81c7d26713b0' -share_network = { - 'name': 'share-net-fake', - 'share_network_id': '5dfe0898-e2a1-4740-9177-81c7d26713b0', - 'project_id': '5dfe0898-e2a1-4740-9177-81c7d26713b0' -} -SHARE_SERVER = { - 'share_network': share_network, - 'share_network_id': 'c5b3a865-56d0-4d88-abe5-879965e099c9', - 'host': host, - 'id': 'c39bb9ae-16a5-40f2-a24f-1cf3f549d3d3', - 'status': constants.STATUS_ACTIVE -} - def mock_fake_admin_check(context, resource_name, action, *args, **kwargs): if context.is_admin: diff --git a/manila/tests/api/openstack/test_wsgi.py b/manila/tests/api/openstack/test_wsgi.py index 3332b08423..d222ab8539 100644 --- a/manila/tests/api/openstack/test_wsgi.py +++ b/manila/tests/api/openstack/test_wsgi.py @@ -150,14 +150,14 @@ class RequestTest(test.TestCase): mock.Mock(return_value=max_version)) self.mock_object(api_version, 'min_api_version', mock.Mock(return_value=min_version)) - headers = {'X-OpenStack-Manila-API-Version': '2.50'} + headers = {'X-OpenStack-Manila-API-Version': '2.51'} request = wsgi.Request.blank( 'https://openstack.acme.com/v2/shares', method='GET', headers=headers, script_name='/v2/shares') self.assertRaises(exception.InvalidGlobalAPIVersion, request.set_api_version_request) - self.assertEqual(api_version.APIVersionRequest('2.50'), + self.assertEqual(api_version.APIVersionRequest('2.51'), request.api_version_request) @ddt.data('', '/share', '/v1', '/v2/shares', '/v1.1/', '/share/v1', diff --git a/manila/tests/api/test_common.py b/manila/tests/api/test_common.py index 5bb595b2ff..be386d2a94 100644 --- a/manila/tests/api/test_common.py +++ b/manila/tests/api/test_common.py @@ -328,6 +328,21 @@ class MiscFunctionsTest(test.TestCase): policy.check_policy.assert_called_once_with( 'fake_context', 'share', 'set_public_share', do_raise=False) + @ddt.data(({}, True), + ({'neutron_net_id': 'fake_nn_id'}, False), + ({'neutron_subnet_id': 'fake_sn_id'}, False), + ({'neutron_net_id': 'fake_nn_id', + 'neutron_subnet_id': 'fake_sn_id'}, True)) + @ddt.unpack + def test__check_net_id_and_subnet_id(self, body, expected): + if not expected: + self.assertRaises(webob.exc.HTTPBadRequest, + common.check_net_id_and_subnet_id, + body) + else: + result = common.check_net_id_and_subnet_id(body) + self.assertIsNone(result) + @ddt.ddt class ViewBuilderTest(test.TestCase): diff --git a/manila/tests/api/v1/test_security_service.py b/manila/tests/api/v1/test_security_service.py index 5dce4b8745..11709e5d7a 100644 --- a/manila/tests/api/v1/test_security_service.py +++ b/manila/tests/api/v1/test_security_service.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ddt import mock from six.moves.urllib import parse import webob @@ -25,6 +26,7 @@ from manila import test from manila.tests.api import fakes +@ddt.ddt class ShareApiTest(test.TestCase): """Share Api Test.""" def setUp(self): @@ -81,6 +83,21 @@ class ShareApiTest(test.TestCase): }, ] } + self.fake_share_network_list_with_share_servers = [{ + 'id': 'fake_sn_id', + 'share_network_subnets': [{ + 'id': 'fake_sns_id', + 'share_servers': [{'id': 'fake_ss_id'}] + }] + }] + self.fake_share_network_list_without_share_servers = [{ + 'id': 'fake_sn_id', + 'share_network_subnets': [{ + 'id': 'fake_sns_id', + 'share_servers': [] + }] + }] + def _stop_started_patcher(self, patcher): if hasattr(patcher, 'is_local'): patcher.stop() @@ -163,10 +180,11 @@ class ShareApiTest(test.TestCase): self.mock_object(security_service.policy, 'check_policy') db.security_service_get = mock.Mock(return_value=new) db.security_service_update = mock.Mock(return_value=updated) + fake_sns = {'id': 'fake_sns_id', 'share_servers': ['fake_ss']} db.share_network_get_all_by_security_service = mock.Mock( return_value=[{ 'id': 'fake_id', - 'share_servers': 'fake_share_server' + 'share_network_subnets': [fake_sns] }]) body = {"security_service": {"name": "new"}} req = fakes.HTTPRequest.blank('/security_service/1') @@ -187,10 +205,11 @@ class ShareApiTest(test.TestCase): self.mock_object(security_service.policy, 'check_policy') db.security_service_get = mock.Mock(return_value=new) db.security_service_update = mock.Mock(return_value=updated) + fake_sns = {'id': 'fake_sns_id', 'share_servers': ['fake_ss']} db.share_network_get_all_by_security_service = mock.Mock( return_value=[{ 'id': 'fake_id', - 'share_servers': 'fake_share_server' + 'share_network_subnets': [fake_sns] }]) body = {"security_service": {"description": "new"}} req = fakes.HTTPRequest.blank('/security_service/1') @@ -209,8 +228,9 @@ class ShareApiTest(test.TestCase): mock.Mock()) def test_security_service_update_invalid_keys_sh_server_exists(self): self.mock_object(security_service.policy, 'check_policy') + fake_sns = {'id': 'fake_sns_id', 'share_servers': ['fake_ss']} db.share_network_get_all_by_security_service.return_value = [ - {'id': 'fake_id', 'share_servers': 'fake_share_servers'}, + {'id': 'fake_id', 'share_network_subnets': [fake_sns]}, ] db.security_service_get.return_value = self.ss_active_directory.copy() body = {'security_service': {'user_id': 'new_user'}} @@ -234,8 +254,9 @@ class ShareApiTest(test.TestCase): mock.Mock()) def test_security_service_update_valid_keys_sh_server_exists(self): self.mock_object(security_service.policy, 'check_policy') + fake_sns = {'id': 'fake_sns_id', 'share_servers': ['fake_ss']} db.share_network_get_all_by_security_service.return_value = [ - {'id': 'fake_id', 'share_servers': 'fake_share_servers'}, + {'id': 'fake_id', 'share_network_subnets': [fake_sns]}, ] old = self.ss_active_directory.copy() updated = self.ss_active_directory.copy() @@ -265,6 +286,36 @@ class ShareApiTest(test.TestCase): security_service.RESOURCE_NAME, 'update', old) ]) + @mock.patch.object(db, 'security_service_get', mock.Mock()) + def test_security_service_update_has_share_servers(self): + db.security_service_get = mock.Mock() + self.mock_object( + self.controller, '_share_servers_dependent_on_sn_exist', + mock.Mock(return_value=True)) + body = {"security_service": {"type": "ldap"}} + + req = fakes.HTTPRequest.blank('/security_services/1') + self.assertRaises(webob.exc.HTTPForbidden, + self.controller.update, + req, + 1, + body) + + @ddt.data(True, False) + def test_security_service_update_share_server_dependent_exists(self, + expected): + req = fakes.HTTPRequest.blank('/security_services/1') + context = req.environ['manila.context'] + db.security_service_get = mock.Mock() + network = (self.fake_share_network_list_with_share_servers if expected + else self.fake_share_network_list_without_share_servers) + db.share_network_get_all_by_security_service = mock.Mock( + return_value=network) + + result = self.controller._share_servers_dependent_on_sn_exist( + context, 'fake_id') + self.assertEqual(expected, result) + def test_security_service_list(self): db.security_service_get_all_by_project = mock.Mock( return_value=[self.ss_active_directory.copy()]) diff --git a/manila/tests/api/v1/test_share_servers.py b/manila/tests/api/v1/test_share_servers.py index 1cbaaca9aa..e7ac003b1f 100644 --- a/manila/tests/api/v1/test_share_servers.py +++ b/manila/tests/api/v1/test_share_servers.py @@ -13,9 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. +import ddt import mock from webob import exc +from manila.api.openstack import api_version_request as api_version from manila.api.v1 import share_servers from manila.common import constants from manila import context @@ -31,23 +33,43 @@ fake_share_server_list = { 'status': constants.STATUS_ACTIVE, 'updated_at': None, 'host': 'fake_host', - 'share_network_id': 'fake_sn_id', 'share_network_name': 'fake_sn_name', + 'share_network_id': 'fake_sn_id', + 'share_network_subnet_id': 'fake_sns_id', 'project_id': 'fake_project_id', 'id': 'fake_server_id', + 'is_auto_deletable': False, + 'identifier': 'fake_id' }, { 'status': constants.STATUS_ERROR, 'updated_at': None, 'host': 'fake_host_2', - 'share_network_id': 'fake_sn_id_2', 'share_network_name': 'fake_sn_id_2', + 'share_network_id': 'fake_sn_id_2', + 'share_network_subnet_id': 'fake_sns_id_2', 'project_id': 'fake_project_id_2', 'id': 'fake_server_id_2', + 'is_auto_deletable': True, + 'identifier': 'fake_id_2' }, ] } +fake_share_network_get_list = { + 'share_networks': [ + { + 'name': 'fake_sn_name', + 'id': 'fake_sn_id', + 'project_id': 'fake_project_id', + }, + { + 'name': None, + 'id': 'fake_sn_id_2', + 'project_id': 'fake_project_id_2', + } + ] +} fake_share_server_get_result = { 'share_server': { @@ -57,12 +79,15 @@ fake_share_server_get_result = { 'host': 'fake_host', 'share_network_name': 'fake_sn_name', 'share_network_id': 'fake_sn_id', + 'share_network_subnet_id': 'fake_sns_id', 'project_id': 'fake_project_id', 'id': 'fake_server_id', 'backend_details': { 'fake_key_1': 'fake_value_1', 'fake_key_2': 'fake_value_2', - } + }, + 'is_auto_deletable': False, + 'identifier': 'fake_id' } } @@ -88,13 +113,14 @@ class FakeShareServer(object): self.created_at = kwargs.get('created_at', None) self.updated_at = kwargs.get('updated_at', None) self.host = kwargs.get('host', 'fake_host') - self.share_network = kwargs.get('share_network', { - 'name': 'fake_sn_name', 'id': 'fake_sn_id', - 'project_id': 'fake_project_id'}) - self.share_network_id = kwargs.get('share_network_id', - self.share_network['id']) + self.share_network_subnet = kwargs.get('share_network_subnet', { + 'id': 'fake_sns_id', 'share_network_id': 'fake_sn_id'}) + self.share_network_subnet_id = kwargs.get( + 'share_network_subnet_id', self.share_network_subnet['id']) self.status = kwargs.get('status', constants.STATUS_ACTIVE) - self.project_id = self.share_network['project_id'] + self.project_id = 'fake_project_id' + self.identifier = kwargs.get('identifier', 'fake_id') + self.is_auto_deletable = kwargs.get('is_auto_deletable', False) self.backend_details = share_server_backend_details def __getitem__(self, item): @@ -106,10 +132,12 @@ def fake_share_server_get_all(): FakeShareServer(), FakeShareServer(id='fake_server_id_2', host='fake_host_2', - share_network={ - 'name': None, - 'id': 'fake_sn_id_2', - 'project_id': 'fake_project_id_2'}, + share_network_subnet={ + 'id': 'fake_sns_id_2', + 'share_network_id': 'fake_sn_id_2', + }, + identifier='fake_id_2', + is_auto_deletable=True, status=constants.STATUS_ERROR) ] return fake_share_servers @@ -136,15 +164,10 @@ class FakeRequestWithProjectId(FakeRequestAdmin): GET = {'project_id': fake_share_server_get_all()[0].project_id} -class FakeRequestWithShareNetworkName(FakeRequestAdmin): +class FakeRequestWithShareNetworkSubnetId(FakeRequestAdmin): GET = { - 'share_network': fake_share_server_get_all()[0].share_network['name'], - } - - -class FakeRequestWithShareNetworkId(FakeRequestAdmin): - GET = { - 'share_network': fake_share_server_get_all()[0].share_network['id'], + 'share_network_subnet_id': + fake_share_server_get_all()[0].share_network_subnet_id, } @@ -152,6 +175,7 @@ class FakeRequestWithFakeFilter(FakeRequestAdmin): GET = {'fake_key': 'fake_value'} +@ddt.ddt class ShareServerAPITest(test.TestCase): def setUp(self): @@ -163,15 +187,20 @@ class ShareServerAPITest(test.TestCase): self.mock_object(db_api, 'share_server_get_all', mock.Mock(return_value=fake_share_server_get_all())) - def _prepare_request(self, url, use_admin_context): + def _prepare_request(self, url, use_admin_context, + version=api_version._MAX_API_VERSION): request = fakes.HTTPRequest.blank(url, - use_admin_context=use_admin_context) + use_admin_context=use_admin_context, + version=version) ctxt = request.environ['manila.context'] return request, ctxt def test_index_no_filters(self): request, ctxt = self._prepare_request(url='/v2/share-servers/', use_admin_context=True) + self.mock_object(db_api, 'share_network_get', mock.Mock( + side_effect=[fake_share_network_get_list['share_networks'][0], + fake_share_network_get_list['share_networks'][1]])) result = self.controller.index(request) policy.check_policy.assert_called_once_with( ctxt, self.resource_name, 'index') @@ -183,6 +212,9 @@ class ShareServerAPITest(test.TestCase): url='/index?host=%s' % fake_share_server_list['share_servers'][0]['host'], use_admin_context=True) + self.mock_object(db_api, 'share_network_get', mock.Mock( + side_effect=[fake_share_network_get_list['share_networks'][0], + fake_share_network_get_list['share_networks'][1]])) result = self.controller.index(request) policy.check_policy.assert_called_once_with( ctxt, self.resource_name, 'index') @@ -194,6 +226,9 @@ class ShareServerAPITest(test.TestCase): request, ctxt = self._prepare_request(url='/index?status=%s' % constants.STATUS_ERROR, use_admin_context=True) + self.mock_object(db_api, 'share_network_get', mock.Mock( + side_effect=[fake_share_network_get_list['share_networks'][0], + fake_share_network_get_list['share_networks'][1]])) result = self.controller.index(request) policy.check_policy.assert_called_once_with( ctxt, self.resource_name, 'index') @@ -206,10 +241,14 @@ class ShareServerAPITest(test.TestCase): url='/index?project_id=%s' % fake_share_server_get_all()[0].project_id, use_admin_context=True) + self.mock_object(db_api, 'share_network_get', mock.Mock( + side_effect=[fake_share_network_get_list['share_networks'][0], + fake_share_network_get_list['share_networks'][1]])) result = self.controller.index(request) policy.check_policy.assert_called_once_with( ctxt, self.resource_name, 'index') db_api.share_server_get_all.assert_called_once_with(ctxt) + self.assertEqual([fake_share_server_list['share_servers'][0]], result['share_servers']) @@ -218,6 +257,9 @@ class ShareServerAPITest(test.TestCase): url='/index?host=%s' % fake_share_server_list['share_servers'][0]['host'], use_admin_context=True) + self.mock_object(db_api, 'share_network_get', mock.Mock( + side_effect=[fake_share_network_get_list['share_networks'][0], + fake_share_network_get_list['share_networks'][1]])) result = self.controller.index(request) policy.check_policy.assert_called_once_with( ctxt, self.resource_name, 'index') @@ -228,8 +270,11 @@ class ShareServerAPITest(test.TestCase): def test_index_share_network_filter_by_id(self): request, ctxt = self._prepare_request( url='/index?share_network=%s' - % fake_share_server_get_all()[0].share_network['id'], + % fake_share_network_get_list['share_networks'][0]['id'], use_admin_context=True) + self.mock_object(db_api, 'share_network_get', mock.Mock( + side_effect=[fake_share_network_get_list['share_networks'][0], + fake_share_network_get_list['share_networks'][1]])) result = self.controller.index(request) policy.check_policy.assert_called_once_with( ctxt, self.resource_name, 'index') @@ -240,16 +285,57 @@ class ShareServerAPITest(test.TestCase): def test_index_fake_filter(self): request, ctxt = self._prepare_request(url='/index?fake_key=fake_value', use_admin_context=True) + self.mock_object(db_api, 'share_network_get', mock.Mock( + side_effect=[fake_share_network_get_list['share_networks'][0], + fake_share_network_get_list['share_networks'][1]])) result = self.controller.index(request) policy.check_policy.assert_called_once_with( ctxt, self.resource_name, 'index') db_api.share_server_get_all.assert_called_once_with(ctxt) self.assertEqual(0, len(result['share_servers'])) + def test_index_share_network_not_found(self): + request, ctxt = self._prepare_request( + url='/index?identifier=%s' + % fake_share_server_get_all()[0].identifier, + use_admin_context=True) + self.mock_object( + db_api, 'share_network_get', + mock.Mock(side_effect=exception.ShareNetworkNotFound( + share_network_id='fake'))) + + result = self.controller.index(request) + db_api.share_server_get_all.assert_called_once_with(ctxt) + policy.check_policy.assert_called_once_with( + ctxt, self.resource_name, 'index') + exp_share_server = fake_share_server_list['share_servers'][0].copy() + exp_share_server['project_id'] = '' + exp_share_server['share_network_name'] = '' + self.assertEqual([exp_share_server], + result['share_servers']) + + def test_index_share_network_not_found_filter_project(self): + request, ctxt = self._prepare_request( + url='/index?project_id=%s' + % fake_share_server_get_all()[0].project_id, + use_admin_context=True) + self.mock_object( + db_api, 'share_network_get', + mock.Mock(side_effect=exception.ShareNetworkNotFound( + share_network_id='fake'))) + + result = self.controller.index(request) + db_api.share_server_get_all.assert_called_once_with(ctxt) + policy.check_policy.assert_called_once_with( + ctxt, self.resource_name, 'index') + self.assertEqual(0, len(result['share_servers'])) + def test_show(self): self.mock_object(db_api, 'share_server_get', mock.Mock(return_value=fake_share_server_get())) request, ctxt = self._prepare_request('/show', use_admin_context=True) + self.mock_object(db_api, 'share_network_get', mock.Mock( + return_value=fake_share_network_get_list['share_networks'][0])) result = self.controller.show( request, fake_share_server_get_result['share_server']['id']) @@ -260,6 +346,36 @@ class ShareServerAPITest(test.TestCase): self.assertEqual(fake_share_server_get_result['share_server'], result['share_server']) + @ddt.data( + {'share_server_side_effect': exception.ShareServerNotFound( + share_server_id="foo"), + 'share_net_side_effect': mock.Mock()}, + {'share_server_side_effect': mock.Mock( + return_value=fake_share_server_get()), + 'share_net_side_effect': exception.ShareNetworkNotFound( + share_network_id="foo")}) + @ddt.unpack + def test_show_server_not_found(self, share_server_side_effect, + share_net_side_effect): + self.mock_object(db_api, 'share_server_get', + mock.Mock(side_effect=share_server_side_effect)) + request, ctxt = self._prepare_request('/show', use_admin_context=True) + self.mock_object(db_api, 'share_network_get', mock.Mock( + side_effect=share_net_side_effect)) + self.assertRaises( + exc.HTTPNotFound, self.controller.show, request, + fake_share_server_get_result['share_server']['id']) + + policy.check_policy.assert_called_once_with( + ctxt, self.resource_name, 'show') + db_api.share_server_get.assert_called_once_with( + ctxt, fake_share_server_get_result['share_server']['id']) + if isinstance(share_net_side_effect, exception.ShareNetworkNotFound): + exp_share_net_id = (fake_share_server_get() + .share_network_subnet['share_network_id']) + db_api.share_network_get.assert_called_once_with( + ctxt, exp_share_net_id) + def test_details(self): self.mock_object(db_api, 'share_server_get', mock.Mock(return_value=fake_share_server_get())) @@ -274,6 +390,21 @@ class ShareServerAPITest(test.TestCase): self.assertEqual(fake_share_server_backend_details_get_result, result) + def test_details_share_server_not_found(self): + share_server_id = 'fake' + self.mock_object( + db_api, 'share_server_get', + mock.Mock(side_effect=exception.ShareServerNotFound( + share_server_id=share_server_id))) + self.assertRaises(exc.HTTPNotFound, + self.controller.details, + FakeRequestAdmin, + share_server_id) + policy.check_policy.assert_called_once_with( + CONTEXT, self.resource_name, 'details') + db_api.share_server_get.assert_called_once_with( + CONTEXT, share_server_id) + def test_delete_active_server(self): share_server = FakeShareServer(status=constants.STATUS_ACTIVE) self.mock_object(db_api, 'share_server_get', diff --git a/manila/tests/api/v1/test_shares.py b/manila/tests/api/v1/test_shares.py index cc98089f81..b63efab43b 100644 --- a/manila/tests/api/v1/test_shares.py +++ b/manila/tests/api/v1/test_shares.py @@ -239,6 +239,9 @@ class ShareAPITest(test.TestCase): self.mock_object(share_api.API, 'create', create_mock) self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value={'id': 'fakenetid'})) + self.mock_object( + db, 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(return_value={'id': 'fakesubnetid'})) body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/shares') @@ -249,6 +252,7 @@ class ShareAPITest(test.TestCase): expected = self._get_expected_share_detailed_response(shr) expected['share'].pop('snapshot_support') self.assertEqual(expected, res_dict) + # pylint: disable=unsubscriptable-object self.assertEqual("fakenetid", create_mock.call_args[1]['share_network_id']) @@ -312,6 +316,8 @@ class ShareAPITest(test.TestCase): return_value=parent_share)) self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value={'id': parent_share_net})) + self.mock_object( + db, 'share_network_subnet_get_by_availability_zone_id') body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/shares') @@ -323,6 +329,7 @@ class ShareAPITest(test.TestCase): expected = self._get_expected_share_detailed_response(shr) expected['share'].pop('snapshot_support') self.assertEqual(expected, res_dict) + # pylint: disable=unsubscriptable-object self.assertEqual(parent_share_net, create_mock.call_args[1]['share_network_id']) @@ -356,6 +363,8 @@ class ShareAPITest(test.TestCase): return_value=parent_share)) self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value={'id': parent_share_net})) + self.mock_object( + db, 'share_network_subnet_get_by_availability_zone_id') body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/shares') @@ -367,6 +376,7 @@ class ShareAPITest(test.TestCase): expected = self._get_expected_share_detailed_response(shr) expected['share'].pop('snapshot_support') self.assertEqual(expected, res_dict) + # pylint: disable=unsubscriptable-object self.assertEqual(parent_share_net, create_mock.call_args[1]['share_network_id']) @@ -425,6 +435,8 @@ class ShareAPITest(test.TestCase): return_value=parent_share)) self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value={'id': parent_share_net})) + self.mock_object( + db, 'share_network_subnet_get_by_availability_zone_id') body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/shares', version=microversion) @@ -436,6 +448,7 @@ class ShareAPITest(test.TestCase): expected = self._get_expected_share_detailed_response(shr) expected['share'].pop('snapshot_support') self.assertDictEqual(expected, res_dict) + # pylint: disable=unsubscriptable-object self.assertEqual(parent_share_net, create_mock.call_args[1]['share_network_id']) @@ -478,6 +491,28 @@ class ShareAPITest(test.TestCase): req, body) + @ddt.data((exception.ShareNetworkNotFound(share_network_id='fake'), + webob.exc.HTTPNotFound), + (mock.Mock(), webob.exc.HTTPBadRequest)) + @ddt.unpack + def test_share_create_invalid_subnet(self, share_network_side_effect, + exception_to_raise): + fake_share_with_sn = copy.deepcopy(self.share) + fake_share_with_sn['share_network_id'] = 'fakenetid' + self.mock_object(db, 'share_network_get', + mock.Mock(side_effect=share_network_side_effect)) + self.mock_object( + db, 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(return_value=None)) + + body = {"share": fake_share_with_sn} + + req = fakes.HTTPRequest.blank('/shares') + self.assertRaises(exception_to_raise, + self.controller.create, + req, + body) + def test_share_show(self): req = fakes.HTTPRequest.blank('/shares/1') expected = self._get_expected_share_detailed_response() diff --git a/manila/tests/api/v2/test_share_network_subnets.py b/manila/tests/api/v2/test_share_network_subnets.py new file mode 100644 index 0000000000..2496ee8c1f --- /dev/null +++ b/manila/tests/api/v2/test_share_network_subnets.py @@ -0,0 +1,481 @@ +# Copyright 2019 NetApp, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy +import ddt +import mock +from oslo_db import exception as db_exception + +from manila.api import common +from manila.api.v2 import share_network_subnets +from manila.db import api as db_api +from manila import exception +from manila import policy +from manila import test +from manila.tests.api import fakes +from manila.tests import db_utils +from webob import exc + +fake_az = { + 'id': 'ae525e12-07e8-4ddc-a2fd-4a89ad4a65ff', + 'name': 'fake_az_name' +} + +fake_default_subnet = { + 'neutron_net_id': 'fake_nn_id', + 'neutron_subnet_id': 'fake_nsn_id', + 'availability_zone_id': None +} + +fake_subnet_with_az = { + 'neutron_net_id': 'fake_nn_id', + 'neutron_subnet_id': 'fake_nsn_id', + 'availability_zone_id': 'fake_az_id' +} + + +@ddt.ddt +class ShareNetworkSubnetControllerTest(test.TestCase): + """Share network subnet api test""" + + def setUp(self): + super(ShareNetworkSubnetControllerTest, self).setUp() + self.controller = share_network_subnets.ShareNetworkSubnetController() + self.mock_policy_check = self.mock_object( + policy, 'check_policy', mock.Mock(return_value=True)) + self.resource_name = self.controller.resource_name + self.mock_az_get = self.mock_object(db_api, 'availability_zone_get', + mock.Mock(return_value=fake_az)) + self.share_network = db_utils.create_share_network( + name='fake_network', id='fake_sn_id') + self.share_server = db_utils.create_share_server( + share_network_subnet_id='fake_sns_id') + self.subnet = db_utils.create_share_network_subnet( + share_network_id=self.share_network['id']) + self.share = db_utils.create_share() + + def test_share_network_subnet_delete(self): + req = fakes.HTTPRequest.blank('/subnets/%s' % self.subnet['id'], + version="2.51") + context = req.environ['manila.context'] + + mock_sns_get = self.mock_object( + db_api, 'share_network_subnet_get', + mock.Mock(return_value=self.subnet)) + mock_all_get_all_shares_by_ss = self.mock_object( + db_api, 'share_instances_get_all_by_share_server', + mock.Mock(return_value=[])) + mock_all_ss_are_auto_deletable = self.mock_object( + self.controller, '_all_share_servers_are_auto_deletable', + mock.Mock(return_value=True)) + mock_delete_share_server = self.mock_object( + self.controller.share_rpcapi, 'delete_share_server') + mock_subnet_delete = self.mock_object(db_api, + 'share_network_subnet_delete') + + result = self.controller.delete(req, self.share_network['id'], + self.subnet['id']) + + self.assertEqual(202, result.status_int) + mock_sns_get.assert_called_once_with( + context, self.subnet['id']) + mock_all_get_all_shares_by_ss.assert_called_once_with( + context, self.subnet['share_servers'][0].id + ) + mock_all_ss_are_auto_deletable.assert_called_once_with( + self.subnet) + mock_delete_share_server.assert_called_once_with( + context, self.subnet['share_servers'][0]) + mock_subnet_delete.assert_called_once_with( + context, self.subnet['id']) + policy.check_policy.assert_called_once_with( + context, self.resource_name, 'delete') + + def test_share_network_subnet_delete_network_not_found(self): + req = fakes.HTTPRequest.blank('/subnets/%s' % self.subnet['id'], + version="2.51") + context = req.environ['manila.context'] + + mock_sn_get = self.mock_object( + db_api, 'share_network_get', + mock.Mock(side_effect=exception.ShareNetworkNotFound( + share_network_id=self.share_network['id'] + ))) + + self.assertRaises(exc.HTTPNotFound, + self.controller.delete, + req, + self.share_network['id'], + self.subnet['id']) + mock_sn_get.assert_called_once_with( + context, self.share_network['id']) + self.mock_policy_check.assert_called_once_with( + context, self.resource_name, 'delete') + + def test_share_network_subnet_delete_subnet_not_found(self): + req = fakes.HTTPRequest.blank('/subnets/%s' % self.subnet['id'], + version="2.51") + context = req.environ['manila.context'] + + mock_sns_get = self.mock_object( + db_api, 'share_network_subnet_get', + mock.Mock(side_effect=exception.ShareNetworkSubnetNotFound( + share_network_subnet_id=self.subnet['id'] + ))) + + self.assertRaises(exc.HTTPNotFound, + self.controller.delete, + req, + self.share_network['id'], + self.subnet['id']) + mock_sns_get.assert_called_once_with( + context, self.subnet['id']) + self.mock_policy_check.assert_called_once_with( + context, self.resource_name, 'delete') + + def test_delete_subnet_with_share_servers_fail(self): + req = fakes.HTTPRequest.blank('/subnets/%s' % self.subnet['id'], + version="2.51") + context = req.environ['manila.context'] + + mock_sns_get = self.mock_object( + db_api, 'share_network_subnet_get', + mock.Mock(return_value=self.subnet)) + mock_all_get_all_shares_by_ss = self.mock_object( + db_api, 'share_instances_get_all_by_share_server', + mock.Mock(return_value=[])) + mock_all_ss_are_auto_deletable = self.mock_object( + self.controller, '_all_share_servers_are_auto_deletable', + mock.Mock(return_value=False)) + + self.assertRaises(exc.HTTPConflict, + self.controller.delete, + req, + self.share_network['id'], + self.subnet['id']) + + mock_sns_get.assert_called_once_with( + context, self.subnet['id']) + mock_all_get_all_shares_by_ss.assert_called_once_with( + context, self.subnet['share_servers'][0].id + ) + mock_all_ss_are_auto_deletable.assert_called_once_with( + self.subnet + ) + self.mock_policy_check.assert_called_once_with( + context, self.resource_name, 'delete') + + def test_delete_subnet_with_shares_fail(self): + req = fakes.HTTPRequest.blank('/subnets/%s' % self.subnet['id'], + version="2.51") + context = req.environ['manila.context'] + + mock_sns_get = self.mock_object( + db_api, 'share_network_subnet_get', + mock.Mock(return_value=self.subnet)) + mock_all_get_all_shares_by_ss = self.mock_object( + db_api, 'share_instances_get_all_by_share_server', + mock.Mock(return_value=[self.share])) + + self.assertRaises(exc.HTTPConflict, + self.controller.delete, + req, + self.share_network['id'], + self.subnet['id']) + + mock_sns_get.assert_called_once_with( + context, self.subnet['id']) + mock_all_get_all_shares_by_ss.assert_called_once_with( + context, self.subnet['share_servers'][0].id + ) + self.mock_policy_check.assert_called_once_with( + context, self.resource_name, 'delete') + + @ddt.data((None, fake_default_subnet, None), + (fake_az, None, fake_subnet_with_az)) + @ddt.unpack + def test__validate_subnet(self, az, default_subnet, subnet_az): + req = fakes.HTTPRequest.blank('/subnets', version='2.51') + context = req.environ['manila.context'] + + mock_get_default_sns = self.mock_object( + db_api, 'share_network_subnet_get_default_subnet', + mock.Mock(return_value=default_subnet)) + mock_get_subnet_by_az = self.mock_object( + db_api, 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(return_value=subnet_az)) + + self.assertRaises(exc.HTTPConflict, + self.controller._validate_subnet, + context, + self.share_network['id'], + az) + if az: + mock_get_subnet_by_az.assert_called_once_with( + context, self.share_network['id'], az['id']) + mock_get_default_sns.assert_not_called() + else: + mock_get_default_sns.assert_called_once_with( + context, self.share_network['id']) + mock_get_subnet_by_az.assert_not_called() + + def _setup_create_test_request_body(self): + 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' + } + return body + + def test_subnet_create(self): + req = fakes.HTTPRequest.blank('/subnets', version="2.51") + context = req.environ['manila.context'] + body = { + 'share-network-subnet': self._setup_create_test_request_body() + } + sn_id = body['share-network-subnet']['share_network_id'] + + expected_result = copy.deepcopy(body) + expected_result['share-network-subnet']['id'] = self.subnet['id'] + mock_check_net_and_subnet_id = self.mock_object( + common, 'check_net_id_and_subnet_id') + mock_validate_subnet = self.mock_object( + self.controller, '_validate_subnet') + mock_subnet_create = self.mock_object( + db_api, 'share_network_subnet_create', + mock.Mock(return_value=self.subnet)) + + self.controller.create( + req, body['share-network-subnet']['share_network_id'], body) + + mock_check_net_and_subnet_id.assert_called_once_with( + body['share-network-subnet']) + mock_validate_subnet.assert_called_once_with( + context, sn_id, az=fake_az) + mock_subnet_create.assert_called_once_with( + context, body['share-network-subnet']) + + def test_subnet_create_share_network_not_found(self): + fake_sn_id = 'fake_id' + req = fakes.HTTPRequest.blank('/subnets', version="2.51") + context = req.environ['manila.context'] + body = { + 'share-network-subnet': self._setup_create_test_request_body() + } + mock_sn_get = self.mock_object( + db_api, 'share_network_get', + mock.Mock(side_effect=exception.ShareNetworkNotFound( + share_network_id=fake_sn_id))) + + self.assertRaises(exc.HTTPNotFound, + self.controller.create, + req, + fake_sn_id, + body) + mock_sn_get.assert_called_once_with(context, fake_sn_id) + + def test_subnet_create_az_not_found(self): + fake_sn_id = 'fake_id' + req = fakes.HTTPRequest.blank('/subnets', version="2.51") + context = req.environ['manila.context'] + body = { + 'share-network-subnet': self._setup_create_test_request_body() + } + mock_sn_get = self.mock_object(db_api, 'share_network_get') + mock_az_get = self.mock_object( + db_api, 'availability_zone_get', + mock.Mock(side_effect=exception.AvailabilityZoneNotFound(id=''))) + + expected_az = body['share-network-subnet']['availability_zone'] + + self.assertRaises(exc.HTTPBadRequest, + self.controller.create, + req, + fake_sn_id, + body) + mock_sn_get.assert_called_once_with(context, fake_sn_id) + mock_az_get.assert_called_once_with( + context, expected_az) + + def test_subnet_create_subnet_default_or_same_az_exists(self): + fake_sn_id = 'fake_id' + req = fakes.HTTPRequest.blank('/subnets', version="2.51") + context = req.environ['manila.context'] + body = { + 'share-network-subnet': self._setup_create_test_request_body() + } + mock_sn_get = self.mock_object(db_api, 'share_network_get') + mock__validate_subnet = self.mock_object( + self.controller, '_validate_subnet', + mock.Mock(side_effect=exc.HTTPConflict('')) + ) + expected_az = body['share-network-subnet']['availability_zone'] + + self.assertRaises(exc.HTTPConflict, + self.controller.create, + req, + fake_sn_id, + body) + mock_sn_get.assert_called_once_with(context, fake_sn_id) + self.mock_az_get.assert_called_once_with(context, expected_az) + mock__validate_subnet.assert_called_once_with( + context, fake_sn_id, az=fake_az) + + def test_subnet_create_subnet_db_error(self): + fake_sn_id = 'fake_sn_id' + req = fakes.HTTPRequest.blank('/subnets', version="2.51") + context = req.environ['manila.context'] + body = { + 'share-network-subnet': self._setup_create_test_request_body() + } + mock_sn_get = self.mock_object(db_api, 'share_network_get') + mock__validate_subnet = self.mock_object( + self.controller, '_validate_subnet') + mock_db_subnet_create = self.mock_object( + db_api, 'share_network_subnet_create', + mock.Mock(side_effect=db_exception.DBError())) + expected_data = copy.deepcopy(body['share-network-subnet']) + expected_data['availability_zone_id'] = fake_az['id'] + expected_data.pop('availability_zone') + + self.assertRaises(exc.HTTPInternalServerError, + self.controller.create, + req, + fake_sn_id, + body) + + mock_sn_get.assert_called_once_with(context, fake_sn_id) + self.mock_az_get.assert_called_once_with(context, fake_az['name']) + mock__validate_subnet.assert_called_once_with( + context, fake_sn_id, az=fake_az) + mock_db_subnet_create.assert_called_once_with( + context, expected_data + ) + + def test_show_subnet(self): + subnet = db_utils.create_share_network_subnet( + id='fake_sns_2', share_network_id=self.share_network['id']) + expected_result = { + 'share_network_subnet': { + "created_at": subnet['created_at'], + "id": subnet['id'], + "share_network_id": subnet['share_network_id'], + "share_network_name": self.share_network['name'], + "availability_zone": subnet['availability_zone'], + "segmentation_id": subnet['segmentation_id'], + "neutron_subnet_id": subnet['neutron_subnet_id'], + "updated_at": subnet['updated_at'], + "neutron_net_id": subnet['neutron_net_id'], + "ip_version": subnet['ip_version'], + "cidr": subnet['cidr'], + "network_type": subnet['network_type'], + "gateway": subnet['gateway'], + "mtu": subnet['mtu'], + } + } + req = fakes.HTTPRequest.blank('/subnets/%s' % subnet['id'], + version="2.51") + context = req.environ['manila.context'] + mock_sn_get = self.mock_object( + db_api, 'share_network_get', mock.Mock( + return_value=self.share_network)) + mock_sns_get = self.mock_object( + db_api, 'share_network_subnet_get', mock.Mock( + return_value=subnet)) + + result = self.controller.show(req, self.share_network['id'], + subnet['id']) + + self.assertEqual(expected_result, result) + mock_sn_get.assert_called_once_with(context, self.share_network['id']) + mock_sns_get.assert_called_once_with(context, subnet['id']) + + @ddt.data( + (mock.Mock(side_effect=exception.ShareNetworkNotFound( + share_network_id='fake_net_id')), None), + (mock.Mock(), mock.Mock( + side_effect=exception.ShareNetworkSubnetNotFound( + share_network_subnet_id='fake_subnet_id')))) + @ddt.unpack + def test_show_subnet_not_found(self, sn_get_side_effect, + sns_get_side_effect): + req = fakes.HTTPRequest.blank('/subnets/%s' % self.subnet['id'], + version="2.51") + context = req.environ['manila.context'] + + mock_sn_get = self.mock_object( + db_api, 'share_network_get', sn_get_side_effect) + mock_sns_get = self.mock_object( + db_api, 'share_network_subnet_get', sns_get_side_effect) + + self.assertRaises(exc.HTTPNotFound, + self.controller.show, + req, + self.share_network['id'], + self.subnet['id']) + mock_sn_get.assert_called_once_with(context, self.share_network['id']) + if sns_get_side_effect: + mock_sns_get.assert_called_once_with(context, self.subnet['id']) + + def test_list_subnet(self): + share_network_id = 'fake_id' + subnet = db_utils.create_share_network_subnet( + share_network_id=share_network_id, id='fake_id') + fake_sn = db_utils.create_share_network(id=share_network_id) + expected_result = { + 'share_network_subnets': [{ + "created_at": subnet['created_at'], + "id": subnet['id'], + "share_network_id": subnet['id'], + "share_network_name": fake_sn["name"], + "availability_zone": subnet['availability_zone'], + "segmentation_id": subnet['segmentation_id'], + "neutron_subnet_id": subnet['neutron_subnet_id'], + "updated_at": subnet['updated_at'], + "neutron_net_id": subnet['neutron_net_id'], + "ip_version": subnet['ip_version'], + "cidr": subnet['cidr'], + "network_type": subnet['network_type'], + "gateway": subnet['gateway'], + "mtu": subnet['mtu'], + }] + } + + req = fakes.HTTPRequest.blank('/subnets/', version="2.51") + context = req.environ['manila.context'] + mock_sn_get = self.mock_object( + db_api, 'share_network_get', mock.Mock( + return_value=fake_sn)) + + result = self.controller.index(req, self.share_network['id']) + + self.assertEqual(expected_result, result) + mock_sn_get.assert_called_once_with(context, self.share_network['id']) + + def test_list_subnet_share_network_not_found(self): + req = fakes.HTTPRequest.blank('/subnets/', version="2.51") + context = req.environ['manila.context'] + + mock_sn_get = self.mock_object( + db_api, 'share_network_get', mock.Mock( + side_effect=exception.ShareNetworkNotFound( + share_network_id=self.share_network['id']))) + + self.assertRaises(exc.HTTPNotFound, + self.controller.index, + req, + self.share_network['id']) + mock_sn_get.assert_called_once_with(context, self.share_network['id']) diff --git a/manila/tests/api/v2/test_share_networks.py b/manila/tests/api/v2/test_share_networks.py index 3c7965b4b4..d7a068bd12 100644 --- a/manila/tests/api/v2/test_share_networks.py +++ b/manila/tests/api/v2/test_share_networks.py @@ -13,7 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import ddt +from manila.api import common import mock from oslo_db import exception as db_exception from oslo_utils import timeutils @@ -29,23 +31,32 @@ from manila import test from manila.tests.api import fakes -fake_share_network = { - 'id': 'fake network id', - 'project_id': 'fake project', - 'created_at': timeutils.parse_strtime('2002-02-02', fmt="%Y-%m-%d"), - 'updated_at': None, +fake_share_network_subnet = { + 'id': 'fake subnet id', 'neutron_net_id': 'fake net id', 'neutron_subnet_id': 'fake subnet id', 'network_type': 'vlan', 'segmentation_id': 1000, 'cidr': '10.0.0.0/24', 'ip_version': 4, + 'share_network_id': 'fake network id', + 'availability_zone_id': None, + 'share_servers': [], + 'availability_zone': [] +} + +fake_share_network = { + 'id': 'fake network id', + 'project_id': 'fake project', + 'created_at': timeutils.parse_strtime('2002-02-02', fmt="%Y-%m-%d"), + 'updated_at': None, 'name': 'fake name', 'description': 'fake description', - 'share_servers': [], - 'security_services': [] + 'security_services': [], + 'share_network_subnets': [] } + fake_share_network_shortened = { 'id': 'fake network id', 'name': 'fake name', @@ -56,15 +67,9 @@ fake_share_network_with_ss = { 'project_id': 'fake', 'created_at': timeutils.parse_strtime('2001-01-01', fmt="%Y-%m-%d"), 'updated_at': None, - 'neutron_net_id': '1111', - 'neutron_subnet_id': '2222', - 'network_type': 'local', - 'segmentation_id': 2000, - 'cidr': '8.0.0.0/12', - 'ip_version': 6, 'name': 'test-sn', 'description': 'fake description', - 'share_servers': [], + 'share_network_subnets': [], 'security_services': [{'id': 'fake-ss-id'}] } @@ -95,42 +100,63 @@ class ShareNetworkAPITest(test.TestCase): self.assertEqual(share_nw['project_id'], view['project_id']) self.assertEqual(share_nw['created_at'], view['created_at']) self.assertEqual(share_nw['updated_at'], view['updated_at']) - self.assertEqual(share_nw['neutron_net_id'], - view['neutron_net_id']) - self.assertEqual(share_nw['neutron_subnet_id'], - view['neutron_subnet_id']) - self.assertEqual(share_nw['network_type'], view['network_type']) - self.assertEqual(share_nw['segmentation_id'], - view['segmentation_id']) - self.assertEqual(share_nw['cidr'], view['cidr']) - self.assertEqual(share_nw['ip_version'], view['ip_version']) self.assertEqual(share_nw['name'], view['name']) self.assertEqual(share_nw['description'], view['description']) - - self.assertEqual(share_nw['created_at'], view['created_at']) - self.assertEqual(share_nw['updated_at'], view['updated_at']) self.assertNotIn('shares', view) self.assertNotIn('network_allocations', view) self.assertNotIn('security_services', view) - @ddt.data( - {'neutron_net_id': 'fake_neutron_net_id'}, - {'neutron_subnet_id': 'fake_neutron_subnet_id'}, - {'neutron_net_id': 'fake', 'neutron_subnet_id': 'fake'}) - def test_create_valid_cases(self, data): + def _setup_body_for_create_test(self, data): data.update({'user_id': 'fake_user_id'}) body = {share_networks.RESOURCE_NAME: data} + return body + + @ddt.data( + {'neutron_net_id': 'fake', 'neutron_subnet_id': 'fake'}) + def test_create_valid_cases(self, data): + body = self._setup_body_for_create_test(data) result = self.controller.create(self.req, body) data.pop('user_id', None) for k, v in data.items(): self.assertIn(data[k], result['share_network'][k]) + @ddt.data( + {'neutron_net_id': 'fake', 'neutron_subnet_id': 'fake', + 'availability_zone': 'fake'}) + def test_create_valid_cases_upper_2_50(self, data): + req = fakes.HTTPRequest.blank('/share-networks', version="2.51") + context = req.environ['manila.context'] + body = self._setup_body_for_create_test(data) + fake_az = { + 'name': 'fake', + 'id': 'fake_id' + } + self.mock_object(db_api, 'availability_zone_get', + mock.Mock(return_value=fake_az)) + + result = self.controller.create(req, body) + result_subnet = result['share_network']['share_network_subnets'][0] + data.pop('user_id', None) + data.pop('project_id', None) + data.pop('availability_zone_id', None) + data.pop('id', None) + data['availability_zone'] = result_subnet['availability_zone'] + + for k, v in data.items(): + self.assertIn(k, result_subnet.keys()) + + db_api.availability_zone_get.assert_called_once_with( + context, fake_az['name'] + ) + @ddt.data( {'nova_net_id': 'foo', 'neutron_net_id': 'bar'}, {'nova_net_id': 'foo', 'neutron_subnet_id': 'quuz'}, {'nova_net_id': 'foo', 'neutron_net_id': 'bar', 'neutron_subnet_id': 'quuz'}, - {'nova_net_id': 'fake_nova_net_id'}) + {'nova_net_id': 'fake_nova_net_id'}, + {'neutron_net_id': 'bar'}, + {'neutron_subnet_id': 'quuz'}) def test_create_invalid_cases(self, data): data.update({'user_id': 'fake_user_id'}) body = {share_networks.RESOURCE_NAME: data} @@ -138,9 +164,9 @@ class ShareNetworkAPITest(test.TestCase): webob_exc.HTTPBadRequest, self.controller.create, self.req, body) @ddt.data( - {'neutron_net_id': 'fake_neutron_net_id'}, - {'neutron_subnet_id': 'fake_neutron_subnet_id'}, - {'neutron_net_id': 'fake', 'neutron_subnet_id': 'fake'}) + {'name': 'new fake name'}, + {'description': 'new fake description'}, + {'name': 'new fake name', 'description': 'new fake description'}) def test_update_valid_cases(self, data): body = {share_networks.RESOURCE_NAME: {'user_id': 'fake_user'}} created = self.controller.create(self.req, body) @@ -172,7 +198,29 @@ class ShareNetworkAPITest(test.TestCase): self.controller.update, self.req, created['share_network']['id'], body) + @ddt.data( + ({'share_network_subnets': [ + {'share_network_id': fake_share_network['id']}]}, True), + ({'share_network_subnets': []}, False)) + @ddt.unpack + def test__subnet_has_search_opt(self, network, has_search_opt): + search_opts = { + 'share_network_id': fake_share_network['id'] + } + + result = None + + for key, value in search_opts.items(): + result = self.controller._subnet_has_search_opt( + key, value, network) + + self.assertEqual(has_search_opt, result) + def test_create_nominal(self): + self.mock_object(db_api, 'share_network_subnet_create') + self.mock_object(db_api, 'share_network_get', + mock.Mock(return_value=fake_share_network)) + self.mock_object(common, 'check_net_id_and_subnet_id') with mock.patch.object(db_api, 'share_network_create', mock.Mock(return_value=fake_share_network)): @@ -191,7 +239,7 @@ class ShareNetworkAPITest(test.TestCase): with mock.patch.object(db_api, 'share_network_create', mock.Mock(side_effect=db_exception.DBError)): - self.assertRaises(webob_exc.HTTPBadRequest, + self.assertRaises(webob_exc.HTTPInternalServerError, self.controller.create, self.req, self.body) @@ -203,9 +251,55 @@ class ShareNetworkAPITest(test.TestCase): self.req, body) + @ddt.data( + {'availability_zone': 'fake-zone'}) + def test_create_az_not_found(self, data): + req = fakes.HTTPRequest.blank('/share-networks', version="2.51") + + self.mock_object( + db_api, 'availability_zone_get', + mock.Mock( + side_effect=exception.AvailabilityZoneNotFound(id='fake'))) + + body = {share_networks.RESOURCE_NAME: data} + + self.assertRaises(webob_exc.HTTPBadRequest, + self.controller.create, + req, + body) + + def test_create_error_on_subnet_creation(self): + data = { + 'neutron_net_id': 'fake', + 'neutron_subnet_id': 'fake', + 'id': fake_share_network['id'] + } + subnet_data = copy.deepcopy(data) + self.mock_object(db_api, 'share_network_create', + mock.Mock(return_value=fake_share_network)) + self.mock_object(db_api, 'share_network_subnet_create', + mock.Mock(side_effect=db_exception.DBError())) + self.mock_object(db_api, 'share_network_delete') + body = {share_networks.RESOURCE_NAME: data} + + self.assertRaises(webob_exc.HTTPInternalServerError, + self.controller.create, + self.req, + body) + + db_api.share_network_create.assert_called_once_with(self.context, data) + subnet_data['share_network_id'] = data['id'] + subnet_data.pop('id') + db_api.share_network_subnet_create.assert_called_once_with( + self.context, subnet_data) + db_api.share_network_delete.assert_called_once_with( + self.context, fake_share_network['id']) + def test_delete_nominal(self): share_nw = fake_share_network.copy() - share_nw['share_servers'] = ['foo', 'bar'] + subnet = fake_share_network_subnet.copy() + subnet['share_servers'] = ['foo', 'bar'] + share_nw['share_network_subnets'] = [subnet] self.mock_object(db_api, 'share_network_get', mock.Mock(return_value=share_nw)) self.mock_object(db_api, 'share_instances_get_all_by_share_network', @@ -242,7 +336,9 @@ class ShareNetworkAPITest(test.TestCase): def test_quota_delete_reservation_failed(self): share_nw = fake_share_network.copy() - share_nw['share_servers'] = ['foo', 'bar'] + subnet = fake_share_network_subnet.copy() + subnet['share_servers'] = ['foo', 'bar'] + share_nw['share_network_subnets'] = [subnet] share_nw['user_id'] = 'fake_user_id' self.mock_object(db_api, 'share_network_get', @@ -330,6 +426,53 @@ class ShareNetworkAPITest(test.TestCase): db_api.share_network_get.assert_called_once_with( self.req.environ['manila.context'], share_nw['id']) + def test_delete_contains_more_than_one_subnet(self): + share_nw = fake_share_network.copy() + self.mock_object(db_api, 'share_network_get', + mock.Mock(return_value=share_nw)) + self.mock_object(db_api, 'share_instances_get_all_by_share_network', + mock.Mock(return_value=None)) + self.mock_object(db_api, 'count_share_groups_in_share_network', + mock.Mock(return_value=None)) + self.mock_object(self.controller, '_share_network_contains_subnets', + mock.Mock(return_value=True)) + + self.assertRaises(webob_exc.HTTPConflict, + self.controller.delete, + self.req, + share_nw['id']) + + db_api.share_network_get.assert_called_once_with( + self.context, share_nw['id']) + (db_api.share_instances_get_all_by_share_network + .assert_called_once_with(self.context, share_nw['id'])) + db_api.count_share_groups_in_share_network.assert_called_once_with( + self.context, share_nw['id'] + ) + (self.controller._share_network_contains_subnets + .assert_called_once_with(share_nw)) + + def test_delete_subnet_contains_share_server(self): + share_nw = fake_share_network.copy() + share_nw['share_network_subnets'].append({ + 'id': 'fake_sns_id', + 'share_servers': [{'id': 'fake_share_server_id'}] + }) + self.mock_object(db_api, 'share_network_get', + mock.Mock(return_value=share_nw)) + self.mock_object(db_api, 'count_share_groups_in_share_network', + mock.Mock(return_value=0)) + self.mock_object(self.controller, '_share_network_contains_subnets', + mock.Mock(return_value=False)) + self.mock_object( + self.controller, '_all_share_servers_are_auto_deletable', + mock.Mock(return_value=False)) + + self.assertRaises(webob_exc.HTTPConflict, + self.controller.delete, + self.req, + share_nw['id']) + @ddt.data( ({'share_servers': [{'is_auto_deletable': True}, {'is_auto_deletable': True}]}, True), @@ -344,6 +487,18 @@ class ShareNetworkAPITest(test.TestCase): self.controller._all_share_servers_are_auto_deletable( fake_share_network)) + @ddt.data( + ({'share_network_subnets': [{'share_servers': [{}, {}]}]}, True), + ({'share_network_subnets': [{'share_servers': []}]}, False), + ) + @ddt.unpack + def test__share_network_subnets_contain_share_servers(self, share_network, + expected_result): + self.assertEqual( + expected_result, + self.controller._share_network_subnets_contain_share_servers( + share_network)) + def test_show_nominal(self): share_nw = 'fake network id' with mock.patch.object(db_api, @@ -552,12 +707,6 @@ class ShareNetworkAPITest(test.TestCase): valid_filter_opts = { 'created_before': '2001-02-02', 'created_since': '1999-01-01', - 'neutron_net_id': '1111', - 'neutron_subnet_id': '2222', - 'network_type': 'local', - 'segmentation_id': 2000, - 'cidr': '8.0.0.0/12', - 'ip_version': 6, 'name': 'test-sn' } db_api.share_network_get_all_by_project.return_value = [ @@ -614,7 +763,9 @@ class ShareNetworkAPITest(test.TestCase): @mock.patch.object(db_api, 'share_network_get', mock.Mock()) def test_update_invalid_key_in_use(self): share_nw = fake_share_network.copy() - share_nw['share_servers'] = [{'id': 1}] + subnet = fake_share_network_subnet.copy() + subnet['share_servers'] = [{'id': 1}] + share_nw['share_network_subnets'] = [subnet] db_api.share_network_get.return_value = share_nw body = { @@ -633,12 +784,15 @@ class ShareNetworkAPITest(test.TestCase): @mock.patch.object(db_api, 'share_network_update', mock.Mock()) def test_update_valid_keys_in_use(self): share_nw = fake_share_network.copy() - share_nw['share_servers'] = [{'id': 1}] + subnet = fake_share_network_subnet.copy() + subnet['share_servers'] = [{'id': 1}] + share_nw['share_network_subnets'] = [subnet] updated_share_nw = share_nw.copy() updated_share_nw['name'] = 'new name' updated_share_nw['description'] = 'new description' db_api.share_network_get.return_value = share_nw + db_api.share_network_update.return_value = updated_share_nw body = { share_networks.RESOURCE_NAME: { 'name': updated_share_nw['name'], @@ -656,6 +810,10 @@ class ShareNetworkAPITest(test.TestCase): share_nw = 'fake network id' db_api.share_network_get.return_value = fake_share_network + self.mock_object( + self.controller, '_share_network_subnets_contain_share_servers', + mock.Mock(return_value=False)) + body = {share_networks.RESOURCE_NAME: {'neutron_subnet_id': 'new subnet'}} @@ -672,6 +830,9 @@ class ShareNetworkAPITest(test.TestCase): def test_action_add_security_service(self, microversion): share_network_id = 'fake network id' security_service_id = 'fake ss id' + self.mock_object( + self.controller, '_share_network_subnets_contain_share_servers') + body = {'add_security_service': {'security_service_id': security_service_id}} @@ -692,6 +853,9 @@ class ShareNetworkAPITest(test.TestCase): 'type': 'ldap'} body = {'add_security_service': {'security_service_id': security_service['id']}} + self.mock_object( + self.controller, '_share_network_subnets_contain_share_servers', + mock.Mock(return_value=False)) db_api.security_service_get.return_value = security_service db_api.share_network_get.return_value = share_network @@ -716,6 +880,8 @@ class ShareNetworkAPITest(test.TestCase): def test_action_remove_security_service(self, microversion): share_network_id = 'fake network id' security_service_id = 'fake ss id' + self.mock_object( + self.controller, '_share_network_subnets_contain_share_servers') body = {'remove_security_service': {'security_service_id': security_service_id}} @@ -730,8 +896,13 @@ class ShareNetworkAPITest(test.TestCase): @mock.patch.object(share_networks.policy, 'check_policy', mock.Mock()) def test_action_remove_security_service_forbidden(self): share_network = fake_share_network.copy() - share_network['share_servers'] = 'fake share server' + subnet = fake_share_network_subnet.copy() + subnet['share_servers'] = ['foo'] + share_network['share_network_subnets'] = [subnet] db_api.share_network_get.return_value = share_network + self.mock_object( + self.controller, '_share_network_subnets_contain_share_servers', + mock.Mock(return_value=True)) body = { 'remove_security_service': { 'security_service_id': 'fake id', @@ -758,3 +929,31 @@ class ShareNetworkAPITest(test.TestCase): self.req, share_network_id, body) + + @ddt.data('add_security_service', 'remove_security_service') + def test_action_security_service_contains_share_servers(self, action): + share_network = fake_share_network.copy() + security_service = {'id': ' security_service_2', + 'type': 'ldap'} + body = { + action: { + 'security_service_id': security_service['id'] + } + } + self.mock_object(share_networks.policy, 'check_policy') + self.mock_object(db_api, 'share_network_get', + mock.Mock(return_value=share_network)) + self.mock_object( + self.controller, '_share_network_subnets_contain_share_servers', + mock.Mock(return_value=True)) + + self.assertRaises(webob_exc.HTTPForbidden, + self.controller.action, + self.req, + share_network['id'], + body) + db_api.share_network_get.assert_called_once_with( + self.req.environ['manila.context'], share_network['id']) + share_networks.policy.check_policy.assert_called_once_with( + self.req.environ['manila.context'], + share_networks.RESOURCE_NAME, action) diff --git a/manila/tests/api/v2/test_share_servers.py b/manila/tests/api/v2/test_share_servers.py index c5a3f8df44..41079e5ec7 100644 --- a/manila/tests/api/v2/test_share_servers.py +++ b/manila/tests/api/v2/test_share_servers.py @@ -46,7 +46,8 @@ class ShareServerControllerTest(test.TestCase): constants.STATUS_UNMANAGE_ERROR, constants.STATUS_MANAGE_ERROR) def test_share_server_reset_status(self, status): req = fakes.HTTPRequest.blank('/v2/share-servers/fake-share-server/', - use_admin_context=True) + use_admin_context=True, + version="2.49") body = {'reset_status': {'status': status}} context = req.environ['manila.context'] @@ -62,7 +63,8 @@ class ShareServerControllerTest(test.TestCase): context, 'fake_server_id', {'status': status}) def test_share_reset_server_status_invalid(self): - req = fakes.HTTPRequest.blank('/reset_status', use_admin_context=True) + req = fakes.HTTPRequest.blank('/reset_status', use_admin_context=True, + version="2.49") body = {'reset_status': {'status': constants.STATUS_EXTENDING}} context = req.environ['manila.context'] @@ -74,7 +76,8 @@ class ShareServerControllerTest(test.TestCase): context, self.resource_name, 'reset_status') def test_share_server_reset_status_no_body(self): - req = fakes.HTTPRequest.blank('/reset_status', use_admin_context=True) + req = fakes.HTTPRequest.blank('/reset_status', use_admin_context=True, + version="2.49") context = req.environ['manila.context'] self.assertRaises( @@ -85,7 +88,8 @@ class ShareServerControllerTest(test.TestCase): context, self.resource_name, 'reset_status') def test_share_server_reset_status_no_status(self): - req = fakes.HTTPRequest.blank('/reset_status', use_admin_context=True) + req = fakes.HTTPRequest.blank('/reset_status', use_admin_context=True, + version="2.49") context = req.environ['manila.context'] self.assertRaises( @@ -98,6 +102,7 @@ class ShareServerControllerTest(test.TestCase): def _setup_manage_test_request_body(self): body = { 'share_network_id': 'fake_net_id', + 'share_network_subnet_id': 'fake_subnet_id', 'host': 'fake_host', 'identifier': 'fake_identifier', 'driver_options': {'opt1': 'fake_opt1', 'opt2': 'fake_opt2'}, @@ -112,14 +117,18 @@ class ShareServerControllerTest(test.TestCase): version="2.49") context = req.environ['manila.context'] share_network = db_utils.create_share_network(name=share_net_name) + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_network['id']) share_server = db_utils.create_share_server( - share_network_id=share_network['id'], + share_network_subnet_id=share_net_subnet['id'], host='fake_host', identifier='fake_identifier', is_auto_deletable=False) self.mock_object(db_api, 'share_network_get', mock.Mock( return_value=share_network)) + self.mock_object(db_api, 'share_network_subnet_get_default_subnet', + mock.Mock(return_value=share_net_subnet)) self.mock_object(utils, 'validate_service_host') body = { @@ -138,7 +147,8 @@ class ShareServerControllerTest(test.TestCase): 'updated_at': None, 'status': constants.STATUS_ACTIVE, 'host': 'fake_host', - 'share_network_id': share_server['share_network_id'], + 'share_network_id': + share_server['share_network_subnet']['share_network_id'], 'created_at': share_server['created_at'], 'backend_details': {}, 'identifier': share_server['identifier'], @@ -150,12 +160,12 @@ class ShareServerControllerTest(test.TestCase): 'fake_net_name') else: expected_result['share_server']['share_network_name'] = ( - share_server['share_network_id']) + share_net_subnet['share_network_id']) req_params = body['share_server'] manage_share_server_mock.assert_called_once_with( context, req_params['identifier'], req_params['host'], - share_network, req_params['driver_options']) + share_net_subnet, req_params['driver_options']) self.assertEqual(expected_result, result) @@ -164,9 +174,11 @@ class ShareServerControllerTest(test.TestCase): def test_manage_invalid(self): req = fakes.HTTPRequest.blank('/manage_share_server', - use_admin_context=True) + use_admin_context=True, version="2.49") context = req.environ['manila.context'] share_network = db_utils.create_share_network() + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_network['id']) body = { 'share_server': self._setup_manage_test_request_body() @@ -174,6 +186,8 @@ class ShareServerControllerTest(test.TestCase): self.mock_object(utils, 'validate_service_host') self.mock_object(db_api, 'share_network_get', mock.Mock(return_value=share_network)) + self.mock_object(db_api, 'share_network_subnet_get_default_subnet', + mock.Mock(return_value=share_net_subnet)) manage_share_server_mock = self.mock_object( share_api.API, 'manage_share_server', @@ -185,15 +199,24 @@ class ShareServerControllerTest(test.TestCase): req_params = body['share_server'] manage_share_server_mock.assert_called_once_with( context, req_params['identifier'], req_params['host'], - share_network, req_params['driver_options']) + share_net_subnet, req_params['driver_options']) def test_manage_forbidden(self): """Tests share server manage without admin privileges""" - req = fakes.HTTPRequest.blank('/manage_share_server') - self.mock_object(share_api.API, 'manage_share_server', mock.Mock()) + req = fakes.HTTPRequest.blank('/manage_share_server', version="2.49") error = mock.Mock(side_effect=exception.PolicyNotAuthorized(action='')) self.mock_object(share_api.API, 'manage_share_server', error) + share_network = db_utils.create_share_network() + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_network['id']) + + self.mock_object(db_api, 'share_network_get', mock.Mock( + return_value=share_network)) + self.mock_object(db_api, 'share_network_subnet_get_default_subnet', + mock.Mock(return_value=share_net_subnet)) + self.mock_object(utils, 'validate_service_host') + body = { 'share_server': self._setup_manage_test_request_body() } @@ -205,7 +228,7 @@ class ShareServerControllerTest(test.TestCase): def test__validate_manage_share_server_validate_no_body(self): """Tests share server manage""" - req = fakes.HTTPRequest.blank('/manage') + req = fakes.HTTPRequest.blank('/manage', version="2.49") body = {} self.assertRaises(webob.exc.HTTPUnprocessableEntity, @@ -223,7 +246,7 @@ class ShareServerControllerTest(test.TestCase): def test__validate_manage_share_server_validate_without_parameters( self, empty, key): """Tests share server manage without some parameters""" - req = fakes.HTTPRequest.blank('/manage_share_server') + req = fakes.HTTPRequest.blank('/manage_share_server', version="2.49") self.mock_object(share_api.API, 'manage_share_server', mock.Mock()) body = { @@ -249,11 +272,20 @@ class ShareServerControllerTest(test.TestCase): @ddt.unpack def test__validate_manage_share_server_validate_service_host( self, exception_to_raise, side_effect_exception): - req = fakes.HTTPRequest.blank('/manage') + req = fakes.HTTPRequest.blank('/manage', version="2.49") context = req.environ['manila.context'] error = mock.Mock(side_effect=side_effect_exception) self.mock_object(utils, 'validate_service_host', error) + share_network = db_utils.create_share_network() + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_network['id']) + + self.mock_object(db_api, 'share_network_get', mock.Mock( + return_value=share_network)) + self.mock_object(db_api, 'share_network_subnet_get_default_subnet', + mock.Mock(return_value=share_net_subnet)) + self.assertRaises( exception_to_raise, self.controller.manage, req, {'share_server': self._setup_manage_test_request_body()}) @@ -262,7 +294,7 @@ class ShareServerControllerTest(test.TestCase): context, self.resource_name, 'manage_share_server') def test__validate_manage_share_server_share_network_not_found(self): - req = fakes.HTTPRequest.blank('/manage') + req = fakes.HTTPRequest.blank('/manage', version="2.49") context = req.environ['manila.context'] self.mock_object(utils, 'validate_service_host') error = mock.Mock( @@ -279,7 +311,7 @@ class ShareServerControllerTest(test.TestCase): context, self.resource_name, 'manage_share_server') def test__validate_manage_share_server_driver_opts_not_instance_dict(self): - req = fakes.HTTPRequest.blank('/manage') + req = fakes.HTTPRequest.blank('/manage', version="2.49") context = req.environ['manila.context'] self.mock_object(utils, 'validate_service_host') self.mock_object(db_api, 'share_network_get') @@ -294,7 +326,7 @@ class ShareServerControllerTest(test.TestCase): context, self.resource_name, 'manage_share_server') def test__validate_manage_share_server_error_extract_host(self): - req = fakes.HTTPRequest.blank('/manage') + req = fakes.HTTPRequest.blank('/manage', version="2.49") context = req.environ['manila.context'] body = self._setup_manage_test_request_body() body['host'] = 'fake@backend#pool' @@ -306,10 +338,44 @@ class ShareServerControllerTest(test.TestCase): policy.check_policy.assert_called_once_with( context, self.resource_name, 'manage_share_server') + @ddt.data(True, False) + def test__validate_manage_share_server_error_subnet_not_found( + self, body_contains_subnet): + req = fakes.HTTPRequest.blank('/manage', version="2.51") + context = req.environ['manila.context'] + share_network = db_utils.create_share_network() + body = {'share_server': self._setup_manage_test_request_body()} + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_network['id']) + body['share_server']['share_network_subnet_id'] = ( + share_net_subnet['id'] if body_contains_subnet else None) + + self.mock_object( + db_api, 'share_network_subnet_get', + mock.Mock(side_effect=exception.ShareNetworkSubnetNotFound( + share_network_subnet_id='fake'))) + self.mock_object(db_api, 'share_network_subnet_get_default_subnet', + mock.Mock(return_value=None)) + + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.manage, + req, + body) + + policy.check_policy.assert_called_once_with( + context, self.resource_name, 'manage_share_server') + if body_contains_subnet: + db_api.share_network_subnet_get.assert_called_once_with( + context, share_net_subnet['id']) + else: + (db_api.share_network_subnet_get_default_subnet + .assert_called_once_with( + context, body['share_server']['share_network_id'])) + @ddt.data(True, False) def test_unmanage(self, force): server = self._setup_unmanage_tests() - req = fakes.HTTPRequest.blank('/unmanage') + req = fakes.HTTPRequest.blank('/unmanage', version="2.49") context = req.environ['manila.context'] mock_get = self.mock_object( db_api, 'share_server_get', mock.Mock(return_value=server)) @@ -326,7 +392,8 @@ class ShareServerControllerTest(test.TestCase): def test_unmanage_share_server_not_found(self): """Tests unmanaging share servers""" - req = fakes.HTTPRequest.blank('/v2/share-servers/fake_server_id/') + req = fakes.HTTPRequest.blank('/v2/share-servers/fake_server_id/', + version="2.49") context = req.environ['manila.context'] share_server_error = mock.Mock( side_effect=exception.ShareServerNotFound( @@ -350,7 +417,7 @@ class ShareServerControllerTest(test.TestCase): server = self._setup_unmanage_tests(status=status) get_mock = self.mock_object(db_api, 'share_server_get', mock.Mock(return_value=server)) - req = fakes.HTTPRequest.blank('/unmanage_share_server') + req = fakes.HTTPRequest.blank('/unmanage_share_server', version="2.49") context = req.environ['manila.context'] body = {'unmanage': {'force': True}} @@ -371,7 +438,7 @@ class ShareServerControllerTest(test.TestCase): @ddt.data(exception.ShareServerInUse, exception.PolicyNotAuthorized) def test_unmanage_share_server_badrequest(self, exc): - req = fakes.HTTPRequest.blank('/unmanage') + req = fakes.HTTPRequest.blank('/unmanage', version="2.49") server = self._setup_unmanage_tests() context = req.environ['manila.context'] error = mock.Mock(side_effect=exc('foobar')) diff --git a/manila/tests/api/v2/test_shares.py b/manila/tests/api/v2/test_shares.py index 4d98b3971a..1f477d8c19 100644 --- a/manila/tests/api/v2/test_shares.py +++ b/manila/tests/api/v2/test_shares.py @@ -666,6 +666,8 @@ class ShareAPITest(test.TestCase): self.mock_object(share_api.API, 'create', create_mock) self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value={'id': 'fakenetid'})) + self.mock_object( + db, 'share_network_subnet_get_by_availability_zone_id') body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/shares', version='2.7') @@ -674,6 +676,7 @@ class ShareAPITest(test.TestCase): expected = self._get_expected_share_detailed_response( shr, version='2.7') self.assertDictMatch(expected, res_dict) + # pylint: disable=unsubscriptable-object self.assertEqual("fakenetid", create_mock.call_args[1]['share_network_id']) @@ -1244,6 +1247,8 @@ class ShareAPITest(test.TestCase): return_value=parent_share)) self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value={'id': parent_share_net})) + self.mock_object( + db, 'share_network_subnet_get_by_availability_zone_id') body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/shares', version='2.7') @@ -1253,6 +1258,7 @@ class ShareAPITest(test.TestCase): expected = self._get_expected_share_detailed_response( shr, version='2.7') self.assertEqual(expected, res_dict) + # pylint: disable=unsubscriptable-object self.assertEqual(parent_share_net, create_mock.call_args[1]['share_network_id']) @@ -1286,6 +1292,8 @@ class ShareAPITest(test.TestCase): return_value=parent_share)) self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value={'id': parent_share_net})) + self.mock_object( + db, 'share_network_subnet_get_by_availability_zone_id') body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/shares', version='2.7') @@ -1293,6 +1301,7 @@ class ShareAPITest(test.TestCase): expected = self._get_expected_share_detailed_response( shr, version='2.7') self.assertDictMatch(expected, res_dict) + # pylint: disable=unsubscriptable-object self.assertEqual(parent_share_net, create_mock.call_args[1]['share_network_id']) diff --git a/manila/tests/api/views/test_share_network_subnets.py b/manila/tests/api/views/test_share_network_subnets.py new file mode 100644 index 0000000000..6f9fe3953e --- /dev/null +++ b/manila/tests/api/views/test_share_network_subnets.py @@ -0,0 +1,75 @@ +# Copyright 2019 NetApp, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import ddt + +from manila.api.views import share_network_subnets +from manila import test +from manila.tests.api import fakes +from manila.tests import db_utils + + +@ddt.ddt +class ViewBuilderTestCase(test.TestCase): + + def setUp(self): + super(ViewBuilderTestCase, self).setUp() + self.builder = share_network_subnets.ViewBuilder() + self.share_network = db_utils.create_share_network( + name='fake_network', id='fake_sn_id') + + def _validate_is_detail_return(self, result): + 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'] + + for key in expected_keys: + self.assertIn(key, result) + + def test_build_share_network_subnet(self): + req = fakes.HTTPRequest.blank('/subnets', version='2.51') + + subnet = db_utils.create_share_network_subnet( + share_network_id=self.share_network['id']) + + result = self.builder.build_share_network_subnet(req, subnet) + + self.assertEqual(1, len(result)) + self.assertIn('share_network_subnet', result) + self.assertEqual(subnet['id'], + result['share_network_subnet']['id']) + self.assertEqual(subnet['share_network_id'], + result['share_network_subnet']['share_network_id']) + self.assertIsNone( + 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') + + share_network = db_utils.create_share_network( + name='fake_network', id='fake_sn_id_1') + subnet = db_utils.create_share_network_subnet( + share_network_id=share_network['id']) + + result = self.builder.build_share_network_subnets(req, [subnet]) + + self.assertIn('share_network_subnets', result) + 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) diff --git a/manila/tests/api/views/test_share_networks.py b/manila/tests/api/views/test_share_networks.py index 41f552fc68..61de70c968 100644 --- a/manila/tests/api/views/test_share_networks.py +++ b/manila/tests/api/views/test_share_networks.py @@ -34,12 +34,21 @@ class ViewBuilderTestCase(test.TestCase): @ddt.data(*itertools.product( [ - {'id': 'fake_sn_id', 'name': 'fake_sn_name'}, {'id': 'fake_sn_id', 'name': 'fake_sn_name', + 'share_network_subnets': []}, + {'id': 'fake_sn_id', 'name': 'fake_sn_name', + 'share_network_subnets': [], 'fake_extra_key': 'foo'}, + {'id': 'fake_sn_id', 'name': 'fake_sn_name', + 'share_network_subnets': [ + {'availability_zone_id': None, + 'id': 'fake', + 'availability_zone': None, + 'is_default': False + }], 'fake_extra_key': 'foo'}, ], ["1.0", "2.0", "2.18", "2.20", "2.25", "2.26", - api_version._MAX_API_VERSION]) + "2.49", api_version._MAX_API_VERSION]) ) @ddt.unpack def test_build_share_network(self, share_network_data, microversion): @@ -49,17 +58,28 @@ class ViewBuilderTestCase(test.TestCase): api_version.APIVersionRequest('2.20')) nova_net_support = (api_version.APIVersionRequest(microversion) < api_version.APIVersionRequest('2.26')) + default_net_info_support = (api_version.APIVersionRequest(microversion) + <= api_version.APIVersionRequest('2.49')) + subnets_support = (api_version.APIVersionRequest(microversion) > + api_version.APIVersionRequest('2.49')) req = fakes.HTTPRequest.blank('/share-networks', version=microversion) expected_keys = { 'id', 'name', 'project_id', 'created_at', 'updated_at', - 'neutron_net_id', 'neutron_subnet_id', 'network_type', - 'segmentation_id', 'cidr', 'ip_version', 'description'} - if gateway_support: - expected_keys.add('gateway') - if mtu_support: - expected_keys.add('mtu') - if nova_net_support: - expected_keys.add('nova_net_id') + 'description'} + if subnets_support: + expected_keys.add('share_network_subnets') + else: + if default_net_info_support: + network_info = { + 'neutron_net_id', 'neutron_subnet_id', 'network_type', + 'segmentation_id', 'cidr', 'ip_version'} + expected_keys.update(network_info) + if gateway_support: + expected_keys.add('gateway') + if mtu_support: + expected_keys.add('mtu') + if nova_net_support: + expected_keys.add('nova_net_id') result = self.builder.build_share_network(req, share_network_data) self.assertEqual(1, len(result)) @@ -93,7 +113,7 @@ class ViewBuilderTestCase(test.TestCase): {'id': 'fake_id2', 'name': 'fake_name2'}], ], - set(["1.0", "2.0", "2.18", "2.20", "2.25", "2.26", + set(["1.0", "2.0", "2.18", "2.20", "2.25", "2.26", "2.49", api_version._MAX_API_VERSION])) ) @ddt.unpack @@ -105,6 +125,10 @@ class ViewBuilderTestCase(test.TestCase): api_version.APIVersionRequest('2.20')) nova_net_support = (api_version.APIVersionRequest(microversion) < api_version.APIVersionRequest('2.26')) + default_net_info_support = (api_version.APIVersionRequest(microversion) + <= api_version.APIVersionRequest('2.49')) + subnets_support = (api_version.APIVersionRequest(microversion) > + api_version.APIVersionRequest('2.49')) req = fakes.HTTPRequest.blank('/share-networks', version=microversion) expected_networks_list = [] for share_network in share_networks: @@ -114,23 +138,34 @@ class ViewBuilderTestCase(test.TestCase): 'project_id': share_network.get('project_id'), 'created_at': share_network.get('created_at'), 'updated_at': share_network.get('updated_at'), - 'neutron_net_id': share_network.get('neutron_net_id'), - 'neutron_subnet_id': share_network.get('neutron_subnet_id'), - 'network_type': share_network.get('network_type'), - 'segmentation_id': share_network.get('segmentation_id'), - 'cidr': share_network.get('cidr'), - 'ip_version': share_network.get('ip_version'), 'description': share_network.get('description'), } - if gateway_support: - share_network.update({'gateway': 'fake_gateway'}) - expected_data.update({'gateway': share_network.get('gateway')}) - if mtu_support: - share_network.update({'mtu': 1509}) - expected_data.update({'mtu': share_network.get('mtu')}) - if nova_net_support: - share_network.update({'nova_net_id': 'fake_nova_net_id'}) - expected_data.update({'nova_net_id': None}) + if subnets_support: + share_network.update({'share_network_subnets': []}) + expected_data.update({'share_network_subnets': []}) + else: + if default_net_info_support: + network_data = { + 'neutron_net_id': share_network.get('neutron_net_id'), + 'neutron_subnet_id': share_network.get( + 'neutron_subnet_id'), + 'network_type': share_network.get('network_type'), + 'segmentation_id': share_network.get( + 'segmentation_id'), + 'cidr': share_network.get('cidr'), + 'ip_version': share_network.get('ip_version'), + } + expected_data.update(network_data) + if gateway_support: + share_network.update({'gateway': 'fake_gateway'}) + expected_data.update({'gateway': + share_network.get('gateway')}) + if mtu_support: + share_network.update({'mtu': 1509}) + expected_data.update({'mtu': share_network.get('mtu')}) + if nova_net_support: + share_network.update({'nova_net_id': 'fake_nova_net_id'}) + expected_data.update({'nova_net_id': None}) expected_networks_list.append(expected_data) expected = {'share_networks': expected_networks_list} @@ -148,7 +183,7 @@ class ViewBuilderTestCase(test.TestCase): {'id': 'id2', 'name': 'name2', 'fake': 'I should not be returned'}] ], - set(["1.0", "2.0", "2.18", "2.20", "2.25", "2.26", + set(["1.0", "2.0", "2.18", "2.20", "2.25", "2.26", "2.49", api_version._MAX_API_VERSION])) ) @ddt.unpack diff --git a/manila/tests/db/migrations/alembic/migrations_data_checks.py b/manila/tests/db/migrations/alembic/migrations_data_checks.py index 75d1c6eb7f..e9d1471c2b 100644 --- a/manila/tests/db/migrations/alembic/migrations_data_checks.py +++ b/manila/tests/db/migrations/alembic/migrations_data_checks.py @@ -34,6 +34,7 @@ See BaseMigrationChecks class for more information. """ import abc +import copy import datetime from oslo_db import exception as oslo_db_exc @@ -2803,3 +2804,116 @@ class ShareServerIsAutoDeletableAndIdentifierChecks(BaseMigrationChecks): for ss in engine.execute(ss_table.select()): self.test_case.assertFalse(hasattr(ss, 'is_auto_deletable')) self.test_case.assertFalse(hasattr(ss, 'identifier')) + + +@map_to_migration('805685098bd2') +class ShareNetworkSubnetMigrationChecks(BaseMigrationChecks): + + user_id = '6VFQ87wnV24lg1c2q1q0lJkTbQBPFZ1m4968' + project_id = '19HAW8w58yeUPBy8zGex4EGulWZHd8zZGtHk' + share_network = { + 'id': uuidutils.generate_uuid(), + 'user_id': user_id, + 'project_id': project_id, + 'neutron_net_id': uuidutils.generate_uuid(), + 'neutron_subnet_id': uuidutils.generate_uuid(), + 'cidr': '203.0.113.0/24', + 'ip_version': 4, + 'network_type': 'vxlan', + 'segmentation_id': 100, + 'gateway': 'fake_gateway', + 'mtu': 1500, + } + + share_networks = [share_network] + + sns_table_name = 'share_network_subnets' + sn_table_name = 'share_networks' + ss_table_name = 'share_servers' + + expected_keys = ['neutron_net_id', 'neutron_subnet_id', 'cidr', + 'ip_version', 'network_type', 'segmentation_id', + 'gateway', 'mtu'] + + def _setup_data_for_empty_neutron_net_and_subnet_id_test(self, network): + network['id'] = uuidutils.generate_uuid() + for key in self.expected_keys: + network[key] = None + return network + + def setup_upgrade_data(self, engine): + share_network_data_without_net_info = ( + self._setup_data_for_empty_neutron_net_and_subnet_id_test( + copy.deepcopy(self.share_network))) + self.share_networks.append(share_network_data_without_net_info) + # Load the table to be used below + sn_table = utils.load_table(self.sn_table_name, engine) + ss_table = utils.load_table(self.ss_table_name, engine) + + # Share server data + share_server_data = { + 'host': 'acme@controller-ostk-0', + 'status': 'active', + } + + # Create share share networks and one share server for each of them + for network in self.share_networks: + share_server_data['share_network_id'] = network['id'] + share_server_data['id'] = uuidutils.generate_uuid() + engine.execute(sn_table.insert(network)) + engine.execute(ss_table.insert(share_server_data)) + + def check_upgrade(self, engine, data): + # Load the necessary tables + sn_table = utils.load_table(self.sn_table_name, engine) + sns_table = utils.load_table(self.sns_table_name, engine) + ss_table = utils.load_table(self.ss_table_name, engine) + + for network in self.share_networks: + sn_record = engine.execute(sn_table.select().where( + sn_table.c.id == network['id'])).first() + + for key in self.expected_keys: + self.test_case.assertFalse(hasattr(sn_record, key)) + + sns_record = engine.execute(sns_table.select().where( + sns_table.c.share_network_id == network['id'])).first() + + for key in self.expected_keys: + self.test_case.assertTrue(hasattr(sns_record, key)) + self.test_case.assertEqual(network[key], sns_record[key]) + + ss_record = ( + engine.execute( + ss_table.select().where( + ss_table.c.share_network_subnet_id == sns_record['id']) + ).first()) + + self.test_case.assertIs( + True, hasattr(ss_record, 'share_network_subnet_id')) + self.test_case.assertEqual( + ss_record['share_network_subnet_id'], sns_record['id']) + self.test_case.assertIs( + False, hasattr(ss_record, 'share_network_id')) + + def check_downgrade(self, engine): + sn_table = utils.load_table(self.sn_table_name, engine) + + # Check if the share network table contains the expected keys + for sn in engine.execute(sn_table.select()): + for key in self.expected_keys: + self.test_case.assertTrue(hasattr(sn, key)) + + ss_table = utils.load_table(self.ss_table_name, engine) + for network in self.share_networks: + for ss in engine.execute(ss_table.select().where( + ss_table.c.share_network_id == network['id'])): + self.test_case.assertFalse(hasattr(ss, + 'share_network_subnet_id')) + self.test_case.assertTrue(hasattr(ss, 'share_network_id')) + self.test_case.assertEqual(network['id'], ss['id']) + + # Check if the created table doesn't exists anymore + self.test_case.assertRaises( + sa_exc.NoSuchTableError, + utils.load_table, self.sns_table_name, engine) diff --git a/manila/tests/db/sqlalchemy/test_api.py b/manila/tests/db/sqlalchemy/test_api.py index dbbc8541ec..7102742a22 100644 --- a/manila/tests/db/sqlalchemy/test_api.py +++ b/manila/tests/db/sqlalchemy/test_api.py @@ -577,7 +577,7 @@ class ShareDatabaseAPITestCase(test.TestCase): db_utils.create_share_replica(share_id=share_2['id']) expected_ss_keys = { 'backend_details', 'host', 'id', - 'share_network_id', 'status', + 'share_network_subnet_id', 'status', } expected_share_keys = { 'project_id', 'share_type_id', 'display_name', @@ -625,7 +625,7 @@ class ShareDatabaseAPITestCase(test.TestCase): share_server_id=share_server['id']) expected_ss_keys = { 'backend_details', 'host', 'id', - 'share_network_id', 'status', + 'share_network_subnet_id', 'status', } expected_share_keys = { 'project_id', 'share_type_id', 'display_name', @@ -690,7 +690,7 @@ class ShareDatabaseAPITestCase(test.TestCase): session = db_api.get_session() expected_ss_keys = { 'backend_details', 'host', 'id', - 'share_network_id', 'status', + 'share_network_subnet_id', 'status', } expected_share_keys = { 'project_id', 'share_type_id', 'display_name', @@ -778,7 +778,7 @@ class ShareDatabaseAPITestCase(test.TestCase): ) expected_extra_keys = { 'backend_details', 'host', 'id', - 'share_network_id', 'status', + 'share_network_subnet_id', 'status', } with session.begin(): share_replica = db_api.share_replica_get( @@ -1941,14 +1941,8 @@ class ShareNetworkDatabaseAPITestCase(BaseDatabaseAPITestCase): def setUp(self): super(ShareNetworkDatabaseAPITestCase, self).setUp() self.share_nw_dict = {'id': 'fake network id', - 'neutron_net_id': 'fake net id', - 'neutron_subnet_id': 'fake subnet id', 'project_id': self.fake_context.project_id, 'user_id': 'fake_user_id', - 'network_type': 'vlan', - 'segmentation_id': 1000, - 'cidr': '10.0.0.0/24', - 'ip_version': 4, 'name': 'whatever', 'description': 'fake description'} @@ -2042,6 +2036,26 @@ class ShareNetworkDatabaseAPITestCase(BaseDatabaseAPITestCase): self._check_fields(expected=service, actual=result['security_services'][index]) + @ddt.data([{'id': 'fake_id_1', 'availability_zone_id': 'None'}], + [{'id': 'fake_id_2', 'availability_zone_id': 'None'}, + {'id': 'fake_id_3', 'availability_zone_id': 'fake_az_id'}]) + def test_get_with_subnets(self, subnets): + db_api.share_network_create(self.fake_context, self.share_nw_dict) + + for subnet in subnets: + subnet['share_network_id'] = self.share_nw_dict['id'] + db_api.share_network_subnet_create(self.fake_context, subnet) + + result = db_api.share_network_get(self.fake_context, + self.share_nw_dict['id']) + + self.assertEqual(len(subnets), + len(result['share_network_subnets'])) + + for index, subnet in enumerate(subnets): + self._check_fields(expected=subnet, + actual=result['share_network_subnets'][index]) + def test_get_not_found(self): self.assertRaises(exception.ShareNetworkNotFound, db_api.share_network_get, @@ -2092,7 +2106,7 @@ class ShareNetworkDatabaseAPITestCase(BaseDatabaseAPITestCase): share_network_dict = dict(self.share_nw_dict) fake_id = 'fake_id%s' % index share_network_dict.update({'id': fake_id, - 'neutron_subnet_id': fake_id}) + 'project_id': fake_id}) share_networks.append(share_network_dict) db_api.share_network_create(self.fake_context, share_network_dict) index += 1 @@ -2107,7 +2121,6 @@ class ShareNetworkDatabaseAPITestCase(BaseDatabaseAPITestCase): share_nw_dict2 = dict(self.share_nw_dict) share_nw_dict2['id'] = 'fake share nw id2' share_nw_dict2['project_id'] = 'fake project 2' - share_nw_dict2['neutron_subnet_id'] = 'fake subnet id2' db_api.share_network_create(self.fake_context, self.share_nw_dict) db_api.share_network_create(self.fake_context, share_nw_dict2) @@ -2269,6 +2282,155 @@ class ShareNetworkDatabaseAPITestCase(BaseDatabaseAPITestCase): self.assertEqual(0, len(result['share_instances'])) +@ddt.ddt +class ShareNetworkSubnetDatabaseAPITestCase(BaseDatabaseAPITestCase): + + def __init__(self, *args, **kwargs): + super(ShareNetworkSubnetDatabaseAPITestCase, self).__init__( + *args, **kwargs) + self.fake_context = context.RequestContext(user_id='fake user', + project_id='fake project', + is_admin=False) + + def setUp(self): + super(ShareNetworkSubnetDatabaseAPITestCase, self).setUp() + self.subnet_dict = {'id': 'fake network id', + 'neutron_net_id': 'fake net id', + 'neutron_subnet_id': 'fake subnet id', + 'network_type': 'vlan', + 'segmentation_id': 1000, + 'share_network_id': 'fake_id', + 'cidr': '10.0.0.0/24', + 'ip_version': 4, + 'availability_zone_id': None} + + def test_create(self): + result = db_api.share_network_subnet_create( + self.fake_context, self.subnet_dict) + self._check_fields(expected=self.subnet_dict, actual=result) + + def test_create_duplicated_id(self): + db_api.share_network_subnet_create(self.fake_context, self.subnet_dict) + + self.assertRaises(db_exception.DBDuplicateEntry, + db_api.share_network_subnet_create, + self.fake_context, + self.subnet_dict) + + def test_get(self): + db_api.share_network_subnet_create(self.fake_context, self.subnet_dict) + + result = db_api.share_network_subnet_get(self.fake_context, + self.subnet_dict['id']) + self._check_fields(expected=self.subnet_dict, actual=result) + + @ddt.data([{'id': 'fake_id_1', 'identifier': 'fake_identifier', + 'host': 'fake_host'}], + [{'id': 'fake_id_2', 'identifier': 'fake_identifier', + 'host': 'fake_host'}, + {'id': 'fake_id_3', 'identifier': 'fake_identifier', + 'host': 'fake_host'}]) + def test_get_with_share_servers(self, share_servers): + db_api.share_network_subnet_create(self.fake_context, + self.subnet_dict) + + for share_server in share_servers: + share_server['share_network_subnet_id'] = self.subnet_dict['id'] + db_api.share_server_create(self.fake_context, share_server) + + result = db_api.share_network_subnet_get(self.fake_context, + self.subnet_dict['id']) + + self.assertEqual(len(share_servers), + len(result['share_servers'])) + + for index, share_server in enumerate(share_servers): + self._check_fields(expected=share_server, + actual=result['share_servers'][index]) + + def test_get_not_found(self): + db_api.share_network_subnet_create(self.fake_context, self.subnet_dict) + + self.assertRaises(exception.ShareNetworkSubnetNotFound, + db_api.share_network_subnet_get, + self.fake_context, + 'fake_id') + + def test_delete(self): + db_api.share_network_subnet_create(self.fake_context, self.subnet_dict) + db_api.share_network_subnet_delete(self.fake_context, + self.subnet_dict['id']) + + self.assertRaises(exception.ShareNetworkSubnetNotFound, + db_api.share_network_subnet_delete, + self.fake_context, + self.subnet_dict['id']) + + def test_delete_not_found(self): + self.assertRaises(exception.ShareNetworkSubnetNotFound, + db_api.share_network_subnet_delete, + self.fake_context, + 'fake_id') + + def test_update(self): + update_dict = { + 'gateway': 'fake_gateway', + 'ip_version': 6, + 'mtu': '' + } + + db_api.share_network_subnet_create(self.fake_context, self.subnet_dict) + db_api.share_network_subnet_update( + self.fake_context, self.subnet_dict['id'], update_dict) + + result = db_api.share_network_subnet_get(self.fake_context, + self.subnet_dict['id']) + self._check_fields(expected=update_dict, actual=result) + + def test_update_not_found(self): + self.assertRaises(exception.ShareNetworkSubnetNotFound, + db_api.share_network_subnet_update, + self.fake_context, + self.subnet_dict['id'], + {}) + + @ddt.data([{'id': 'sn_id1', 'project_id': 'fake', 'user_id': 'fake'}], + [{'id': 'fake_id', 'project_id': 'fake', 'user_id': 'fake'}, + {'id': 'sn_id2', 'project_id': 'fake', 'user_id': 'fake'}]) + def test_get_all_by_share_network(self, share_networks): + + for idx, share_network in enumerate(share_networks): + self.subnet_dict['share_network_id'] = share_network['id'] + self.subnet_dict['id'] = 'fake_id%s' % idx + + db_api.share_network_create(self.fake_context, share_network) + db_api.share_network_subnet_create(self.fake_context, + self.subnet_dict) + for share_network in share_networks: + subnets = db_api.share_network_subnet_get_all_by_share_network( + self.fake_context, share_network['id']) + self.assertEqual(1, len(subnets)) + + def test_get_by_availability_zone_id(self): + az = db_api.availability_zone_create_if_not_exist(self.fake_context, + 'fake_zone_id') + self.subnet_dict['availability_zone_id'] = az['id'] + db_api.share_network_subnet_create(self.fake_context, self.subnet_dict) + + result = db_api.share_network_subnet_get_by_availability_zone_id( + self.fake_context, self.subnet_dict['share_network_id'], az['id']) + + self._check_fields(expected=self.subnet_dict, actual=result) + + def test_get_default_subnet(self): + db_api.share_network_subnet_create(self.fake_context, self.subnet_dict) + + result = db_api.share_network_subnet_get_default_subnet( + self.fake_context, self.subnet_dict['share_network_id']) + + self._check_fields(expected=self.subnet_dict, actual=result) + + @ddt.ddt class SecurityServiceDatabaseAPITestCase(BaseDatabaseAPITestCase): @@ -2437,7 +2599,8 @@ class ShareServerDatabaseAPITestCase(test.TestCase): expected = db_utils.create_share_server() server = db_api.share_server_get(self.ctxt, expected['id']) self.assertEqual(expected['id'], server['id']) - self.assertEqual(expected.share_network_id, server.share_network_id) + self.assertEqual(expected.share_network_subnet_id, + server.share_network_subnet_id) self.assertEqual(expected.host, server.host) self.assertEqual(expected.status, server.status) @@ -2449,7 +2612,8 @@ class ShareServerDatabaseAPITestCase(test.TestCase): def test_create(self): server = db_utils.create_share_server() self.assertTrue(server['id']) - self.assertEqual(server.share_network_id, server['share_network_id']) + self.assertEqual(server.share_network_subnet_id, + server['share_network_subnet_id']) self.assertEqual(server.host, server['host']) self.assertEqual(server.status, server['status']) @@ -2488,21 +2652,31 @@ class ShareServerDatabaseAPITestCase(test.TestCase): self.ctxt, fake_id, {}) def test_get_all_by_host_and_share_net_valid(self): - valid = { + subnet_1 = { + 'id': '1', 'share_network_id': '1', + } + subnet_2 = { + 'id': '2', + 'share_network_id': '2', + } + valid = { + 'share_network_subnet_id': '1', 'host': 'host1', 'status': constants.STATUS_ACTIVE, } invalid = { - 'share_network_id': '1', + 'share_network_subnet_id': '2', 'host': 'host1', 'status': constants.STATUS_ERROR, } other = { - 'share_network_id': '2', + 'share_network_subnet_id': '1', 'host': 'host2', 'status': constants.STATUS_ACTIVE, } + db_utils.create_share_network_subnet(**subnet_1) + db_utils.create_share_network_subnet(**subnet_2) valid = db_utils.create_share_server(**valid) db_utils.create_share_server(**invalid) db_utils.create_share_server(**other) @@ -2572,7 +2746,7 @@ class ShareServerDatabaseAPITestCase(test.TestCase): def test_get_with_details(self): values = { - 'share_network_id': 'fake-share-net-id', + 'share_network_subnet_id': 'fake-share-net-id', 'host': 'hostname', 'status': constants.STATUS_ACTIVE, } @@ -2584,7 +2758,8 @@ class ShareServerDatabaseAPITestCase(test.TestCase): db_api.share_server_backend_details_set(self.ctxt, srv_id, details) server = db_api.share_server_get(self.ctxt, srv_id) self.assertEqual(srv_id, server['id']) - self.assertEqual(values['share_network_id'], server.share_network_id) + self.assertEqual(values['share_network_subnet_id'], + server.share_network_subnet_id) self.assertEqual(values['host'], server.host) self.assertEqual(values['status'], server.status) self.assertDictMatch(server['backend_details'], details) diff --git a/manila/tests/db_utils.py b/manila/tests/db_utils.py index d935b325d7..768209a59d 100644 --- a/manila/tests/db_utils.py +++ b/manila/tests/db_utils.py @@ -212,7 +212,7 @@ def create_share_server(**kwargs): backend_details = kwargs.pop('backend_details', {}) srv = { 'host': 'host1', - 'share_network_id': 'fake_srv_id', + 'share_network_subnet_id': 'fake_srv_id', 'status': constants.STATUS_ACTIVE } share_srv = _create_db_row(db.share_server_create, srv, kwargs) @@ -251,19 +251,31 @@ def create_share_network(**kwargs): net = { 'user_id': 'fake', 'project_id': 'fake', - 'neutron_net_id': 'fake-neutron-net', - 'neutron_subnet_id': 'fake-neutron-subnet', 'status': 'new', - 'network_type': 'vlan', - 'segmentation_id': 1000, - 'cidr': '10.0.0.0/24', - 'ip_version': 4, 'name': 'whatever', 'description': 'fake description', } return _create_db_row(db.share_network_create, net, kwargs) +def create_share_network_subnet(**kwargs): + """Create a share network subnet object.""" + subnet = { + 'id': 'fake_sns_id', + 'neutron_net_id': 'fake-neutron-net', + 'neutron_subnet_id': 'fake-neutron-subnet', + 'network_type': 'vlan', + 'segmentation_id': 1000, + 'cidr': '10.0.0.0/24', + 'ip_version': 4, + 'availability_zone_id': 'fake_zone_id', + 'share_network_id': 'fake_sn_id', + 'gateway': None, + 'mtu': None + } + return _create_db_row(db.share_network_subnet_create, subnet, kwargs) + + def create_security_service(**kwargs): share_network_id = kwargs.pop('share_network_id', None) service = { diff --git a/manila/tests/fake_share.py b/manila/tests/fake_share.py index 95542c0eba..d442df54cc 100644 --- a/manila/tests/fake_share.py +++ b/manila/tests/fake_share.py @@ -302,7 +302,7 @@ def fake_share_server_get(): 'status': constants.STATUS_ACTIVE, 'updated_at': None, 'host': 'fake_host', - 'share_network_id': 'fake_sn_id', + 'share_network_subnet_id': 'fake_sn_id', 'share_network_name': 'fake_sn_name', 'project_id': 'fake_project_id', 'id': 'fake_share_server_id', diff --git a/manila/tests/network/neutron/test_neutron_plugin.py b/manila/tests/network/neutron/test_neutron_plugin.py index 809cfcda7f..c14917b625 100644 --- a/manila/tests/network/neutron/test_neutron_plugin.py +++ b/manila/tests/network/neutron/test_neutron_plugin.py @@ -79,15 +79,10 @@ fake_neutron_subnet = { 'gateway_ip': '10.0.0.1', } -fake_share_network = { - 'id': 'fake nw info id', +fake_share_network_subnet = { + 'id': 'fake nw subnet id', 'neutron_subnet_id': fake_neutron_network['subnets'][0], 'neutron_net_id': fake_neutron_network['id'], - 'project_id': 'fake project id', - 'status': 'test_subnet_status', - 'name': 'fake name', - 'description': 'fake description', - 'security_services': [], 'network_type': 'fake_network_type', 'segmentation_id': 1234, 'ip_version': 4, @@ -96,6 +91,16 @@ fake_share_network = { 'mtu': 1509, } +fake_share_network = { + 'id': 'fake nw info id', + 'project_id': 'fake project id', + 'status': 'test_subnet_status', + 'name': 'fake name', + 'description': 'fake description', + 'security_services': [], + 'subnets': [fake_share_network_subnet], +} + fake_share_server = { 'id': 'fake nw info id', 'status': 'test_server_status', @@ -111,11 +116,11 @@ fake_network_allocation = { 'mac_address': fake_neutron_port['mac_address'], 'status': constants.STATUS_ACTIVE, 'label': 'user', - 'network_type': fake_share_network['network_type'], - 'segmentation_id': fake_share_network['segmentation_id'], - 'ip_version': fake_share_network['ip_version'], - 'cidr': fake_share_network['cidr'], - 'gateway': fake_share_network['gateway'], + 'network_type': fake_share_network_subnet['network_type'], + 'segmentation_id': fake_share_network_subnet['segmentation_id'], + 'ip_version': fake_share_network_subnet['ip_version'], + 'cidr': fake_share_network_subnet['cidr'], + 'gateway': fake_share_network_subnet['gateway'], 'mtu': 1509, } @@ -230,17 +235,18 @@ class NeutronNetworkPluginTest(test.TestCase): self.fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet, allocation_info={'count': 1}) has_provider_nw_ext.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) self.plugin.neutron_api.create_port.assert_called_once_with( fake_share_network['project_id'], - network_id=fake_share_network['neutron_net_id'], - subnet_id=fake_share_network['neutron_subnet_id'], + network_id=fake_share_network_subnet['neutron_net_id'], + subnet_id=fake_share_network_subnet['neutron_subnet_id'], device_owner='manila:share', device_id=fake_share_network['id']) db_api.network_allocation_create.assert_called_once_with( @@ -270,22 +276,22 @@ class NeutronNetworkPluginTest(test.TestCase): with mock.patch.object(self.plugin.neutron_api, 'create_port', mock.Mock(return_value=fake_neutron_port)): self.plugin.allocate_network( - self.fake_context, - fake_share_server, - fake_share_network, - count=2) + self.fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet, count=2) neutron_api_calls = [ - mock.call(fake_share_network['project_id'], - network_id=fake_share_network['neutron_net_id'], - subnet_id=fake_share_network['neutron_subnet_id'], - device_owner='manila:share', - device_id=fake_share_network['id']), - mock.call(fake_share_network['project_id'], - network_id=fake_share_network['neutron_net_id'], - subnet_id=fake_share_network['neutron_subnet_id'], - device_owner='manila:share', - device_id=fake_share_network['id']), + mock.call( + fake_share_network['project_id'], + network_id=fake_share_network_subnet['neutron_net_id'], + subnet_id=fake_share_network_subnet['neutron_subnet_id'], + device_owner='manila:share', + device_id=fake_share_network['id']), + mock.call( + fake_share_network['project_id'], + network_id=fake_share_network_subnet['neutron_net_id'], + subnet_id=fake_share_network_subnet['neutron_subnet_id'], + device_owner='manila:share', + device_id=fake_share_network['id']), ] db_api_calls = [ mock.call(self.fake_context, fake_network_allocation), @@ -342,7 +348,7 @@ class NeutronNetworkPluginTest(test.TestCase): neutron_ports[3]['fixed_ips'][0]['ip_address'] = '192.168.0.13' neutron_ports[3]['id'] = 'fake_port_id_3' - self.mock_object(self.plugin, '_verify_share_network') + self.mock_object(self.plugin, '_verify_share_network_subnet') self.mock_object(self.plugin, '_store_neutron_net_info') self.mock_object(self.plugin.neutron_api, 'list_ports', @@ -366,14 +372,15 @@ class NeutronNetworkPluginTest(test.TestCase): result = self.plugin.manage_network_allocations( self.fake_context, allocations, fake_share_server, - fake_share_network) + share_network_subnet=fake_share_network_subnet) self.assertEqual(['fd12::2000'], result) self.plugin.neutron_api.list_ports.assert_called_once_with( - network_id=fake_share_network['neutron_net_id'], + network_id=fake_share_network_subnet['neutron_net_id'], device_owner='manila:share', - fixed_ips='subnet_id=' + fake_share_network['neutron_subnet_id']) + fixed_ips='subnet_id=' + + fake_share_network_subnet['neutron_subnet_id']) db_api.network_allocation_get.assert_has_calls([ mock.call(self.fake_context, 'fake_port_id_1', read_deleted=False), @@ -385,15 +392,15 @@ class NeutronNetworkPluginTest(test.TestCase): port_dict_list = [{ 'share_server_id': fake_share_server['id'], 'ip_address': x, - 'gateway': fake_share_network['gateway'], + 'gateway': fake_share_network_subnet['gateway'], 'mac_address': fake_neutron_port['mac_address'], 'status': constants.STATUS_ACTIVE, 'label': 'user', - 'network_type': fake_share_network['network_type'], - 'segmentation_id': fake_share_network['segmentation_id'], - 'ip_version': fake_share_network['ip_version'], - 'cidr': fake_share_network['cidr'], - 'mtu': fake_share_network['mtu'], + 'network_type': fake_share_network_subnet['network_type'], + 'segmentation_id': fake_share_network_subnet['segmentation_id'], + 'ip_version': fake_share_network_subnet['ip_version'], + 'cidr': fake_share_network_subnet['cidr'], + 'mtu': fake_share_network_subnet['mtu'], } for x in ['192.168.0.11', '192.168.0.12']] if side_effect: @@ -415,11 +422,11 @@ class NeutronNetworkPluginTest(test.TestCase): port_dict_list[1], read_deleted=True) ]) - self.plugin._verify_share_network.assert_called_once_with( - fake_share_server['id'], fake_share_network) + self.plugin._verify_share_network_subnet.assert_called_once_with( + fake_share_server['id'], fake_share_network_subnet) self.plugin._store_neutron_net_info( - self.fake_context, fake_share_network) + self.fake_context, fake_share_network_subnet) def test__get_ports_respective_to_ips_multiple_fixed_ips(self): self.mock_object(plugin.LOG, 'warning') @@ -462,7 +469,8 @@ class NeutronNetworkPluginTest(test.TestCase): self.assertRaises( exception.ManageShareServerError, self.plugin.manage_network_allocations, self.fake_context, - allocations, fake_share_server, fake_share_network) + allocations, fake_share_server, fake_share_network, + fake_share_network_subnet) db_api.network_allocation_get.assert_called_once_with( self.fake_context, 'fake_port_id_1', read_deleted=False) @@ -533,7 +541,7 @@ class NeutronNetworkPluginTest(test.TestCase): {'status': constants.STATUS_ERROR}) delete_port.stop() - @mock.patch.object(db_api, 'share_network_update', mock.Mock()) + @mock.patch.object(db_api, 'share_network_subnet_update', mock.Mock()) def test_save_neutron_network_data(self): neutron_nw_info = { 'provider:network_type': 'vlan', @@ -550,16 +558,16 @@ class NeutronNetworkPluginTest(test.TestCase): 'get_network', mock.Mock(return_value=neutron_nw_info)): self.plugin._save_neutron_network_data(self.fake_context, - fake_share_network) + fake_share_network_subnet) self.plugin.neutron_api.get_network.assert_called_once_with( - fake_share_network['neutron_net_id']) - self.plugin.db.share_network_update.assert_called_once_with( + fake_share_network_subnet['neutron_net_id']) + self.plugin.db.share_network_subnet_update.assert_called_once_with( self.fake_context, - fake_share_network['id'], + fake_share_network_subnet['id'], share_nw_update_dict) - @mock.patch.object(db_api, 'share_network_update', mock.Mock()) + @mock.patch.object(db_api, 'share_network_subnet_update', mock.Mock()) def test_save_neutron_network_data_multi_segment(self): share_nw_update_dict = { 'network_type': 'vlan', @@ -577,13 +585,13 @@ class NeutronNetworkPluginTest(test.TestCase): with test_utils.create_temp_config_with_opts(config_data): self.plugin._save_neutron_network_data(self.fake_context, - fake_share_network) + fake_share_network_subnet) self.plugin.neutron_api.get_network.assert_called_once_with( - fake_share_network['neutron_net_id']) - self.plugin.db.share_network_update.assert_called_once_with( + fake_share_network_subnet['neutron_net_id']) + self.plugin.db.share_network_subnet_update.assert_called_once_with( self.fake_context, - fake_share_network['id'], + fake_share_network_subnet['id'], share_nw_update_dict) @mock.patch.object(db_api, 'share_network_update', mock.Mock()) @@ -600,7 +608,7 @@ class NeutronNetworkPluginTest(test.TestCase): with test_utils.create_temp_config_with_opts(config_data): self.assertRaises(exception.NetworkBadConfigurationException, self.plugin._save_neutron_network_data, - self.fake_context, fake_share_network) + self.fake_context, fake_share_network_subnet) @mock.patch.object(db_api, 'share_network_update', mock.Mock()) def test_save_neutron_network_data_multi_segment_without_cfg(self): @@ -609,9 +617,9 @@ class NeutronNetworkPluginTest(test.TestCase): self.assertRaises(exception.NetworkBadConfigurationException, self.plugin._save_neutron_network_data, - self.fake_context, fake_share_network) + self.fake_context, fake_share_network_subnet) - @mock.patch.object(db_api, 'share_network_update', mock.Mock()) + @mock.patch.object(db_api, 'share_network_subnet_update', mock.Mock()) def test_save_neutron_subnet_data(self): neutron_subnet_info = fake_neutron_subnet subnet_value = { @@ -624,13 +632,13 @@ class NeutronNetworkPluginTest(test.TestCase): 'get_subnet', mock.Mock(return_value=neutron_subnet_info)): self.plugin._save_neutron_subnet_data(self.fake_context, - fake_share_network) + fake_share_network_subnet) self.plugin.neutron_api.get_subnet.assert_called_once_with( - fake_share_network['neutron_subnet_id']) - self.plugin.db.share_network_update.assert_called_once_with( + fake_share_network_subnet['neutron_subnet_id']) + self.plugin.db.share_network_subnet_update.assert_called_once_with( self.fake_context, - fake_share_network['id'], + fake_share_network_subnet['id'], subnet_value) def test_has_network_provider_extension_true(self): @@ -757,13 +765,13 @@ class NeutronSingleNetworkPluginTest(test.TestCase): 'neutron_subnet_id': instance.subnet, } self.mock_object( - instance.db, 'share_network_update', + instance.db, 'share_network_subnet_update', mock.Mock(return_value='foo')) instance._update_share_network_net_data( self.context, share_network_input) - instance.db.share_network_update.assert_called_once_with( + instance.db.share_network_subnet_update.assert_called_once_with( self.context, share_network_input['id'], share_network_result) @ddt.data( @@ -793,23 +801,24 @@ class NeutronSingleNetworkPluginTest(test.TestCase): fake_neutron_port, fake_neutron_port] instance = self._get_neutron_network_plugin_instance() share_server = 'fake_share_server' - share_network = 'fake_share_network' - share_network_upd = 'updated_fake_share_network' + share_network = {'id': 'fake_share_network'} + share_network_subnet = {'id': 'fake_share_network_subnet'} + share_network_subnet_upd = {'id': 'updated_fake_share_network_subnet'} count = 2 device_owner = 'fake_device_owner' self.mock_object( instance, '_update_share_network_net_data', - mock.Mock(return_value=share_network_upd)) + mock.Mock(return_value=share_network_subnet_upd)) instance.allocate_network( - self.context, share_server, share_network, count=count, - device_owner=device_owner) + self.context, share_server, share_network, share_network_subnet, + count=count, device_owner=device_owner) instance._update_share_network_net_data.assert_called_once_with( - self.context, share_network) + self.context, share_network_subnet) plugin.NeutronNetworkPlugin.allocate_network.assert_called_once_with( - self.context, share_server, share_network_upd, count=count, - device_owner=device_owner) + self.context, share_server, share_network, + share_network_subnet_upd, count=count, device_owner=device_owner) def test_manage_network_allocations(self): allocations = ['192.168.10.10', 'fd12::2000'] @@ -819,18 +828,20 @@ class NeutronSingleNetworkPluginTest(test.TestCase): mock.Mock(return_value=['fd12::2000'])) self.mock_object( instance, '_update_share_network_net_data', - mock.Mock(return_value=fake_share_network)) + mock.Mock(return_value=fake_share_network_subnet)) result = instance.manage_network_allocations( - self.context, allocations, fake_share_server, fake_share_network) + self.context, allocations, fake_share_server, fake_share_network, + fake_share_network_subnet) self.assertEqual(['fd12::2000'], result) instance._update_share_network_net_data.assert_called_once_with( - self.context, fake_share_network) + self.context, fake_share_network_subnet) parent.assert_called_once_with( - self.context, allocations, fake_share_server, fake_share_network) + self.context, allocations, fake_share_server, fake_share_network, + fake_share_network_subnet) def test_manage_network_allocations_admin(self): allocations = ['192.168.10.10', 'fd12::2000'] @@ -846,12 +857,14 @@ class NeutronSingleNetworkPluginTest(test.TestCase): } result = instance.manage_network_allocations( - self.context, allocations, fake_share_server, None) + self.context, allocations, fake_share_server, + share_network_subnet=share_network_dict) self.assertEqual(['fd12::2000'], result) parent.assert_called_once_with( - self.context, allocations, fake_share_server, share_network_dict) + self.context, allocations, fake_share_server, None, + share_network_dict) @ddt.ddt @@ -943,18 +956,19 @@ class NeutronBindNetworkPluginTest(test.TestCase): self.fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet, allocation_info={'count': 1}) self.bind_plugin._has_provider_network_extension.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) expected_kwargs = { 'binding:vnic_type': 'baremetal', 'host_id': 'foohost1', - 'network_id': fake_share_network['neutron_net_id'], - 'subnet_id': fake_share_network['neutron_subnet_id'], + 'network_id': fake_share_network_subnet['neutron_net_id'], + 'subnet_id': fake_share_network_subnet['neutron_subnet_id'], 'device_owner': 'manila:share', 'device_id': fake_share_network['id'], } @@ -1020,13 +1034,14 @@ class NeutronBindNetworkPluginTest(test.TestCase): self.mock_object(self.bind_plugin.neutron_api, 'get_subnet') self.bind_plugin.neutron_api.get_subnet.return_value = ( fake_neutron_subnet) - self.mock_object(db_api, 'share_network_update') + self.mock_object(db_api, 'share_network_subnet_update') with mock.patch.object(self.bind_plugin.neutron_api, 'create_port', mock.Mock(return_value=fake_neutron_port)): self.bind_plugin.allocate_network( self.fake_context, fake_share_server, + fake_share_network, self.fake_share_network_multi, allocation_info={'count': 1}) @@ -1044,7 +1059,7 @@ class NeutronBindNetworkPluginTest(test.TestCase): db_api.network_allocation_create.assert_called_once_with( self.fake_context, fake_network_allocation_multi) - db_api.share_network_update.assert_called_with( + db_api.share_network_subnet_update.assert_called_with( self.fake_context, fake_share_network_multi['id'], network_update_data) @@ -1143,14 +1158,14 @@ class NeutronBindNetworkPluginTest(test.TestCase): instance = self._get_neutron_network_plugin_instance(config_data) create_args = instance._get_port_create_args(fake_share_server, - fake_share_network, + fake_share_network_subnet, fake_device_owner) expected_create_args = { 'binding:vnic_type': 'baremetal', 'host_id': fake_host_id, - 'network_id': fake_share_network['neutron_net_id'], - 'subnet_id': fake_share_network['neutron_subnet_id'], + 'network_id': fake_share_network_subnet['neutron_net_id'], + 'subnet_id': fake_share_network_subnet['neutron_subnet_id'], 'device_owner': 'manila:' + fake_device_owner, 'device_id': fake_share_server['id'] } @@ -1197,14 +1212,14 @@ class NeutronBindNetworkPluginTest(test.TestCase): instance = self._get_neutron_network_plugin_instance(config_data) create_args = instance._get_port_create_args(fake_share_server, - fake_share_network, + fake_share_network_subnet, fake_device_owner) expected_create_args = { 'binding:vnic_type': 'baremetal', 'host_id': fake_host_id, - 'network_id': fake_share_network['neutron_net_id'], - 'subnet_id': fake_share_network['neutron_subnet_id'], + 'network_id': fake_share_network_subnet['neutron_net_id'], + 'subnet_id': fake_share_network_subnet['neutron_subnet_id'], 'device_owner': 'manila:' + fake_device_owner, 'device_id': fake_share_server['id'] } @@ -1253,7 +1268,8 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase): 'port1', 'port2'] instance = self._get_neutron_network_plugin_instance() share_server = 'fake_share_server' - share_network = {'neutron_net_id': {}} + share_network = {} + share_network_subnet = {'neutron_net_id': {}} share_network_upd = {'neutron_net_id': {'upd': True}} count = 2 device_owner = 'fake_device_owner' @@ -1263,14 +1279,14 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase): self.mock_object(instance, '_wait_for_ports_bind', mock.Mock()) instance.allocate_network( - self.context, share_server, share_network, count=count, - device_owner=device_owner) + self.context, share_server, share_network, share_network_subnet, + count=count, device_owner=device_owner) instance._update_share_network_net_data.assert_called_once_with( - self.context, share_network) + self.context, share_network_subnet) plugin.NeutronNetworkPlugin.allocate_network.assert_called_once_with( - self.context, share_server, share_network_upd, count=count, - device_owner=device_owner) + self.context, share_server, share_network, share_network_upd, + count=count, device_owner=device_owner) instance._wait_for_ports_bind.assert_called_once_with( ['port1', 'port2'], share_server) @@ -1360,7 +1376,7 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase): def test___update_share_network_net_data_different_values_empty(self): instance = self._get_neutron_single_network_plugin_instance() - share_network_input = { + share_network_subnet_input = { 'id': 'fake_share_network_id', } share_network_result = { @@ -1368,14 +1384,15 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase): 'neutron_subnet_id': instance.subnet, } self.mock_object( - instance.db, 'share_network_update', + instance.db, 'share_network_subnet_update', mock.Mock(return_value='foo')) instance._update_share_network_net_data( - self.context, share_network_input) + self.context, share_network_subnet_input) - instance.db.share_network_update.assert_called_once_with( - self.context, share_network_input['id'], share_network_result) + instance.db.share_network_subnet_update.assert_called_once_with( + self.context, share_network_subnet_input['id'], + share_network_result) @ddt.data( {'n': 'fake_net_id', 's': 'bar'}, @@ -1464,18 +1481,19 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase): self.fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet, allocation_info={'count': 1}) self.bind_plugin._has_provider_network_extension.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) expected_kwargs = { 'binding:vnic_type': 'baremetal', 'host_id': 'foohost1', - 'network_id': fake_share_network['neutron_net_id'], - 'subnet_id': fake_share_network['neutron_subnet_id'], + 'network_id': fake_share_network_subnet['neutron_net_id'], + 'subnet_id': fake_share_network_subnet['neutron_subnet_id'], 'device_owner': 'manila:share', 'device_id': fake_share_network['id'], } @@ -1575,14 +1593,14 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase): instance = self._get_neutron_network_plugin_instance(config_data) create_args = instance._get_port_create_args(fake_share_server, - fake_share_network, + fake_share_network_subnet, fake_device_owner) expected_create_args = { 'binding:vnic_type': 'baremetal', 'host_id': fake_host_id, - 'network_id': fake_share_network['neutron_net_id'], - 'subnet_id': fake_share_network['neutron_subnet_id'], + 'network_id': fake_share_network_subnet['neutron_net_id'], + 'subnet_id': fake_share_network_subnet['neutron_subnet_id'], 'device_owner': 'manila:' + fake_device_owner, 'device_id': fake_share_server['id'] } @@ -1629,14 +1647,14 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase): instance = self._get_neutron_network_plugin_instance(config_data) create_args = instance._get_port_create_args(fake_share_server, - fake_share_network, + fake_share_network_subnet, fake_device_owner) expected_create_args = { 'binding:vnic_type': 'baremetal', 'host_id': fake_host_id, - 'network_id': fake_share_network['neutron_net_id'], - 'subnet_id': fake_share_network['neutron_subnet_id'], + 'network_id': fake_share_network_subnet['neutron_net_id'], + 'subnet_id': fake_share_network_subnet['neutron_subnet_id'], 'device_owner': 'manila:' + fake_device_owner, 'device_id': fake_share_server['id'] } @@ -1690,20 +1708,21 @@ class NeutronBindNetworkPluginWithNormalTypeTest(test.TestCase): self.fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet, allocation_info={'count': 1}) self.bind_plugin._has_provider_network_extension.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) expected_kwargs = { 'binding:vnic_type': 'normal', 'host_id': 'foohost1', - 'network_id': fake_share_network['neutron_net_id'], - 'subnet_id': fake_share_network['neutron_subnet_id'], + 'network_id': fake_share_network_subnet['neutron_net_id'], + 'subnet_id': fake_share_network_subnet['neutron_subnet_id'], 'device_owner': 'manila:share', - 'device_id': fake_share_network['id'], + 'device_id': fake_share_server['id'], } self.bind_plugin.neutron_api.create_port.assert_called_once_with( fake_share_network['project_id'], **expected_kwargs) @@ -1776,18 +1795,19 @@ class NeutronBindSingleNetworkPluginWithNormalTypeTest(test.TestCase): self.fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet, allocation_info={'count': 1}) self.bind_plugin._has_provider_network_extension.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network) + fake_share_network_subnet) expected_kwargs = { 'binding:vnic_type': 'normal', 'host_id': 'foohost1', - 'network_id': fake_share_network['neutron_net_id'], - 'subnet_id': fake_share_network['neutron_subnet_id'], + 'network_id': fake_share_network_subnet['neutron_net_id'], + 'subnet_id': fake_share_network_subnet['neutron_subnet_id'], 'device_owner': 'manila:share', 'device_id': fake_share_network['id'], } diff --git a/manila/tests/network/test_standalone_network_plugin.py b/manila/tests/network/test_standalone_network_plugin.py index 8f25084893..b4c98d7f31 100644 --- a/manila/tests/network/test_standalone_network_plugin.py +++ b/manila/tests/network/test_standalone_network_plugin.py @@ -32,6 +32,7 @@ fake_context = context.RequestContext( user_id='fake user', project_id='fake project', is_admin=False) fake_share_server = dict(id='fake_share_server_id') fake_share_network = dict(id='fake_share_network_id') +fake_share_network_subnet = dict(id='fake_share_network_subnet_id') @ddt.ddt @@ -305,14 +306,15 @@ class StandaloneNetworkPluginTest(test.TestCase): } with test_utils.create_temp_config_with_opts(data): instance = plugin.StandaloneNetworkPlugin() - self.mock_object(instance.db, 'share_network_update') + self.mock_object(instance.db, 'share_network_subnet_update') allocations = instance.allocate_network( - fake_context, fake_share_server, fake_share_network, count=0) + fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet, count=0) self.assertEqual([], allocations) - instance.db.share_network_update.assert_called_once_with( - fake_context, fake_share_network['id'], + instance.db.share_network_subnet_update.assert_called_once_with( + fake_context, fake_share_network_subnet['id'], dict(network_type=None, segmentation_id=None, cidr=six.text_type(instance.net.cidr), gateway=six.text_type(instance.gateway), @@ -329,14 +331,15 @@ class StandaloneNetworkPluginTest(test.TestCase): } with test_utils.create_temp_config_with_opts(data): instance = plugin.StandaloneNetworkPlugin() - self.mock_object(instance.db, 'share_network_update') + self.mock_object(instance.db, 'share_network_subnet_update') allocations = instance.allocate_network( - fake_context, fake_share_server, fake_share_network, count=0) + fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet, count=0) self.assertEqual([], allocations) - instance.db.share_network_update.assert_called_once_with( - fake_context, fake_share_network['id'], + instance.db.share_network_subnet_update.assert_called_once_with( + fake_context, fake_share_network_subnet['id'], dict(network_type=None, segmentation_id=None, cidr=six.text_type(instance.net.cidr), gateway=six.text_type(instance.gateway), @@ -354,14 +357,15 @@ class StandaloneNetworkPluginTest(test.TestCase): } with test_utils.create_temp_config_with_opts(data): instance = plugin.StandaloneNetworkPlugin() - self.mock_object(instance.db, 'share_network_update') + self.mock_object(instance.db, 'share_network_subnet_update') self.mock_object(instance.db, 'network_allocation_create') self.mock_object( instance.db, 'network_allocations_get_by_ip_address', mock.Mock(return_value=[])) allocations = instance.allocate_network( - fake_context, fake_share_server, fake_share_network) + fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet) self.assertEqual(1, len(allocations)) na_data = { @@ -372,8 +376,8 @@ class StandaloneNetworkPluginTest(test.TestCase): 'ip_version': 4, 'mtu': 1500, } - instance.db.share_network_update.assert_called_once_with( - fake_context, fake_share_network['id'], na_data) + instance.db.share_network_subnet_update.assert_called_once_with( + fake_context, fake_share_network_subnet['id'], na_data) instance.db.network_allocations_get_by_ip_address.assert_has_calls( [mock.call(fake_context, '10.0.0.2')]) instance.db.network_allocation_create.assert_called_once_with( @@ -400,14 +404,15 @@ class StandaloneNetworkPluginTest(test.TestCase): } with test_utils.create_temp_config_with_opts(data): instance = plugin.StandaloneNetworkPlugin() - self.mock_object(instance.db, 'share_network_update') + self.mock_object(instance.db, 'share_network_subnet_update') self.mock_object(instance.db, 'network_allocation_create') self.mock_object( instance.db, 'network_allocations_get_by_ip_address', mock.Mock(side_effect=fake_get_allocations_by_ip_address)) allocations = instance.allocate_network( - ctxt, fake_share_server, fake_share_network, count=2) + ctxt, fake_share_server, fake_share_network, + fake_share_network_subnet, count=2) self.assertEqual(2, len(allocations)) na_data = { @@ -418,8 +423,8 @@ class StandaloneNetworkPluginTest(test.TestCase): 'ip_version': 4, 'mtu': 1500, } - instance.db.share_network_update.assert_called_once_with( - ctxt, fake_share_network['id'], dict(**na_data)) + instance.db.share_network_subnet_update.assert_called_once_with( + ctxt, fake_share_network_subnet['id'], dict(**na_data)) instance.db.network_allocations_get_by_ip_address.assert_has_calls( [mock.call(ctxt, '10.0.0.2'), mock.call(ctxt, '10.0.0.3'), mock.call(ctxt, '10.0.0.4'), mock.call(ctxt, '10.0.0.5')]) @@ -445,7 +450,7 @@ class StandaloneNetworkPluginTest(test.TestCase): } with test_utils.create_temp_config_with_opts(data): instance = plugin.StandaloneNetworkPlugin() - self.mock_object(instance.db, 'share_network_update') + self.mock_object(instance.db, 'share_network_subnet_update') self.mock_object(instance.db, 'network_allocation_create') self.mock_object( instance.db, 'network_allocations_get_by_ip_address', @@ -454,10 +459,11 @@ class StandaloneNetworkPluginTest(test.TestCase): self.assertRaises( exception.NetworkBadConfigurationException, instance.allocate_network, - fake_context, fake_share_server, fake_share_network) + fake_context, fake_share_server, fake_share_network, + fake_share_network_subnet) - instance.db.share_network_update.assert_called_once_with( - fake_context, fake_share_network['id'], + instance.db.share_network_subnet_update.assert_called_once_with( + fake_context, fake_share_network_subnet['id'], dict(network_type=None, segmentation_id=None, cidr=six.text_type(instance.net.cidr), gateway=six.text_type(instance.gateway), @@ -484,12 +490,13 @@ class StandaloneNetworkPluginTest(test.TestCase): instance = self._setup_manage_network_allocations(label=label) if not label: - self.mock_object(instance, '_verify_share_network') - self.mock_object(instance.db, 'share_network_update') + self.mock_object(instance, '_verify_share_network_subnet') + self.mock_object(instance.db, 'share_network_subnet_update') self.mock_object(instance.db, 'network_allocation_create') result = instance.manage_network_allocations( - fake_context, allocations, fake_share_server, fake_share_network) + fake_context, allocations, fake_share_server, + fake_share_network, fake_share_network_subnet) self.assertEqual(['fd12::2000'], result) @@ -513,10 +520,10 @@ class StandaloneNetworkPluginTest(test.TestCase): data_list[1].update(network_data) if not label: - instance.db.share_network_update.assert_called_once_with( - fake_context, fake_share_network['id'], network_data) - instance._verify_share_network.assert_called_once_with( - fake_share_server['id'], fake_share_network) + instance.db.share_network_subnet_update.assert_called_once_with( + fake_context, fake_share_network_subnet['id'], network_data) + instance._verify_share_network_subnet.assert_called_once_with( + fake_share_server['id'], fake_share_network_subnet) instance.db.network_allocation_create.assert_has_calls([ mock.call(fake_context, data_list[0]), diff --git a/manila/tests/share/test_api.py b/manila/tests/share/test_api.py index a967a482db..238f1403b3 100644 --- a/manila/tests/share/test_api.py +++ b/manila/tests/share/test_api.py @@ -683,6 +683,128 @@ class ShareAPITestCase(test.TestCase): self.assertSubDictMatch(share_data, db_api.share_create.call_args[0][1]) + @ddt.data( + {'get_all_azs_return': [], 'subnet_by_az_side_effect': []}, + {'get_all_azs_return': [{'name': 'az1', 'id': 'az_id_1'}], + 'subnet_by_az_side_effect': [None]}, + {'get_all_azs_return': [{'name': 'az1', 'id': 'az_id_1'}], + 'subnet_by_az_side_effect': ['fake_sns_1']}, + {'get_all_azs_return': [{'name': 'az1', 'id': 'az_id_1'}, + {'name': 'az2', 'id': 'az_id_2'}], + 'subnet_by_az_side_effect': [None, 'fake_sns_2']} + ) + @ddt.unpack + def test__get_all_availability_zones_with_subnets( + self, get_all_azs_return, subnet_by_az_side_effect): + fake_share_network_id = 'fake_sn_id' + self.mock_object(db_api, 'availability_zone_get_all', + mock.Mock(return_value=get_all_azs_return)) + self.mock_object(db_api, + 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(side_effect=subnet_by_az_side_effect)) + expected_az_names = [] + expected_get_az_calls = [] + for index, value in enumerate(get_all_azs_return): + expected_get_az_calls.append(mock.call( + self.context, share_network_id=fake_share_network_id, + availability_zone_id=value['id'])) + if subnet_by_az_side_effect[index] is not None: + expected_az_names.append(value['name']) + + get_all_subnets = self.api._get_all_availability_zones_with_subnets + compatible_azs = get_all_subnets(self.context, fake_share_network_id) + + db_api.availability_zone_get_all.assert_called_once_with( + self.context) + db_get_azs_with_subnet = ( + db_api.share_network_subnet_get_by_availability_zone_id) + db_get_azs_with_subnet.assert_has_calls(expected_get_az_calls) + + self.assertEqual(expected_az_names, compatible_azs) + + @ddt.data( + {'availability_zones': None, 'azs_with_subnet': ['fake_az_1']}, + {'availability_zones': ['fake_az_2'], + 'azs_with_subnet': ['fake_az_2']}, + {'availability_zones': ['fake_az_1', 'faze_az_2', 'fake_az_3'], + 'azs_with_subnet': ['fake_az_3']} + ) + @ddt.unpack + def test_create_share_with_subnets(self, availability_zones, + azs_with_subnet): + share, share_data = self._setup_create_mocks() + reservation = 'fake' + self.mock_object(quota.QUOTAS, 'reserve', + mock.Mock(return_value=reservation)) + self.mock_object(self.api, '_get_all_availability_zones_with_subnets', + mock.Mock(return_value=azs_with_subnet)) + self.mock_object(quota.QUOTAS, 'commit') + self.mock_object(self.api, 'create_instance') + self.mock_object(db_api, 'share_get') + fake_share_network_id = 'fake_sn_id' + + if availability_zones: + expected_azs = ( + [az for az in availability_zones if az in azs_with_subnet]) + else: + expected_azs = azs_with_subnet + + self.api.create( + self.context, + share_data['share_proto'], + share_data['size'], + share_data['display_name'], + share_data['display_description'], + share_network_id=fake_share_network_id, + availability_zones=availability_zones) + share['status'] = constants.STATUS_CREATING + share['host'] = None + + quota.QUOTAS.reserve.assert_called_once() + get_all_azs_sns = self.api._get_all_availability_zones_with_subnets + get_all_azs_sns.assert_called_once_with( + self.context, fake_share_network_id) + quota.QUOTAS.commit.assert_called_once() + self.api.create_instance.assert_called_once_with( + self.context, share, share_network_id=fake_share_network_id, + host=None, availability_zone=None, share_group=None, + share_group_snapshot_member=None, share_type_id=None, + availability_zones=expected_azs + ) + db_api.share_get.assert_called_once() + + @ddt.data( + {'availability_zones': None, 'azs_with_subnet': []}, + {'availability_zones': ['fake_az_1'], + 'azs_with_subnet': ['fake_az_2']} + ) + @ddt.unpack + def test_create_share_with_subnets_invalid_azs(self, availability_zones, + azs_with_subnet): + share, share_data = self._setup_create_mocks() + reservation = 'fake' + self.mock_object(quota.QUOTAS, 'reserve', + mock.Mock(return_value=reservation)) + self.mock_object(self.api, '_get_all_availability_zones_with_subnets', + mock.Mock(return_value=azs_with_subnet)) + self.mock_object(quota.QUOTAS, 'commit') + self.mock_object(self.api, 'create_instance') + self.mock_object(db_api, 'share_get') + fake_share_network_id = 'fake_sn_id' + + self.assertRaises( + exception.InvalidInput, + self.api.create, + self.context, share_data['share_proto'], share_data['size'], + share_data['display_name'], share_data['display_description'], + share_network_id=fake_share_network_id, + availability_zones=availability_zones) + + quota.QUOTAS.reserve.assert_called_once() + get_all_azs_sns = self.api._get_all_availability_zones_with_subnets + get_all_azs_sns.assert_called_once_with( + self.context, fake_share_network_id) + @ddt.data( None, '', 'fake', 'nfsfake', 'cifsfake', 'glusterfsfake', 'hdfsfake') def test_create_share_invalid_protocol(self, proto): @@ -868,17 +990,31 @@ class ShareAPITestCase(test.TestCase): self.api.get_share_attributes_from_share_type, share_type) - @ddt.data('dr', 'readable', None) - def test_manage_new(self, replication_type): + @ddt.data( + {'replication_type': 'dr', 'dhss': False, 'share_server_id': None}, + {'replication_type': 'readable', 'dhss': False, + 'share_server_id': None}, + {'replication_type': None, 'dhss': False, 'share_server_id': None}, + {'replication_type': None, 'dhss': True, 'share_server_id': 'fake'} + ) + @ddt.unpack + def test_manage_new(self, replication_type, dhss, share_server_id): share_data = { 'host': 'fake', 'export_location': 'fake', 'share_proto': 'fake', 'share_type_id': 'fake', } + if dhss: + share_data['share_server_id'] = share_server_id driver_options = {} date = datetime.datetime(1, 1, 1, 1, 1, 1) timeutils.utcnow.return_value = date + fake_subnet = db_utils.create_share_network_subnet( + share_network_id='fake') + share_server = db_utils.create_share_server( + status=constants.STATUS_ACTIVE, id=share_server_id, + share_network_subnet_id=fake_subnet['id']) fake_share_data = { 'id': 'fakeid', 'status': constants.STATUS_CREATING, @@ -891,7 +1027,7 @@ class ShareAPITestCase(test.TestCase): 'create_share_from_snapshot_support': False, 'revert_to_snapshot_support': False, 'mount_snapshot_support': False, - 'driver_handles_share_servers': False, + 'driver_handles_share_servers': dhss, }, } @@ -905,6 +1041,10 @@ class ShareAPITestCase(test.TestCase): mock.Mock(return_value=share)) self.mock_object(share_types, 'get_share_type', mock.Mock(return_value=fake_type)) + self.mock_object(db_api, 'share_server_get', + mock.Mock(return_value=share_server)) + self.mock_object(db_api, 'share_network_subnet_get', + mock.Mock(return_value=fake_subnet)) self.mock_object(self.api, 'get_all', mock.Mock(return_value=[])) self.api.manage(self.context, copy.deepcopy(share_data), @@ -929,6 +1069,9 @@ class ShareAPITestCase(test.TestCase): share, fake_type, size=0, share_proto=share_data['share_proto'], host=share_data['host']) + if dhss: + share_data.update({ + 'share_network_id': fake_subnet['share_network_id']}) export_location = share_data.pop('export_location') self.api.get_all.assert_called_once_with(self.context, mock.ANY) db_api.share_create.assert_called_once_with(self.context, share_data) @@ -938,6 +1081,11 @@ class ShareAPITestCase(test.TestCase): ) self.scheduler_rpcapi.manage_share.assert_called_once_with( self.context, share['id'], driver_options, expected_request_spec) + if dhss: + db_api.share_server_get.assert_called_once_with( + self.context, share_data['share_server_id']) + db_api.share_network_subnet_get.assert_called_once_with( + self.context, share_server['share_network_subnet_id']) @ddt.data((True, exception.InvalidInput, True), (True, exception.InvalidInput, False), @@ -1387,10 +1535,14 @@ class ShareAPITestCase(test.TestCase): fake_share_network = { 'id': 'fake_net_id' } + fake_share_net_subnet = { + 'id': 'fake_subnet_id', + 'share_network_id': fake_share_network['id'] + } identifier = 'fake_identifier' values = { 'host': host, - 'share_network_id': fake_share_network['id'], + 'share_network_subnet_id': fake_share_net_subnet['id'], 'status': constants.STATUS_MANAGING, 'is_auto_deletable': False, 'identifier': identifier, @@ -1400,7 +1552,7 @@ class ShareAPITestCase(test.TestCase): 'id': 'fake_server_id', 'status': constants.STATUS_MANAGING, 'host': host, - 'share_network_id': fake_share_network['id'], + 'share_network_subnet_id': fake_share_net_subnet['id'], 'is_auto_deletable': False, 'identifier': identifier, } @@ -1419,7 +1571,7 @@ class ShareAPITestCase(test.TestCase): mock.Mock(return_value=server_managing) ) result = self.api.manage_share_server( - self.context, 'fake_identifier', host, fake_share_network, + self.context, 'fake_identifier', host, fake_share_net_subnet, {'opt1': 'val1', 'opt2': 'val2'} ) @@ -1434,7 +1586,7 @@ class ShareAPITestCase(test.TestCase): result_dict = { 'host': result['host'], - 'share_network_id': result['share_network_id'], + 'share_network_subnet_id': result['share_network_subnet_id'], 'status': result['status'], 'is_auto_deletable': result['is_auto_deletable'], 'identifier': result['identifier'], @@ -3273,26 +3425,211 @@ class ShareAPITestCase(test.TestCase): self.assertFalse(mock_db_update_call.called) self.assertFalse(mock_scheduler_rpcapi_call.called) + def test_create_share_replica_subnet_not_found(self): + request_spec = fakes.fake_replica_request_spec() + replica = request_spec['share_instance_properties'] + extra_specs = { + 'availability_zones': 'FAKE_AZ,FAKE_AZ2', + 'replication_type': constants.REPLICATION_TYPE_DR + } + share_type = db_utils.create_share_type(extra_specs=extra_specs) + share_type = db_api.share_type_get(self.context, share_type['id']) + az_name = 'FAKE_AZ' + share = db_utils.create_share( + id=replica['share_id'], replication_type='dr') + self.mock_object(db_api, 'share_replicas_get_available_active_replica', + mock.Mock(return_value=mock.Mock( + return_value={'host': 'fake_ar_host'}))) + self.mock_object(share_types, 'get_share_type', + mock.Mock(return_value=share_type)) + self.mock_object(db_api, 'availability_zone_get') + self.mock_object(db_api, + 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(return_value=None)) + + self.assertRaises(exception.InvalidShare, + self.api.create_share_replica, + self.context, + share, + availability_zone=az_name, + share_network_id='fake_id') + (db_api.share_replicas_get_available_active_replica + .assert_called_once_with(self.context, share['id'])) + self.assertTrue(share_types.get_share_type.called) + db_api.availability_zone_get.assert_called_once_with( + self.context, az_name) + self.assertTrue( + db_api.share_network_subnet_get_by_availability_zone_id.called) + + def test_create_share_replica_az_not_found(self): + request_spec = fakes.fake_replica_request_spec() + replica = request_spec['share_instance_properties'] + extra_specs = { + 'availability_zones': 'FAKE_AZ,FAKE_AZ2', + 'replication_type': constants.REPLICATION_TYPE_DR + } + share_type = db_utils.create_share_type(extra_specs=extra_specs) + share_type = db_api.share_type_get(self.context, share_type['id']) + az_name = 'FAKE_AZ' + share = db_utils.create_share( + id=replica['share_id'], replication_type='dr') + self.mock_object(db_api, 'share_replicas_get_available_active_replica', + mock.Mock(return_value=mock.Mock( + return_value={'host': 'fake_ar_host'}))) + self.mock_object(share_types, 'get_share_type', + mock.Mock(return_value=share_type)) + side_effect = exception.AvailabilityZoneNotFound(id=az_name) + self.mock_object(db_api, 'availability_zone_get', + mock.Mock(side_effect=side_effect)) + + self.assertRaises(exception.InvalidInput, + self.api.create_share_replica, + self.context, + share, + availability_zone=az_name, + share_network_id='fake_id') + (db_api.share_replicas_get_available_active_replica + .assert_called_once_with(self.context, share['id'])) + self.assertTrue(share_types.get_share_type.called) + db_api.availability_zone_get.assert_called_once_with( + self.context, az_name) + + @ddt.data( + {'availability_zones': '', 'azs_with_subnet': ['fake_az_1']}, + {'availability_zones': 'fake_az_1,fake_az_2', + 'azs_with_subnet': ['fake_az_2']} + ) + @ddt.unpack + def test_create_share_replica_azs_with_subnets(self, availability_zones, + azs_with_subnet): + request_spec = fakes.fake_replica_request_spec() + replica = request_spec['share_instance_properties'] + share_network_id = 'fake_share_network_id' + extra_specs = { + 'availability_zones': availability_zones, + 'replication_type': constants.REPLICATION_TYPE_DR + } + share_type = db_utils.create_share_type(extra_specs=extra_specs) + share_type = db_api.share_type_get(self.context, share_type['id']) + share = db_utils.create_share( + id=replica['share_id'], replication_type='dr', + share_type_id=share_type['id']) + cast_rules_to_readonly = ( + share['replication_type'] == constants.REPLICATION_TYPE_READABLE) + fake_replica = fakes.fake_replica(id=replica['id']) + fake_request_spec = fakes.fake_replica_request_spec() + + self.mock_object(db_api, 'share_replicas_get_available_active_replica', + mock.Mock(return_value={'host': 'fake_ar_host'})) + self.mock_object(share_types, 'get_share_type', + mock.Mock(return_value=share_type)) + mock_get_all_az_subnet = self.mock_object( + self.api, '_get_all_availability_zones_with_subnets', + mock.Mock(return_value=azs_with_subnet)) + + if availability_zones == '': + expected_azs = azs_with_subnet + else: + availability_zones = [ + t for t in availability_zones.split(',') if availability_zones] + expected_azs = ( + [az for az in availability_zones if az in azs_with_subnet]) + + self.mock_object( + self.api, 'create_share_instance_and_get_request_spec', + mock.Mock(return_value=(fake_request_spec, fake_replica))) + self.mock_object(db_api, 'share_replica_update') + mock_snapshot_get_all_call = self.mock_object( + db_api, 'share_snapshot_get_all_for_share', + mock.Mock(return_value=[])) + mock_sched_rpcapi_call = self.mock_object( + self.api.scheduler_rpcapi, 'create_share_replica') + + self.api.create_share_replica( + self.context, share, share_network_id=share_network_id) + + (db_api.share_replicas_get_available_active_replica + .assert_called_once_with(self.context, share['id'])) + self.assertTrue(share_types.get_share_type.called) + mock_get_all_az_subnet.assert_called_once_with( + self.context, share_network_id + ) + (self.api.create_share_instance_and_get_request_spec. + assert_called_once_with( + self.context, share, availability_zone=None, + share_network_id=share_network_id, share_type_id=share_type['id'], + availability_zones=expected_azs, + cast_rules_to_readonly=cast_rules_to_readonly)) + db_api.share_replica_update.assert_called_once() + mock_snapshot_get_all_call.assert_called_once() + mock_sched_rpcapi_call.assert_called_once() + + @ddt.data( + {'availability_zones': '', 'azs_with_subnet': []}, + {'availability_zones': 'fake_az_1,fake_az_2', + 'azs_with_subnet': ['fake_az_3']} + ) + @ddt.unpack + def test_create_share_replica_azs_with_subnets_invalid_input( + self, availability_zones, azs_with_subnet): + request_spec = fakes.fake_replica_request_spec() + replica = request_spec['share_instance_properties'] + share_network_id = 'fake_share_network_id' + extra_specs = { + 'availability_zones': availability_zones, + 'replication_type': constants.REPLICATION_TYPE_DR + } + share_type = db_utils.create_share_type(extra_specs=extra_specs) + share_type = db_api.share_type_get(self.context, share_type['id']) + share = db_utils.create_share( + id=replica['share_id'], replication_type='dr', + share_type_id=share_type['id']) + + self.mock_object(db_api, 'share_replicas_get_available_active_replica', + mock.Mock(return_value={'host': 'fake_ar_host'})) + self.mock_object(share_types, 'get_share_type', + mock.Mock(return_value=share_type)) + mock_get_all_az_subnet = self.mock_object( + self.api, '_get_all_availability_zones_with_subnets', + mock.Mock(return_value=azs_with_subnet)) + + self.assertRaises( + exception.InvalidInput, + self.api.create_share_replica, + self.context, share, share_network_id=share_network_id) + + (db_api.share_replicas_get_available_active_replica + .assert_called_once_with(self.context, share['id'])) + self.assertTrue(share_types.get_share_type.called) + mock_get_all_az_subnet.assert_called_once_with( + self.context, share_network_id + ) + @ddt.data({'has_snapshots': True, 'extra_specs': { 'replication_type': constants.REPLICATION_TYPE_DR, - }}, + }, + 'share_network_id': None}, {'has_snapshots': False, 'extra_specs': { 'availability_zones': 'FAKE_AZ,FAKE_AZ2', 'replication_type': constants.REPLICATION_TYPE_DR, - }}, + }, + 'share_network_id': None}, {'has_snapshots': True, 'extra_specs': { 'availability_zones': 'FAKE_AZ,FAKE_AZ2', 'replication_type': constants.REPLICATION_TYPE_READABLE, - }}, + }, + 'share_network_id': None}, {'has_snapshots': False, 'extra_specs': { 'replication_type': constants.REPLICATION_TYPE_READABLE, - }}) + }, + 'share_network_id': 'fake_sn_id'}) @ddt.unpack - def test_create_share_replica(self, has_snapshots, extra_specs): + def test_create_share_replica(self, has_snapshots, extra_specs, + share_network_id): request_spec = fakes.fake_replica_request_spec() replication_type = extra_specs['replication_type'] replica = request_spec['share_instance_properties'] @@ -3314,6 +3651,9 @@ class ShareAPITestCase(test.TestCase): mock.Mock(return_value={'host': 'fake_ar_host'})) self.mock_object(share_types, 'get_share_type', mock.Mock(return_value=share_type)) + self.mock_object(db_api, 'availability_zone_get') + self.mock_object(db_api, + 'share_network_subnet_get_by_availability_zone_id') self.mock_object( share_api.API, 'create_share_instance_and_get_request_spec', mock.Mock(return_value=(fake_request_spec, fake_replica))) @@ -3328,7 +3668,8 @@ class ShareAPITestCase(test.TestCase): expected_snap_instance_create_call_count = 2 if has_snapshots else 0 result = self.api.create_share_replica( - self.context, share, availability_zone='FAKE_AZ') + self.context, share, availability_zone='FAKE_AZ', + share_network_id=share_network_id) self.assertTrue(mock_sched_rpcapi_call.called) self.assertEqual(replica, result) @@ -3343,7 +3684,7 @@ class ShareAPITestCase(test.TestCase): (share_api.API.create_share_instance_and_get_request_spec. assert_called_once_with( self.context, share, availability_zone='FAKE_AZ', - share_network_id=None, share_type_id=share_type['id'], + share_network_id=share_network_id, share_type_id=share_type['id'], availability_zones=expected_azs, cast_rules_to_readonly=cast_rules_to_readonly)) diff --git a/manila/tests/share/test_manager.py b/manila/tests/share/test_manager.py index 05f84e02f9..f01d9e8383 100644 --- a/manila/tests/share/test_manager.py +++ b/manila/tests/share/test_manager.py @@ -713,8 +713,10 @@ class ShareManagerTestCase(test.TestCase): def test_create_share_instance_from_snapshot_with_server(self): """Test share can be created from snapshot if server exists.""" network = db_utils.create_share_network() + subnet = db_utils.create_share_network_subnet( + share_network_id=network['id']) server = db_utils.create_share_server( - share_network_id=network['id'], host='fake_host', + share_network_subnet_id=subnet['id'], host='fake_host', backend_details=dict(fake='fake')) parent_share = db_utils.create_share(share_network_id='net-id', share_server_id=server['id']) @@ -843,6 +845,9 @@ class ShareManagerTestCase(test.TestCase): def test_create_share_replica_with_share_server_exception(self): replica = fake_replica() + share_network_subnet = db_utils.create_share_network_subnet( + share_network_id=replica['share_network_id'], + availability_zone_id=replica['availability_zone_id']) manager.CONF.set_default('driver_handles_share_servers', True) self.mock_object(db, 'share_instance_access_copy', mock.Mock(return_value=[])) @@ -850,6 +855,9 @@ class ShareManagerTestCase(test.TestCase): mock.Mock(return_value=fake_replica(id='fake2'))) self.mock_object(db, 'share_replica_get', mock.Mock(return_value=replica)) + self.mock_object(db, + 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(return_value=share_network_subnet)) mock_replica_update_call = self.mock_object(db, 'share_replica_update') mock_driver_replica_call = self.mock_object( self.share_manager.driver, 'create_replica') @@ -871,7 +879,7 @@ class ShareManagerTestCase(test.TestCase): def test_create_share_replica_driver_error_on_creation(self): fake_access_rules = [{'id': '1'}, {'id': '2'}, {'id': '3'}] - replica = fake_replica(share_network_id='') + replica = fake_replica() replica_2 = fake_replica(id='fake2') self.mock_object(db, 'share_replica_get', mock.Mock(return_value=replica)) @@ -2038,13 +2046,16 @@ class ShareManagerTestCase(test.TestCase): """Test share can be created without share server.""" share_net = db_utils.create_share_network() + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_net['id'], + availability_zone_id=None, + ) share = db_utils.create_share(share_network_id=share_net['id']) - share_id = share['id'] def fake_setup_server(context, share_network, *args, **kwargs): return db_utils.create_share_server( - share_network_id=share_network['id'], + share_network_subnet_id=share_net_subnet['id'], host='fake_host') self.mock_object(manager.LOG, 'info') @@ -2058,18 +2069,23 @@ class ShareManagerTestCase(test.TestCase): manager.LOG.info.assert_called_with(mock.ANY, share.instance['id']) def test_create_share_instance_with_share_network_server_fail(self): - fake_share = db_utils.create_share(share_network_id='fake_sn_id', - size=1) - fake_server = { - 'id': 'fake_srv_id', - 'status': constants.STATUS_CREATING, - } + share_network = db_utils.create_share_network(id='fake_sn_id') + share_net_subnet = db_utils.create_share_network_subnet( + id='fake_sns_id', share_network_id=share_network['id'] + ) + fake_share = db_utils.create_share( + share_network_id=share_network['id'], size=1) + fake_server = db_utils.create_share_server( + id='fake_srv_id', status=constants.STATUS_CREATING) self.mock_object(db, 'share_server_create', mock.Mock(return_value=fake_server)) self.mock_object(db, 'share_instance_update', mock.Mock(return_value=fake_share.instance)) self.mock_object(db, 'share_instance_get', mock.Mock(return_value=fake_share.instance)) + self.mock_object(db, + 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(return_value=share_net_subnet)) self.mock_object(manager.LOG, 'error') def raise_share_server_not_found(*args, **kwargs): @@ -2119,7 +2135,7 @@ class ShareManagerTestCase(test.TestCase): resource_id=fake_share['id'], detail=message_field.Detail.NO_SHARE_SERVER) - def test_create_share_instance_with_share_network_not_found(self): + def test_create_share_instance_with_share_network_subnet_not_found(self): """Test creation fails if share network not found.""" self.mock_object(manager.LOG, 'error') @@ -2127,7 +2143,7 @@ class ShareManagerTestCase(test.TestCase): share = db_utils.create_share(share_network_id='fake-net-id') share_id = share['id'] self.assertRaises( - exception.ShareNetworkNotFound, + exception.ShareNetworkSubnetNotFound, self.share_manager.create_share_instance, self.context, share.instance['id'] @@ -2146,9 +2162,14 @@ class ShareManagerTestCase(test.TestCase): def test_create_share_instance_with_share_network_server_exists(self): """Test share can be created with existing share server.""" share_net = db_utils.create_share_network() + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_net['id'], + availability_zone_id=None, + ) share = db_utils.create_share(share_network_id=share_net['id']) share_srv = db_utils.create_share_server( - share_network_id=share_net['id'], host=self.share_manager.host) + share_network_subnet_id=share_net_subnet['id'], + host=self.share_manager.host) share_id = share['id'] @@ -2201,9 +2222,14 @@ class ShareManagerTestCase(test.TestCase): def test_create_share_instance_with_server_created(self): """Test share can be created and share server is created.""" share_net = db_utils.create_share_network() - share = db_utils.create_share(share_network_id=share_net['id']) + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_net['id'], + availability_zone_id=None) + share = db_utils.create_share(share_network_id=share_net['id'], + availability_zone=None) db_utils.create_share_server( - share_network_id=share_net['id'], host=self.share_manager.host, + share_network_subnet_id=share_net_subnet['id'], + host=self.share_manager.host, status=constants.STATUS_ERROR) share_id = share['id'] fake_server = { @@ -2231,11 +2257,16 @@ class ShareManagerTestCase(test.TestCase): def test_create_share_instance_update_replica_state(self): share_net = db_utils.create_share_network() + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_net['id'], + availability_zone_id=None + ) share = db_utils.create_share(share_network_id=share_net['id'], - replication_type='dr') + replication_type='dr', + availability_zone=None) db_utils.create_share_server( - share_network_id=share_net['id'], host=self.share_manager.host, - status=constants.STATUS_ERROR) + share_network_subnet_id=share_net_subnet['id'], + host=self.share_manager.host, status=constants.STATUS_ERROR) share_id = share['id'] fake_server = { 'id': 'fake_srv_id', @@ -2344,9 +2375,16 @@ class ShareManagerTestCase(test.TestCase): def test_provide_share_server_for_share_incompatible_servers(self): fake_exception = exception.ManilaException("fake") - fake_share_server = {'id': 'fake'} + fake_share_network = db_utils.create_share_network(id='fake_sn_id') + fake_share_net_subnet = db_utils.create_share_network_subnet( + id='fake_sns_id', share_network_id=fake_share_network['id'] + ) + fake_share_server = db_utils.create_share_server(id='fake') share = db_utils.create_share() + db_method_mock = self.mock_object( + db, 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(return_value=fake_share_net_subnet)) self.mock_object(db, 'share_server_get_all_by_host_and_share_net_valid', mock.Mock(return_value=[fake_share_server])) @@ -2358,7 +2396,13 @@ class ShareManagerTestCase(test.TestCase): self.assertRaises(exception.ManilaException, self.share_manager._provide_share_server_for_share, - self.context, "fake_id", share.instance) + self.context, fake_share_network['id'], + share.instance) + + db_method_mock.assert_called_once_with( + self.context, fake_share_network['id'], + availability_zone_id=share.instance.get('availability_zone_id') + ) driver_mock = self.share_manager.driver driver_method_mock = ( driver_mock.choose_share_server_compatible_with_share @@ -2374,6 +2418,7 @@ class ShareManagerTestCase(test.TestCase): def test_provide_share_server_for_share_parent_ss_not_found(self): fake_parent_id = "fake_server_id" + fake_share_network = db_utils.create_share_network(id='fake_sn_id') fake_exception = exception.ShareServerNotFound("fake") share = db_utils.create_share() fake_snapshot = { @@ -2388,14 +2433,15 @@ class ShareManagerTestCase(test.TestCase): self.assertRaises(exception.ShareServerNotFound, self.share_manager._provide_share_server_for_share, - self.context, "fake_id", share.instance, - snapshot=fake_snapshot) + self.context, fake_share_network['id'], + share.instance, snapshot=fake_snapshot) db.share_server_get.assert_called_once_with( self.context, fake_parent_id) def test_provide_share_server_for_share_parent_ss_invalid(self): fake_parent_id = "fake_server_id" + fake_share_network = db_utils.create_share_network(id='fake_sn_id') share = db_utils.create_share() fake_snapshot = { 'share': { @@ -2410,8 +2456,8 @@ class ShareManagerTestCase(test.TestCase): self.assertRaises(exception.InvalidShareServer, self.share_manager._provide_share_server_for_share, - self.context, "fake_id", share.instance, - snapshot=fake_snapshot) + self.context, fake_share_network['id'], + share.instance, snapshot=fake_snapshot) db.share_server_get.assert_called_once_with( self.context, fake_parent_id) @@ -2433,7 +2479,7 @@ class ShareManagerTestCase(test.TestCase): self.assertRaises( exception.ManilaException, self.share_manager._provide_share_server_for_share_group, - self.context, "fake_id", sg) + self.context, "fake_sn_id", "fake_sns_id", sg) driver_mock = self.share_manager.driver driver_method_mock = ( @@ -2445,7 +2491,7 @@ class ShareManagerTestCase(test.TestCase): self.assertRaises( exception.InvalidInput, self.share_manager._provide_share_server_for_share_group, - self.context, None, None) + self.context, None, None, None) def test_manage_share_driver_exception(self): self.mock_object(self.share_manager, 'driver') @@ -2951,12 +2997,13 @@ class ShareManagerTestCase(test.TestCase): def test_setup_server(self): # Setup required test data - share_server = { - 'id': 'fake_id', - 'share_network_id': 'fake_sn_id', - } metadata = {'fake_metadata_key': 'fake_metadata_value'} - share_network = {'id': 'fake_sn_id'} + share_network = db_utils.create_share_network(id='fake_sn_id') + share_net_subnet = db_utils.create_share_network_subnet( + id='fake_sns_id', share_network_id=share_network['id'] + ) + share_server = db_utils.create_share_server( + id='fake_id', share_network_subnet_id=share_net_subnet['id']) network_info = {'security_services': []} for ss_type in constants.SECURITY_SERVICES_ALLOWED_TYPES: network_info['security_services'].append({ @@ -2974,6 +3021,8 @@ class ShareManagerTestCase(test.TestCase): network_info['network_type'] = 'fake_network_type' # mock required stuff + self.mock_object(self.share_manager.db, 'share_network_subnet_get', + mock.Mock(return_value=share_net_subnet)) self.mock_object(self.share_manager.db, 'share_network_get', mock.Mock(return_value=share_network)) self.mock_object(self.share_manager.driver, 'allocate_network') @@ -2993,14 +3042,15 @@ class ShareManagerTestCase(test.TestCase): # verify results self.assertEqual(share_server, result) - self.share_manager.db.share_network_get.assert_has_calls([ - mock.call(self.context, share_server['share_network_id']), - mock.call(self.context, share_server['share_network_id']), - ]) + self.share_manager.db.share_network_get.assert_called_once_with( + self.context, share_net_subnet['share_network_id']) + self.share_manager.db.share_network_subnet_get.assert_called_once_with( + self.context, share_server['share_network_subnet']['id']) self.share_manager.driver.allocate_network.assert_called_once_with( - self.context, share_server, share_network) + self.context, share_server, share_network, + share_server['share_network_subnet']) self.share_manager._form_server_setup_info.assert_called_once_with( - self.context, share_server, share_network) + self.context, share_server, share_network, share_net_subnet) self.share_manager._validate_segmentation_id.assert_called_once_with( network_info) self.share_manager.driver.setup_server.assert_called_once_with( @@ -3025,12 +3075,14 @@ class ShareManagerTestCase(test.TestCase): def test_setup_server_server_info_not_present(self): # Setup required test data - share_server = { - 'id': 'fake_id', - 'share_network_id': 'fake_sn_id', - } metadata = {'fake_metadata_key': 'fake_metadata_value'} share_network = {'id': 'fake_sn_id'} + share_net_subnet = {'id': 'fake_sns_id', + 'share_network_id': share_network['id']} + share_server = { + 'id': 'fake_id', + 'share_network_subnet': share_net_subnet, + } network_info = { 'fake_network_info_key': 'fake_network_info_value', 'security_services': [], @@ -3039,6 +3091,8 @@ class ShareManagerTestCase(test.TestCase): server_info = {} # mock required stuff + self.mock_object(self.share_manager.db, 'share_network_subnet_get', + mock.Mock(return_value=share_net_subnet)) self.mock_object(self.share_manager.db, 'share_network_get', mock.Mock(return_value=share_network)) self.mock_object(self.share_manager, '_form_server_setup_info', @@ -3055,11 +3109,12 @@ class ShareManagerTestCase(test.TestCase): # verify results self.assertEqual(share_server, result) - self.share_manager.db.share_network_get.assert_has_calls([ - mock.call(self.context, share_server['share_network_id']), - mock.call(self.context, share_server['share_network_id'])]) + self.share_manager.db.share_network_get.assert_called_once_with( + self.context, share_net_subnet['share_network_id']) + self.share_manager.db.share_network_subnet_get.assert_called_once_with( + self.context, share_server['share_network_subnet']['id']) self.share_manager._form_server_setup_info.assert_called_once_with( - self.context, share_server, share_network) + self.context, share_server, share_network, share_net_subnet) self.share_manager.driver.setup_server.assert_called_once_with( network_info, metadata=metadata) self.share_manager.db.share_server_update.assert_called_once_with( @@ -3067,16 +3122,18 @@ class ShareManagerTestCase(test.TestCase): {'status': constants.STATUS_ACTIVE, 'identifier': share_server['id']}) self.share_manager.driver.allocate_network.assert_called_once_with( - self.context, share_server, share_network) + self.context, share_server, share_network, share_net_subnet) def setup_server_raise_exception(self, detail_data_proper): # Setup required test data - share_server = { - 'id': 'fake_id', - 'share_network_id': 'fake_sn_id', - } server_info = {'details_key': 'value'} share_network = {'id': 'fake_sn_id'} + share_net_subnet = {'id': 'fake_sns_id', + 'share_network_id': share_network['id']} + share_server = { + 'id': 'fake_id', + 'share_network_subnet': share_net_subnet + } network_info = { 'fake_network_info_key': 'fake_network_info_value', 'security_services': [], @@ -3092,6 +3149,8 @@ class ShareManagerTestCase(test.TestCase): # Mock required parameters self.mock_object(self.share_manager.db, 'share_network_get', mock.Mock(return_value=share_network)) + self.mock_object(self.share_manager.db, 'share_network_subnet_get', + mock.Mock(return_value=share_net_subnet)) self.mock_object(self.share_manager.db, 'share_server_update') for m in ['deallocate_network', 'allocate_network']: self.mock_object(self.share_manager.driver, m) @@ -3117,15 +3176,17 @@ class ShareManagerTestCase(test.TestCase): assert_called_once_with( self.context, share_server['id'], server_info)) self.share_manager._form_server_setup_info.assert_called_once_with( - self.context, share_server, share_network) + self.context, share_server, share_network, share_net_subnet) self.share_manager.db.share_server_update.assert_called_once_with( self.context, share_server['id'], {'status': constants.STATUS_ERROR}) - self.share_manager.db.share_network_get.assert_has_calls([ - mock.call(self.context, share_server['share_network_id']), - mock.call(self.context, share_server['share_network_id'])]) + self.share_manager.db.share_network_get.assert_called_once_with( + self.context, share_net_subnet['share_network_id']) + self.share_manager.db.share_network_subnet_get.assert_called_once_with( + self.context, share_server['share_network_subnet']['id']) self.share_manager.driver.allocate_network.assert_has_calls([ - mock.call(self.context, share_server, share_network)]) + mock.call(self.context, share_server, share_network, + share_net_subnet)]) self.share_manager.driver.deallocate_network.assert_has_calls([ mock.call(self.context, share_server['id'])]) @@ -3151,13 +3212,17 @@ class ShareManagerTestCase(test.TestCase): if not isinstance(d, dict): return {} return d - - share_server = {'id': 'fake', 'share_network_id': 'fake'} + share_net_subnet = db_utils.create_share_network_subnet( + id='fake_subnet_id', share_network_id='fake_share_net_id' + ) + share_server = db_utils.create_share_server( + id='fake', share_network_subnet_id=share_net_subnet['id']) details = get_server_details_from_data(data) exc_mock = mock.Mock(side_effect=exception.ManilaException(**data)) details_mock = mock.Mock(side_effect=exception.ManilaException()) - self.mock_object(self.share_manager.db, 'share_network_get', exc_mock) + self.mock_object(self.share_manager.db, 'share_network_get', + exc_mock) self.mock_object(self.share_manager.db, 'share_server_backend_details_set', details_mock) self.mock_object(self.share_manager.db, 'share_server_update') @@ -3242,28 +3307,31 @@ class ShareManagerTestCase(test.TestCase): fake_share_server = dict( id='fake_share_server_id', backend_details=dict(foo='bar')) fake_share_network = dict( + security_services='fake_security_services' + ) + fake_share_network_subnet = dict( segmentation_id='fake_segmentation_id', cidr='fake_cidr', neutron_net_id='fake_neutron_net_id', neutron_subnet_id='fake_neutron_subnet_id', - security_services='fake_security_services', network_type='fake_network_type') expected = dict( server_id=fake_share_server['id'], - segmentation_id=fake_share_network['segmentation_id'], - cidr=fake_share_network['cidr'], - neutron_net_id=fake_share_network['neutron_net_id'], - neutron_subnet_id=fake_share_network['neutron_subnet_id'], + segmentation_id=fake_share_network_subnet['segmentation_id'], + cidr=fake_share_network_subnet['cidr'], + neutron_net_id=fake_share_network_subnet['neutron_net_id'], + neutron_subnet_id=fake_share_network_subnet['neutron_subnet_id'], security_services=fake_share_network['security_services'], network_allocations=( fake_network_allocations_get_for_share_server()), admin_network_allocations=( fake_network_allocations_get_for_share_server(label='admin')), backend_details=fake_share_server['backend_details'], - network_type=fake_share_network['network_type']) + network_type=fake_share_network_subnet['network_type']) network_info = self.share_manager._form_server_setup_info( - self.context, fake_share_server, fake_share_network) + self.context, fake_share_server, fake_share_network, + fake_share_network_subnet) self.assertEqual(expected, network_info) (self.share_manager.db.network_allocations_get_for_share_server. @@ -3674,12 +3742,20 @@ class ShareManagerTestCase(test.TestCase): 'host': "fake_host", 'availability_zone_id': 'fake_az', } + fake_subnet = { + 'id': 'fake_subnet_id' + } self.mock_object( self.share_manager.db, 'share_group_get', mock.Mock(return_value=fake_group)) self.mock_object( self.share_manager.db, 'share_group_update', mock.Mock(return_value=fake_group)) + self.mock_object( + self.share_manager.db, + 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(return_value=fake_subnet) + ) self.mock_object( self.share_manager, '_provide_share_server_for_share_group', mock.Mock(return_value=({}, fake_group))) @@ -3757,7 +3833,9 @@ class ShareManagerTestCase(test.TestCase): 'share_server_id': 'fake_ss_id', 'availability_zone_id': 'fake_az', } - fake_ss = {'id': 'fake_ss_id', 'share_network_id': 'fake_sn'} + fake_sn = {'id': 'fake_sn_id'} + fake_sns = {'id': 'fake_sns_id', 'share_network_id': fake_sn['id']} + fake_ss = {'id': 'fake_ss_id', 'share_network_subnet': fake_sns} fake_snap = {'id': 'fake_snap_id', 'share_group_snapshot_members': [], 'share_group': {'share_server_id': fake_ss['id']}} self.mock_object(self.share_manager.db, 'share_group_get', @@ -3823,6 +3901,9 @@ class ShareManagerTestCase(test.TestCase): self.mock_object(self.share_manager.driver.configuration, 'safe_get', mock.Mock(return_value=True)) share_network_id = 'fake_sn' + share_network_subnet = { + 'id': 'fake_subnet_id' + } fake_group = { 'id': 'fake_id', 'source_share_group_snapshot_id': 'fake_snap_id', @@ -3837,6 +3918,9 @@ class ShareManagerTestCase(test.TestCase): mock.Mock(return_value=fake_snap)) self.mock_object(self.share_manager.db, 'share_group_update', mock.Mock(return_value=fake_group)) + self.mock_object(self.share_manager.db, + 'share_network_subnet_get_by_availability_zone_id', + mock.Mock(return_value=share_network_subnet)) self.mock_object( self.share_manager, '_provide_share_server_for_share_group', mock.Mock(return_value=({}, fake_group))) @@ -5564,6 +5648,8 @@ class ShareManagerTestCase(test.TestCase): share_server = db_utils.create_share_server(**fake_share_server) security_service = db_utils.create_security_service(**ss_data) share_network = db_utils.create_share_network() + share_net_subnet = db_utils.create_share_network_subnet( + share_network_id=share_network['id']) db.share_network_add_security_service(context.get_admin_context(), share_network['id'], security_service['id']) @@ -5577,6 +5663,10 @@ class ShareManagerTestCase(test.TestCase): db, 'share_server_get', mock.Mock(return_value=share_server)) mock_share_network_get = self.mock_object( db, 'share_network_get', mock.Mock(return_value=share_network)) + mock_share_net_subnet_get = self.mock_object( + db, 'share_network_subnet_get', mock.Mock( + return_value=share_net_subnet) + ) mock_network_allocations_get = self.mock_object( self.share_manager.driver, 'get_network_allocations_number', mock.Mock(return_value=1)) @@ -5627,7 +5717,11 @@ class ShareManagerTestCase(test.TestCase): ) mock_share_network_get.assert_called_once_with( utils.IsAMatcher(context.RequestContext), - fake_share_server['share_network_id'] + share_net_subnet['share_network_id'] + ) + mock_share_net_subnet_get.assert_called_once_with( + utils.IsAMatcher(context.RequestContext), + fake_share_server['share_network_subnet_id'] ) mock_network_allocations_get.assert_called_once_with() mock_share_server_net_info.assert_called_once_with( @@ -5636,7 +5730,8 @@ class ShareManagerTestCase(test.TestCase): ) mock_manage_network_allocations.assert_called_once_with( utils.IsAMatcher(context.RequestContext), - fake_list_network_info, share_server, share_network + fake_list_network_info, share_server, share_network, + share_net_subnet ) mock_manage_server.assert_called_once_with( utils.IsAMatcher(context.RequestContext), share_server, identifier, @@ -5673,12 +5768,19 @@ class ShareManagerTestCase(test.TestCase): identifier = 'fake_id' share_server = db_utils.create_share_server(**fake_share_server) share_network = db_utils.create_share_network() + share_network_subnet = db_utils.create_share_network_subnet( + share_network_id=share_network['id'] + ) + share_server['share_network_subnet_id'] = share_network_subnet['id'] self.share_manager.driver._admin_network_api = mock.Mock() mock_share_server_get = self.mock_object( db, 'share_server_get', mock.Mock(return_value=share_server)) mock_share_network_get = self.mock_object( db, 'share_network_get', mock.Mock(return_value=share_network)) + mock_share_net_subnet_get = self.mock_object( + db, 'share_network_subnet_get', mock.Mock( + return_value=share_network_subnet)) mock_network_allocations_get = self.mock_object( self.share_manager.driver, 'get_network_allocations_number', mock.Mock(return_value=1)) @@ -5697,7 +5799,11 @@ class ShareManagerTestCase(test.TestCase): ) mock_share_network_get.assert_called_once_with( utils.IsAMatcher(context.RequestContext), - fake_share_server['share_network_id'] + share_network_subnet['share_network_id'] + ) + mock_share_net_subnet_get.assert_called_once_with( + utils.IsAMatcher(context.RequestContext), + share_server['share_network_subnet_id'] ) mock_network_allocations_get.assert_called_once_with() mock_get_share_network_info.assert_called_once_with( @@ -5712,12 +5818,19 @@ class ShareManagerTestCase(test.TestCase): identifier = 'fake_id' share_server = db_utils.create_share_server(**fake_share_server) share_network = db_utils.create_share_network() + share_network_subnet = db_utils.create_share_network_subnet( + share_network_id=share_network['id'] + ) + share_server['share_network_subnet_id'] = share_network_subnet['id'] self.share_manager.driver._admin_network_api = mock.Mock() mock_share_server_get = self.mock_object( db, 'share_server_get', mock.Mock(return_value=share_server)) mock_share_network_get = self.mock_object( db, 'share_network_get', mock.Mock(return_value=share_network)) + mock_share_net_subnet_get = self.mock_object( + db, 'share_network_subnet_get', mock.Mock( + return_value=share_network_subnet)) mock_network_allocations_get = self.mock_object( self.share_manager.driver, 'get_network_allocations_number', mock.Mock(return_value=1)) @@ -5744,7 +5857,11 @@ class ShareManagerTestCase(test.TestCase): ) mock_share_network_get.assert_called_once_with( utils.IsAMatcher(context.RequestContext), - fake_share_server['share_network_id'] + share_network_subnet['share_network_id'] + ) + mock_share_net_subnet_get.assert_called_once_with( + utils.IsAMatcher(context.RequestContext), + share_server['share_network_subnet_id'] ) mock_network_allocations_get.assert_called_once_with() mock_get_share_network_info.assert_called_once_with( @@ -5757,7 +5874,8 @@ class ShareManagerTestCase(test.TestCase): ) mock_manage_network_allocations.assert_called_once_with( utils.IsAMatcher(context.RequestContext), - fake_list_network_info, share_server, share_network + fake_list_network_info, share_server, share_network, + share_network_subnet ) def test_manage_snapshot_driver_exception(self): diff --git a/manila/tests/test_exception.py b/manila/tests/test_exception.py index e357f43d7a..333318d28f 100644 --- a/manila/tests/test_exception.py +++ b/manila/tests/test_exception.py @@ -303,6 +303,14 @@ class ManilaExceptionResponseCode404(test.TestCase): self.assertEqual(404, e.code) self.assertIn(share_network_id, e.msg) + def test_share_network_subnet_not_found(self): + # Verify response code for exception.ShareNetworkSubnetNotFound + share_network_subnet_id = "fake_share_network_subnet_id" + e = exception.ShareNetworkSubnetNotFound( + share_network_subnet_id=share_network_subnet_id) + self.assertEqual(404, e.code) + self.assertIn(share_network_subnet_id, e.msg) + def test_share_server_not_found(self): # Verify response code for exception.ShareServerNotFound share_server_id = "fake_share_server_id" diff --git a/releasenotes/notes/share-network-with-multiple-subnets-a56be8b646b9e463.yaml b/releasenotes/notes/share-network-with-multiple-subnets-a56be8b646b9e463.yaml new file mode 100644 index 0000000000..dfc49c3009 --- /dev/null +++ b/releasenotes/notes/share-network-with-multiple-subnets-a56be8b646b9e463.yaml @@ -0,0 +1,21 @@ +--- +features: + - Added APIs with default policy set to 'rule:default' that allow the + creation of share networks with multiple subnets. This gives the users + the ability to create multiple subnets in a share network for different + availability zones. Also, users will be able to delete and show + existing subnets. + - Updated the share server API to make possible to manage share servers + in a specific subnet when the driver is operating in + ``driver_handles_share_servers`` enabled mode. + - Share servers are now associated with a single share network subnet, which + pertain to a share network. +upgrade: + - On upgrading to this release, all existing share networks will be updated + to accommodate an availability zone assignment. Existing share networks + will have their availability zone set to "empty" indicating that they are + available across all storage availability zones known to manila. +fixes: + - A share network cannot be provided while creating a share replica. Replicas + will inherit the share's share network if one exists. +