diff --git a/manila/api/common.py b/manila/api/common.py index 5bca072857..a19a379e0e 100644 --- a/manila/api/common.py +++ b/manila/api/common.py @@ -25,10 +25,12 @@ from oslo_log import log from oslo_utils import encodeutils from oslo_utils import strutils import webob +from webob import exc from manila.api.openstack import api_version_request as api_version from manila.api.openstack import versioned_method from manila.common import constants +from manila.db import api as db_api from manila import exception from manila.i18n import _ from manila import policy @@ -560,3 +562,53 @@ def validate_public_share_policy(context, api_params, api='create'): raise exception.NotAuthorized(message=message) return api_params + + +def _get_existing_subnets(context, share_network_id, az): + """Return any existing subnets in the requested AZ. + + If az is None, the method will search for an existent default subnet. + """ + if az is None: + return db_api.share_network_subnet_get_default_subnets( + context, share_network_id) + + return ( + db_api.share_network_subnets_get_all_by_availability_zone_id( + context, share_network_id, az, + fallback_to_default=False) + ) + + +def validate_subnet_create(context, share_network_id, data, + multiple_subnet_support): + + check_net_id_and_subnet_id(data) + try: + share_network = 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 = {} + 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) + data['availability_zone_id'] = subnet_az.get('id') + + existing_subnets = _get_existing_subnets( + context, share_network_id, data['availability_zone_id']) + if existing_subnets and not multiple_subnet_support: + 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) + raise exc.HTTPConflict(explanation=msg) + + return share_network, existing_subnets diff --git a/manila/api/openstack/api_version_request.py b/manila/api/openstack/api_version_request.py index 60e0fee58b..2e3684b12e 100644 --- a/manila/api/openstack/api_version_request.py +++ b/manila/api/openstack/api_version_request.py @@ -182,13 +182,17 @@ REST_API_VERSION_HISTORY = """ restore share from recycle bin. Also, a new parameter called `is_soft_deleted` was added so users can filter out shares in the recycle bin while listing shares. + * 2.70 - Added support for multiple share network subnets in the same + availability zone. Also, users can add subnets for an in-use share + network. + """ # 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.69" +_MAX_API_VERSION = "2.70" 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 90099d3998..45995459bd 100644 --- a/manila/api/openstack/rest_api_version_history.rst +++ b/manila/api/openstack/rest_api_version_history.rst @@ -384,3 +384,11 @@ ____ /v2/shares/{share_id}/action {"soft_delete": null}``. List shares in Recycle Bin: `` GET /v2/shares?is_soft_deleted=true``. Restore share from Recycle Bin: `` POST /v2/shares/{share_id}/action {'restore': null}``. + +2.70 +---- + Added support to configure multiple subnets for a given share network in the + same availability zone (or the default one). Users can also add new subnets + for an in-use share network. To distinguish this update support a new + property called 'network_allocation_update_support' was added in the share + network and share server. diff --git a/manila/api/v1/share_servers.py b/manila/api/v1/share_servers.py index f02730494f..51edd15de5 100644 --- a/manila/api/v1/share_servers.py +++ b/manila/api/v1/share_servers.py @@ -51,7 +51,6 @@ class ShareServerController(wsgi.Controller): share_servers = db_api.share_server_get_all(context) for s in share_servers: 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'] @@ -75,7 +74,9 @@ class ShareServerController(wsgi.Controller): (hasattr(s, k) and s[k] == v or k == 'share_network' and v in [s.share_network_name, - s.share_network_id])] + s.share_network_id] or + k == 'share_network_subnet_id' and + v in s.share_network_subnet_ids)] return self._view_builder.build_share_servers(req, share_servers) @wsgi.Controller.authorize @@ -85,8 +86,7 @@ class ShareServerController(wsgi.Controller): try: server = db_api.share_server_get(context, id) share_network = db_api.share_network_get( - context, server.share_network_subnet['share_network_id']) - server.share_network_id = share_network['id'] + context, server['share_network_id']) server.project_id = share_network['project_id'] if share_network['name']: server.share_network_name = share_network['name'] @@ -97,7 +97,7 @@ class ShareServerController(wsgi.Controller): except exception.ShareNetworkNotFound: msg = _("Share server %s could not be found. Its associated " "share network does not " - "exist.") % server.share_network_subnet['share_network_id'] + "exist.") % server['share_network_id'] raise exc.HTTPNotFound(explanation=msg) return self._view_builder.build_share_server(req, server) diff --git a/manila/api/v1/shares.py b/manila/api/v1/shares.py index 18c04b78cc..bd74983096 100644 --- a/manila/api/v1/shares.py +++ b/manila/api/v1/shares.py @@ -355,12 +355,17 @@ class ShareMixin(object): common.check_share_network_is_active(share_network) if availability_zone_id: - if not db.share_network_subnet_get_by_availability_zone_id( + subnets = ( + db.share_network_subnets_get_all_by_availability_zone_id( context, share_network_id, - availability_zone_id=availability_zone_id): + availability_zone_id=availability_zone_id)) + if not subnets: msg = _("A share network subnet was not found for the " "requested availability zone.") raise exc.HTTPBadRequest(explanation=msg) + kwargs['az_request_multiple_subnet_support_map'] = { + availability_zone_id: len(subnets) > 1, + } display_name = share.get('display_name') display_description = share.get('display_description') diff --git a/manila/api/v2/share_network_subnets.py b/manila/api/v2/share_network_subnets.py index 261c97a475..ae403f8ded 100644 --- a/manila/api/v2/share_network_subnets.py +++ b/manila/api/v2/share_network_subnets.py @@ -21,11 +21,13 @@ from oslo_log import log import webob from webob import exc +from manila.api.openstack import api_version_request as api_version 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 import share from manila.share import rpcapi as share_rpcapi LOG = log.getLogger(__name__) @@ -40,6 +42,7 @@ class ShareNetworkSubnetController(wsgi.Controller): def __init__(self): super(ShareNetworkSubnetController, self).__init__() self.share_rpcapi = share_rpcapi.ShareAPI() + self.share_api = share.API() @wsgi.Controller.api_version("2.51") @wsgi.Controller.authorize @@ -104,74 +107,55 @@ class ShareNetworkSubnetController(wsgi.Controller): 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 + multiple_subnet_support = (req.api_version_request >= + api_version.APIVersionRequest("2.70")) + share_network, existing_subnets = common.validate_subnet_create( + context, share_network_id, data, multiple_subnet_support) - common.check_net_id_and_subnet_id(data) + # create subnet operation on subnets with share servers means that an + # allocation update is requested. + if existing_subnets and existing_subnets[0]['share_servers']: - 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: + # NOTE(felipe_rodrigues): all subnets have the same set of share + # servers, so we can just get the servers from one of them. Not + # necessarily all share servers from the specified AZ will be + # updated, only the ones created with subnets in the AZ. Others + # created with default AZ will only have its allocations updated + # when default subnet set is updated. + data['share_servers'] = existing_subnets[0]['share_servers'] 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) + share_network_subnet = ( + self.share_api.update_share_server_network_allocations( + context, share_network, data)) + except exception.ServiceIsDown as e: + msg = _('Could not add the share network subnet.') + LOG.error(e) + raise exc.HTTPInternalServerError(explanation=msg) + except exception.InvalidShareNetwork as e: + raise exc.HTTPBadRequest(explanation=e.msg) + except db_exception.DBError as e: + msg = _('Could not add the share network subnet.') + LOG.error(e) + raise exc.HTTPInternalServerError(explanation=msg) + else: + try: + 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) - 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( diff --git a/manila/api/v2/share_networks.py b/manila/api/v2/share_networks.py index 8e70c10bf3..fd3a8d7bac 100644 --- a/manila/api/v2/share_networks.py +++ b/manila/api/v2/share_networks.py @@ -279,15 +279,23 @@ class ShareNetworkController(wsgi.Controller, wsgi.AdminActionsMixin): try: if ('neutron_net_id' in update_values or 'neutron_subnet_id' in update_values): - subnet = db_api.share_network_subnet_get_default_subnet( + subnets = db_api.share_network_subnet_get_default_subnets( context, id) - if not subnet: + if not subnets: msg = _("The share network %(id)s does not have a " "'default' subnet that serves all availability " "zones, so subnet details " "('neutron_net_id', 'neutron_subnet_id') cannot " "be updated.") % {'id': id} raise exc.HTTPBadRequest(explanation=msg) + if len(subnets) > 1: + msg = _("The share network %(id)s does not have an unique " + "'default' subnet that serves all availability " + "zones, so subnet details " + "('neutron_net_id', 'neutron_subnet_id') cannot " + "be updated.") % {'id': id} + raise exc.HTTPBadRequest(explanation=msg) + subnet = subnets[0] # NOTE(silvacarlose): If the default share network subnet have # the fields neutron_net_id and neutron_subnet_id set as None, @@ -616,6 +624,54 @@ class ShareNetworkController(wsgi.Controller, wsgi.AdminActionsMixin): return self._view_builder.build_security_service_update_check( req, data, result) + @wsgi.Controller.api_version('2.70') + @wsgi.action('share_network_subnet_create_check') + @wsgi.response(202) + def share_network_subnet_create_check(self, req, id, body): + """Check the feasibility of creating a share network subnet.""" + context = req.environ['manila.context'] + if not self.is_valid_body(body, 'share_network_subnet_create_check'): + msg = _("Share Network Subnet Create Check is missing from " + "the request body.") + raise exc.HTTPBadRequest(explanation=msg) + data = body['share_network_subnet_create_check'] + share_network, existing_subnets = common.validate_subnet_create( + context, id, data, True) + + reset_check = utils.get_bool_from_api_params('reset_operation', data) + + # create subnet operation alongside subnets with share servers means + # that an allocation update is requested. + if existing_subnets and existing_subnets[0]['share_servers']: + + # NOTE(felipe_rodrigues): all subnets within the same az have the + # same set of share servers, so we can just get the servers from + # one of them. Not necessarily all share servers from the specified + # AZ will be updated, only the ones created with subnets in the AZ. + # Others created with default AZ will only have its allocations + # updated when default subnet set is updated. + data['share_servers'] = existing_subnets[0]['share_servers'] + try: + check_result = ( + self.share_api. + check_update_share_server_network_allocations( + context, share_network, data, reset_check)) + except exception.ServiceIsDown as e: + msg = _("A share network subnet update check cannot be " + "performed at this time.") + LOG.error(e) + raise exc.HTTPInternalServerError(explanation=msg) + except exception.InvalidShareNetwork as e: + raise exc.HTTPBadRequest(explanation=e.msg) + else: + check_result = { + 'compatible': True, + 'hosts_check_result': {} + } + + return self._view_builder.build_share_network_subnet_create_check( + req, check_result) + @wsgi.Controller.api_version('2.63') @wsgi.action('reset_status') def reset_status(self, req, id, body): diff --git a/manila/api/v2/share_servers.py b/manila/api/v2/share_servers.py index 176825f321..a477d03a18 100644 --- a/manila/api/v2/share_servers.py +++ b/manila/api/v2/share_servers.py @@ -73,7 +73,6 @@ class ShareServerController(share_servers.ShareServerController, raise exc.HTTPForbidden(explanation=e.msg) result.project_id = share_network["project_id"] - result.share_network_id = share_network["id"] if share_network['name']: result.share_network_name = share_network['name'] else: @@ -107,14 +106,12 @@ class ShareServerController(share_servers.ShareServerController, except exception.ShareServerNotFound as e: raise exc.HTTPNotFound(explanation=e.msg) - network_subnet_id = share_server.get('share_network_subnet_id', None) - if network_subnet_id: - subnet = db_api.share_network_subnet_get(context, - network_subnet_id) - share_network_id = subnet['share_network_id'] - else: - share_network_id = share_server.get('share_network_id') + if len(share_server['share_network_subnets']) > 1: + msg = _("Cannot unmanage the share server containing multiple " + "subnets.") + raise exc.HTTPBadRequest(explanation=msg) + share_network_id = share_server['share_network_id'] share_network = db_api.share_network_get(context, share_network_id) common.check_share_network_is_active(share_network) @@ -169,22 +166,30 @@ class ShareServerController(share_servers.ShareServerController, 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) + network_subnets = ( + db_api.share_network_subnet_get_all_with_same_az( + 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( + network_subnets = db_api.share_network_subnet_get_default_subnets( context, share_network_id) - if network_subnet is None: + if not network_subnets: 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 len(network_subnets) > 1: + msg = _("Cannot manage the share server, since the share network " + "subnet %s has more subnets in its availability " + "zone and share network.") % network_subnet_id + raise exc.HTTPBadRequest(explanation=msg) + + network_subnet = network_subnets[0] common.check_share_network_is_active(network_subnet['share_network']) if share_utils.extract_host(host, 'pool'): @@ -260,7 +265,7 @@ class ShareServerController(share_servers.ShareServerController, common.check_share_network_is_active(new_share_network) else: share_network_id = ( - share_server['share_network_subnet']['share_network_id']) + share_server['share_network_id']) current_share_network = db_api.share_network_get( context, share_network_id) common.check_share_network_is_active(current_share_network) @@ -387,7 +392,7 @@ class ShareServerController(share_servers.ShareServerController, common.check_share_network_is_active(new_share_network) else: share_network_id = ( - share_server['share_network_subnet']['share_network_id']) + share_server['share_network_id']) current_share_network = db_api.share_network_get( context, share_network_id) common.check_share_network_is_active(current_share_network) diff --git a/manila/api/views/share_networks.py b/manila/api/views/share_networks.py index d1f6083985..2c8f5c9afd 100644 --- a/manila/api/views/share_networks.py +++ b/manila/api/views/share_networks.py @@ -22,7 +22,8 @@ class ViewBuilder(common.ViewBuilder): _collection_name = 'share_networks' _detail_version_modifiers = ["add_gateway", "add_mtu", "add_nova_net_id", "add_subnets", - "add_status_and_sec_service_update_fields"] + "add_status_and_sec_service_update_fields", + "add_network_allocation_update_support_field"] def build_share_network(self, request, share_network): """View of a share network.""" @@ -55,6 +56,16 @@ class ViewBuilder(common.ViewBuilder): view['hosts_check_result'] = result['hosts_check_result'] return view + def build_share_network_subnet_create_check(self, request, result): + """View of share network subnet create check.""" + context = request.environ['manila.context'] + view = { + 'compatible': result['compatible'], + } + if context.is_admin: + view['hosts_check_result'] = result['hosts_check_result'] + return view + 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: @@ -135,3 +146,9 @@ class ViewBuilder(common.ViewBuilder): network_dict['status'] = network.get('status') network_dict['security_service_update_support'] = network.get( 'security_service_update_support') + + @common.ViewBuilder.versioned_method("2.70") + def add_network_allocation_update_support_field( + self, context, network_dict, network): + network_dict['network_allocation_update_support'] = network.get( + 'network_allocation_update_support') diff --git a/manila/api/views/share_servers.py b/manila/api/views/share_servers.py index 8b33a4106e..8d2344ccc2 100644 --- a/manila/api/views/share_servers.py +++ b/manila/api/views/share_servers.py @@ -24,7 +24,8 @@ class ViewBuilder(common.ViewBuilder): "add_is_auto_deletable_and_identifier_fields", "add_share_network_subnet_id_field", "add_task_state_and_source_server_fields", - "add_sec_service_update_fields" + "add_sec_service_update_fields", + "add_share_network_subnet_ids_and_network_allocation_update_support" ] def build_share_server(self, request, share_server): @@ -64,11 +65,13 @@ class ViewBuilder(common.ViewBuilder): return share_server_dict - @common.ViewBuilder.versioned_method("2.51") + @common.ViewBuilder.versioned_method("2.51", "2.69") def add_share_network_subnet_id_field( self, context, share_server_dict, share_server): + """In 2.70, share_network_subnet_id is dropped, it becomes a list.""" share_server_dict['share_network_subnet_id'] = ( - share_server['share_network_subnet_id']) + share_server['share_network_subnet_ids'][0] + if share_server['share_network_subnet_ids'] else None) @common.ViewBuilder.versioned_method("2.49") def add_is_auto_deletable_and_identifier_fields( @@ -89,3 +92,11 @@ class ViewBuilder(common.ViewBuilder): self, context, share_server_dict, share_server): share_server_dict['security_service_update_support'] = share_server[ 'security_service_update_support'] + + @common.ViewBuilder.versioned_method("2.70") + def add_share_network_subnet_ids_and_network_allocation_update_support( + self, context, share_server_dict, share_server): + share_server_dict['share_network_subnet_ids'] = sorted( + share_server['share_network_subnet_ids']) + share_server_dict['network_allocation_update_support'] = ( + share_server['network_allocation_update_support']) diff --git a/manila/cmd/manage.py b/manila/cmd/manage.py index 96f62b38f7..4e6e3139c3 100644 --- a/manila/cmd/manage.py +++ b/manila/cmd/manage.py @@ -421,7 +421,8 @@ class ShareServerCommands(object): """ share_servers = [server.strip() for server in share_servers.split(",")] capabilities = [cap.strip() for cap in capabilities.split(",")] - supported_capabilities = ['security_service_update_support'] + supported_capabilities = ['security_service_update_support', + 'network_allocation_update_support'] values = dict() for capability in capabilities: diff --git a/manila/db/api.py b/manila/db/api.py index 91ae8722f7..b6878edbda 100644 --- a/manila/db/api.py +++ b/manila/db/api.py @@ -984,29 +984,45 @@ def share_network_subnet_get(context, network_subnet_id, session=None): session=session) +def share_network_subnet_get_all_with_same_az(context, network_subnet_id, + session=None): + """Get requested az share network subnets DB record.""" + return IMPL.share_network_subnet_get_all_with_same_az( + 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. +def share_network_subnets_get_all_by_availability_zone_id( + context, share_network_id, availability_zone_id, + fallback_to_default=True): + """Get the share network subnets DB record in a given AZ. - 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. + This method returns list of subnets 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 + subnets. If there is no subnet for a specific availability zone id and + "fallback_to_default" is True, this method will return the default share + network subnets, if it exists. """ - return IMPL.share_network_subnet_get_by_availability_zone_id( - context, share_network_id, availability_zone_id) + return IMPL.share_network_subnets_get_all_by_availability_zone_id( + context, share_network_id, availability_zone_id, + fallback_to_default=fallback_to_default) -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 share_network_subnet_get_default_subnets(context, share_network_id): + """Get the default share network subnets DB records.""" + return IMPL.share_network_subnet_get_default_subnets(context, + share_network_id) + + +def share_network_subnet_get_all_by_share_server_id(context, share_server_id): + """Get the subnets that are being used by the share server.""" + return IMPL.share_network_subnet_get_all_by_share_server_id( + context, share_server_id) ################## @@ -1035,10 +1051,12 @@ def network_allocation_get(context, id, session=None, read_deleted=None): def network_allocations_get_for_share_server(context, share_server_id, - session=None, label=None): + session=None, label=None, + subnet_id=None): """Get network allocations for share server.""" return IMPL.network_allocations_get_for_share_server( - context, share_server_id, label=label, session=session) + context, share_server_id, label=label, session=session, + subnet_id=subnet_id) def network_allocations_get_by_ip_address(context, ip_address): @@ -1077,6 +1095,7 @@ def share_server_search_by_identifier(context, identifier, session=None): def share_server_get_all_by_host_and_share_subnet_valid(context, host, share_subnet_id, + server_status=None, session=None): """Get share server DB records by host and share net not error.""" return IMPL.share_server_get_all_by_host_and_share_subnet_valid( diff --git a/manila/db/migrations/alembic/versions/a87e0fb17dee_multiple_share_server_subnets.py b/manila/db/migrations/alembic/versions/a87e0fb17dee_multiple_share_server_subnets.py new file mode 100644 index 0000000000..198adf49de --- /dev/null +++ b/manila/db/migrations/alembic/versions/a87e0fb17dee_multiple_share_server_subnets.py @@ -0,0 +1,229 @@ +# 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. + +"""multiple share server subnets + +Revision ID: a87e0fb17dee +Revises: 1946cb97bb8d +Create Date: 2022-01-14 06:12:27.596130 + +""" + +# revision identifiers, used by Alembic. +revision = 'a87e0fb17dee' +down_revision = '1946cb97bb8d' + + +from alembic import op +from oslo_log import log +import sqlalchemy as sa + +from manila.db.migrations import utils + + +SHARE_SERVERS_TABLE = 'share_servers' +SHARE_SERVER_SUBNET_MAP_TABLE = 'share_server_share_network_subnet_mappings' +NETWORK_ALLOCATIONS_TABLE = 'network_allocations' +LOG = log.getLogger(__name__) + + +def upgrade(): + + # Create mappings table. + context = op.get_context() + mysql_dl = context.bind.dialect.name == 'mysql' + datetime_type = (sa.dialects.mysql.DATETIME(fsp=6) + if mysql_dl else sa.DateTime) + try: + share_server_fk_name = "fk_ss_sns_m_share_server_id_share_servers" + share_network_subnet_fk_name = ( + "fk_ss_sns_m_share_network_subnet_id_share_network_subnets") + server_subnet_mappings_table = op.create_table( + SHARE_SERVER_SUBNET_MAP_TABLE, + sa.Column('id', sa.Integer, primary_key=True, nullable=False), + sa.Column('created_at', datetime_type), + sa.Column('updated_at', datetime_type), + sa.Column('deleted_at', datetime_type), + sa.Column('deleted', sa.Integer, default=0), + sa.Column( + 'share_server_id', sa.String(length=36), + sa.ForeignKey('share_servers.id', name=share_server_fk_name), + nullable=False), + sa.Column( + 'share_network_subnet_id', sa.String(length=36), + sa.ForeignKey('share_network_subnets.id', + name=share_network_subnet_fk_name), + nullable=False), + mysql_engine='InnoDB', + mysql_charset='utf8') + except Exception: + LOG.error('Table %s could not be created.', + SHARE_SERVER_SUBNET_MAP_TABLE) + raise + + # Populate the mappings table from the share servers table. + try: + connection = op.get_bind() + share_servers_table = utils.load_table(SHARE_SERVERS_TABLE, connection) + server_subnet_mappings = [] + for server in connection.execute(share_servers_table.select()): + if server.share_network_subnet_id: + server_subnet_mappings.append({ + 'created_at': server.created_at, + 'updated_at': server.updated_at, + 'deleted_at': server.deleted_at, + 'deleted': 0 if server.deleted == 'False' else 1, + 'share_server_id': server.id, + 'share_network_subnet_id': server.share_network_subnet_id, + }) + op.bulk_insert(server_subnet_mappings_table, server_subnet_mappings) + except Exception: + LOG.error('Table %s could not be populated from the %s table.', + SHARE_SERVER_SUBNET_MAP_TABLE, SHARE_SERVERS_TABLE) + raise + + # add subnet id column to the allocations table. + try: + network_allocation_fk_name = ( + "fk_network_allocation_subnet_id_share_network_subnets") + op.add_column( + NETWORK_ALLOCATIONS_TABLE, + sa.Column('share_network_subnet_id', sa.String(length=36), + sa.ForeignKey('share_network_subnets.id', + name=network_allocation_fk_name)) + ) + except Exception: + LOG.error("Could not add ForeignKey column 'share_network_subnet_id'" + "to table %s.", NETWORK_ALLOCATIONS_TABLE) + raise + + # populate the allocation with its subnet id using the share server. + network_allocation_table = utils.load_table(NETWORK_ALLOCATIONS_TABLE, + connection) + for alloc in connection.execute(network_allocation_table.select()): + # admin allocations should not contain subnet id. + if alloc['label'] == 'admin': + continue + + server = connection.execute( + share_servers_table.select().where( + alloc['share_server_id'] == ( + share_servers_table.c.id))).first() + + # pylint: disable=no-value-for-parameter + op.execute(network_allocation_table.update().where( + alloc['id'] == network_allocation_table.c.id).values( + {'share_network_subnet_id': server['share_network_subnet_id']})) + + # add a new column to share_servers. + try: + op.add_column( + SHARE_SERVERS_TABLE, + sa.Column('network_allocation_update_support', sa.Boolean, + nullable=False, server_default=sa.sql.false())) + except Exception: + LOG.error("Table %s could not add column " + "'network_allocation_update_support'.", + SHARE_SERVERS_TABLE) + raise + + # drop subnet id foreign key from share servers. + try: + 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_TABLE, + type_="foreignkey") + op.drop_column(SHARE_SERVERS_TABLE, 'share_network_subnet_id') + except Exception: + LOG.error("Table %s could not drop column 'share_network_subnet_id'.", + SHARE_SERVERS_TABLE) + raise + + +def downgrade(): + """Remove share_server_share_network_subnet_mapping table and new columns. + + This method can lead to data loss because the share server can have + more than one subnet. + """ + try: + share_serves_fk_name = ( + "fk_share_servers_share_network_subnet_id_share_network_subnets") + op.add_column( + SHARE_SERVERS_TABLE, + sa.Column( + 'share_network_subnet_id', sa.String(36), + sa.ForeignKey('share_network_subnets.id', + name=share_serves_fk_name), + ) + ) + + connection = op.get_bind() + server_subnet_mappings_table = utils.load_table( + SHARE_SERVER_SUBNET_MAP_TABLE, connection) + share_servers_table = utils.load_table(SHARE_SERVERS_TABLE, + connection) + session = sa.orm.Session(bind=connection.connect()) + for server in connection.execute(share_servers_table.select()): + subnets = session.query( + server_subnet_mappings_table).filter( + server['id'] == ( + server_subnet_mappings_table.c.share_server_id)).all() + + if server['deleted'] != 'False' and len(subnets) > 1: + LOG.warning('Share server %s is not deleted and it ' + 'has more than one subnet (%s subnets), ' + 'the downgrade may cause an inconsistent ' + 'environment.', server['id'], len(subnets)) + + subnet_id = subnets[0].share_network_subnet_id if subnets else None + + # pylint: disable=no-value-for-parameter + op.execute(share_servers_table.update().where( + server['id'] == share_servers_table.c.id).values( + {'share_network_subnet_id': subnet_id})) + + session.close_all() + + except Exception: + LOG.error("'share_network_subnet_id' field in the %s table could not " + "be created and populated from %s table.", + SHARE_SERVERS_TABLE, SHARE_SERVER_SUBNET_MAP_TABLE) + raise + + try: + op.drop_table(SHARE_SERVER_SUBNET_MAP_TABLE) + except Exception: + LOG.error("Failed to drop table %s.", SHARE_SERVER_SUBNET_MAP_TABLE) + raise + + try: + op.drop_column(SHARE_SERVERS_TABLE, + 'network_allocation_update_support') + except Exception: + LOG.error("Table %s failed to drop the column " + "'network_allocation_update_support'.", SHARE_SERVERS_TABLE) + raise + + try: + network_allocation_fk_name = ( + "fk_network_allocation_subnet_id_share_network_subnets") + if connection.engine.name == 'mysql': + op.drop_constraint(network_allocation_fk_name, + NETWORK_ALLOCATIONS_TABLE, + type_="foreignkey") + op.drop_column(NETWORK_ALLOCATIONS_TABLE, 'share_network_subnet_id') + except Exception: + LOG.error("Column 'network_allocations.share_network_subnet_id' from " + "table %s failed to drop.", NETWORK_ALLOCATIONS_TABLE) + raise diff --git a/manila/db/sqlalchemy/api.py b/manila/db/sqlalchemy/api.py index f2b5083701..32e5a2e1d0 100644 --- a/manila/db/sqlalchemy/api.py +++ b/manila/db/sqlalchemy/api.py @@ -4253,6 +4253,24 @@ def share_network_subnet_get(context, network_subnet_id, session=None): return result +@require_context +def share_network_subnet_get_all_with_same_az(context, network_subnet_id, + session=None): + subnet = (_network_subnet_get_query(context, session) + .filter_by(id=network_subnet_id).subquery()) + result = (_network_subnet_get_query(context, session) + .join(subnet, subnet.c.share_network_id == + models.ShareNetworkSubnet.share_network_id) + .filter(func.coalesce(subnet.c.availability_zone_id, '0') == + func.coalesce( + models.ShareNetworkSubnet.availability_zone_id, '0')) + .all()) + if not result: + 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() @@ -4265,25 +4283,55 @@ def share_network_subnet_get_all_by_share_network(context, network_id): @require_context -def share_network_subnet_get_by_availability_zone_id( - context, share_network_id, availability_zone_id): +def share_network_subnets_get_all_by_availability_zone_id( + context, share_network_id, availability_zone_id, + fallback_to_default=True): + """Get the share network subnets DB records in a given AZ. + + This method returns list of subnets 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 + subnets. If there is no subnet for a specific availability zone id and + "fallback_to_default" is True, this method will return the default share + network subnets, if it exists. + + :param context: operation context. + :param share_network_id: the share network id to be the subnets. + :param availability_zone_id: the availability zone id to be the subnets. + :param fallback_to_default: determines in case no subnets found in the + given AZ, it will return the "default" subnets. + :return: the list of share network subnets in the AZ and share network. + """ result = (_network_subnet_get_query(context).filter_by( share_network_id=share_network_id, - availability_zone_id=availability_zone_id).first()) + availability_zone_id=availability_zone_id).all()) # If a specific subnet wasn't found, try get the default one - if availability_zone_id and not result: + if availability_zone_id and not result and fallback_to_default: return (_network_subnet_get_query(context).filter_by( share_network_id=share_network_id, - availability_zone_id=None).first()) + availability_zone_id=None).all()) return result @require_context -def share_network_subnet_get_default_subnet(context, share_network_id): - return share_network_subnet_get_by_availability_zone_id( +def share_network_subnet_get_default_subnets(context, share_network_id): + return share_network_subnets_get_all_by_availability_zone_id( context, share_network_id, availability_zone_id=None) +@require_context +def share_network_subnet_get_all_by_share_server_id(context, share_server_id): + result = (_network_subnet_get_query(context) + .filter(models.ShareNetworkSubnet.share_servers.any( + id=share_server_id)) + .all()) + if not result: + raise exception.ShareNetworkSubnetNotFoundByShareServer( + share_server_id=share_server_id) + + return result + + ################### @@ -4293,7 +4341,7 @@ def _server_get_query(context, session=None): return (model_query(context, models.ShareServer, session=session). options(joinedload('share_instances'), joinedload('network_allocations'), - joinedload('share_network_subnet'))) + joinedload('share_network_subnets'))) @require_context @@ -4314,6 +4362,12 @@ def share_server_delete(context, id): session = get_session() with session.begin(): server_ref = share_server_get(context, id, session=session) + model_query( + context, models.ShareServerShareNetworkSubnetMapping, + session=session + ).filter_by( + share_server_id=id, + ).soft_delete() share_server_backend_details_delete(context, id, session=session) server_ref.soft_delete(session=session, update_status=True) @@ -4384,12 +4438,19 @@ def share_server_search_by_identifier(context, identifier, session=None): @require_context def share_server_get_all_by_host_and_share_subnet_valid(context, host, share_subnet_id, + server_status=None, session=None): - result = (_server_get_query(context, session).filter_by(host=host) - .filter_by(share_network_subnet_id=share_subnet_id) - .filter(models.ShareServer.status.in_( - (constants.STATUS_CREATING, - constants.STATUS_ACTIVE))).all()) + query = (_server_get_query(context, session) + .filter_by(host=host) + .filter(models.ShareServer.share_network_subnets.any( + id=share_subnet_id))) + if server_status: + query.filter_by(status=server_status) + else: + query.filter(models.ShareServer.status.in_( + (constants.STATUS_CREATING, constants.STATUS_ACTIVE))) + + result = query.all() if not result: filters_description = ('share_network_subnet_id is "%(share_net_id)s",' ' host is "%(host)s" and status in' @@ -4423,9 +4484,13 @@ def share_server_get_all_with_filters(context, filters): source_share_server_id=filters.get('source_share_server_id')) if filters.get('share_network_id'): query = query.join( + models.ShareServerShareNetworkSubnetMapping, + models.ShareServerShareNetworkSubnetMapping.share_server_id == + models.ShareServer.id + ).join( models.ShareNetworkSubnet, models.ShareNetworkSubnet.id == - models.ShareServer.share_network_subnet_id + models.ShareServerShareNetworkSubnetMapping.share_network_subnet_id ).filter( models.ShareNetworkSubnet.share_network_id == filters.get('share_network_id')) @@ -4637,7 +4702,8 @@ def network_allocations_get_by_ip_address(context, ip_address): @require_context def network_allocations_get_for_share_server(context, share_server_id, - session=None, label=None): + session=None, label=None, + subnet_id=None): if session is None: session = get_session() @@ -4655,6 +4721,9 @@ def network_allocations_get_for_share_server(context, share_server_id, )) else: query = query.filter(models.NetworkAllocation.label == label) + if subnet_id: + query = query.filter( + models.NetworkAllocation.share_network_subnet_id == subnet_id) result = query.all() return result diff --git a/manila/db/sqlalchemy/models.py b/manila/db/sqlalchemy/models.py index a9070b3216..8f3778cd18 100644 --- a/manila/db/sqlalchemy/models.py +++ b/manila/db/sqlalchemy/models.py @@ -967,6 +967,17 @@ class ShareNetwork(BASE, ManilaBase): # set to True. return all(share_servers_support_updating) + @property + def network_allocation_update_support(self): + share_servers_support_updating = [] + for network_subnet in self.share_network_subnets: + for server in network_subnet['share_servers']: + share_servers_support_updating.append( + server['network_allocation_update_support']) + # NOTE(felipe_rodrigues): all share servers within this share network + # must support updating in order to have this property set to True. + return all(share_servers_support_updating) + class ShareNetworkSubnet(BASE, ManilaBase): """Represents a share network subnet used by some resources.""" @@ -990,11 +1001,19 @@ class ShareNetworkSubnet(BASE, ManilaBase): String(36), ForeignKey('availability_zones.id'), nullable=True) share_servers = orm.relationship( - "ShareServer", backref='share_network_subnet', + "ShareServer", + secondary="share_server_share_network_subnet_mappings", + backref="share_network_subnets", lazy='immediate', - primaryjoin='and_(ShareNetworkSubnet.id ' - '== ShareServer.share_network_subnet_id,' - 'ShareServer.deleted == "False")') + primaryjoin="and_(ShareNetworkSubnet.id == " + "%(cls_name)s.share_network_subnet_id, " + "%(cls_name)s.deleted == 0)" % { + "cls_name": "ShareServerShareNetworkSubnetMapping"}, + secondaryjoin='and_(' + 'ShareServer.id == ' + 'ShareServerShareNetworkSubnetMapping.share_server_id,' + 'ShareServerShareNetworkSubnetMapping.deleted == 0)' + ) _availability_zone = orm.relationship( "AvailabilityZone", @@ -1024,9 +1043,6 @@ class ShareServer(BASE, ManilaBase): __tablename__ = 'share_servers' id = Column(String(36), primary_key=True, nullable=False) deleted = Column(String(36), default='False') - 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) @@ -1035,6 +1051,8 @@ class ShareServer(BASE, ManilaBase): nullable=True) security_service_update_support = Column( Boolean, nullable=False, default=False) + network_allocation_update_support = Column( + Boolean, nullable=False, default=False) status = Column(Enum( constants.STATUS_INACTIVE, constants.STATUS_ACTIVE, constants.STATUS_ERROR, constants.STATUS_DELETING, @@ -1071,12 +1089,31 @@ class ShareServer(BASE, ManilaBase): 'ShareServerBackendDetails.share_server_id, ' 'ShareServerBackendDetails.deleted == "False")') + _share_network_subnet_ids = orm.relationship( + "ShareServerShareNetworkSubnetMapping", + lazy='immediate', + viewonly=True, + primaryjoin='and_(' + 'ShareServer.id == ' + 'ShareServerShareNetworkSubnetMapping.share_server_id,' + 'ShareServerShareNetworkSubnetMapping.deleted == 0)') + @property def backend_details(self): return {model['key']: model['value'] for model in self._backend_details} - _extra_keys = ['backend_details'] + @property + def share_network_subnet_ids(self): + return [model['share_network_subnet_id'] + for model in self._share_network_subnet_ids] + + @property + def share_network_id(self): + return (self.share_network_subnets[0]['share_network_id'] + if self.share_network_subnets else None) + + _extra_keys = ['backend_details', 'share_network_subnet_ids'] class ShareServerBackendDetails(BASE, ManilaBase): @@ -1090,6 +1127,16 @@ class ShareServerBackendDetails(BASE, ManilaBase): nullable=False) +class ShareServerShareNetworkSubnetMapping(BASE, ManilaBase): + """Represents the Share Server and Share Network Subnet mapping.""" + __tablename__ = 'share_server_share_network_subnet_mappings' + id = Column(Integer, primary_key=True) + share_server_id = Column( + String(36), ForeignKey('share_servers.id'), nullable=False) + share_network_subnet_id = Column( + String(36), ForeignKey('share_network_subnets.id'), nullable=False) + + class ShareNetworkSecurityServiceAssociation(BASE, ManilaBase): """Association table between compute_zones and compute_nodes tables.""" @@ -1121,6 +1168,10 @@ class NetworkAllocation(BASE, ManilaBase): share_server_id = Column(String(36), ForeignKey('share_servers.id'), nullable=False) + # NOTE(felipe_rodrigues): admin allocation does not have subnet. + share_network_subnet_id = Column( + String(36), ForeignKey('share_network_subnets.id'), nullable=True) + class DriverPrivateData(BASE, ManilaBase): """Represents a private data as key-value pairs for a driver.""" diff --git a/manila/exception.py b/manila/exception.py index b6b9eb6052..f936c81839 100644 --- a/manila/exception.py +++ b/manila/exception.py @@ -232,6 +232,11 @@ class ShareNetworkSubnetNotFound(NotFound): " found.") +class ShareNetworkSubnetNotFoundByShareServer(NotFound): + message = _("Share network subnet could not be found by " + "%(share_server_id)s.") + + class ShareServerNotFound(NotFound): message = _("Share server %(share_server_id)s could not be found.") @@ -241,6 +246,11 @@ class ShareServerNotFoundByFilters(ShareServerNotFound): "filters: %(filters_description)s.") +class AllocationsNotFoundForShareServer(NotFound): + message = _("No allocations found for the share server " + "%(share_server_id)s on the subnet.") + + class InvalidShareNetwork(Invalid): message = _("Invalid share network: %(reason)s") diff --git a/manila/network/__init__.py b/manila/network/__init__.py index b8b8f79698..1fb2682a63 100644 --- a/manila/network/__init__.py +++ b/manila/network/__init__.py @@ -134,3 +134,8 @@ class NetworkBaseAPI(db_base.Base, metaclass=abc.ABCMeta): "should be configured to 'True'.") raise exception.NetworkBadConfigurationException(reason=msg) return self._enabled_ip_versions + + @abc.abstractmethod + def include_network_info(self, share_network_subnet): + """Includes share-network-subnet with plugin specific data.""" + pass diff --git a/manila/network/neutron/neutron_network_plugin.py b/manila/network/neutron/neutron_network_plugin.py index 00f306e194..54c28f59d8 100644 --- a/manila/network/neutron/neutron_network_plugin.py +++ b/manila/network/neutron/neutron_network_plugin.py @@ -121,9 +121,16 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): **self._neutron_api_kwargs) return self._neutron_api - 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 include_network_info(self, share_network_subnet): + """Includes share-network-subnet with plugin specific data.""" + self._store_neutron_net_info(None, share_network_subnet, save_db=False) + + def _store_neutron_net_info(self, context, share_network_subnet, + save_db=True): + self._save_neutron_network_data(context, share_network_subnet, + save_db=save_db) + self._save_neutron_subnet_data(context, share_network_subnet, + save_db=save_db) def allocate_network(self, context, share_server, share_network=None, share_network_subnet=None, **kwargs): @@ -202,6 +209,11 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): 'cidr': share_network_subnet['cidr'], 'mtu': share_network_subnet['mtu'], } + # NOTE(felipe_rodrigues): admin plugin does not have any Manila + # share net subnet, its data is from manila configuration file. + if self.label != 'admin': + port_dict['share_network_subnet_id'] = ( + share_network_subnet['id']) # There should not be existing allocations with the same port_id. try: @@ -332,6 +344,12 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): 'cidr': share_network_subnet['cidr'], 'mtu': share_network_subnet['mtu'], } + # NOTE(felipe_rodrigues): admin plugin does not have any Manila + # share net subnet, its data is from manila configuration file. + if self.label != 'admin': + port_dict['share_network_subnet_id'] = ( + share_network_subnet['id']) + return self.db.network_allocation_create(context, port_dict) def _delete_port(self, context, port): @@ -354,7 +372,8 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): share_network_subnet['neutron_net_id']) return 'segments' in net_info - def _save_neutron_network_data(self, context, share_network_subnet): + def _save_neutron_network_data(self, context, share_network_subnet, + save_db=True): net_info = self.neutron_api.get_network( share_network_subnet['neutron_net_id']) segmentation_id = None @@ -389,11 +408,12 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): } share_network_subnet.update(provider_nw_dict) - if self.label != 'admin': + if self.label != 'admin' and save_db: self.db.share_network_subnet_update( context, share_network_subnet['id'], provider_nw_dict) - def _save_neutron_subnet_data(self, context, share_network_subnet): + def _save_neutron_subnet_data(self, context, share_network_subnet, + save_db=True): subnet_info = self.neutron_api.get_subnet( share_network_subnet['neutron_subnet_id']) @@ -404,7 +424,7 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): } share_network_subnet.update(subnet_values) - if self.label != 'admin': + if self.label != 'admin' and save_db: self.db.share_network_subnet_update( context, share_network_subnet['id'], subnet_values) @@ -576,7 +596,8 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin): "local_link_information": local_links} return arguments - def _save_neutron_network_data(self, context, share_network_subnet): + def _save_neutron_network_data(self, context, share_network_subnet, + save_db=True): """Store the Neutron network info. In case of dynamic multi segments the segment is determined while @@ -589,12 +610,14 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin): 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_subnet) + self._save_neutron_network_mtu(context, share_network_subnet, + save_db=save_db) return super(NeutronBindNetworkPlugin, self)._save_neutron_network_data( - context, share_network_subnet) + context, share_network_subnet, save_db=save_db) - def _save_neutron_network_mtu(self, context, share_network_subnet): + def _save_neutron_network_mtu(self, context, share_network_subnet, + save_db=True): """Store the Neutron network mtu. In case of dynamic multi segments only the mtu needs storing before @@ -608,7 +631,7 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin): } share_network_subnet.update(mtu_dict) - if self.label != 'admin': + if self.label != 'admin' and save_db: self.db.share_network_subnet_update( context, share_network_subnet['id'], mtu_dict) diff --git a/manila/network/standalone_network_plugin.py b/manila/network/standalone_network_plugin.py index 613f0c9d87..100473c33f 100644 --- a/manila/network/standalone_network_plugin.py +++ b/manila/network/standalone_network_plugin.py @@ -257,7 +257,11 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): 'available': len(ips)} raise exception.NetworkBadConfigurationException(reason=msg) - def _save_network_info(self, context, share_network_subnet): + def include_network_info(self, share_network_subnet): + """Includes share-network-subnet with plugin specific data.""" + self._save_network_info(None, share_network_subnet, save_db=False) + + def _save_network_info(self, context, share_network_subnet, save_db=True): """Update share-network-subnet with plugin specific data.""" data = { 'network_type': self.network_type, @@ -268,7 +272,7 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): 'mtu': self.mtu, } share_network_subnet.update(data) - if self.label != 'admin': + if self.label != 'admin' and save_db: self.db.share_network_subnet_update( context, share_network_subnet['id'], data) @@ -303,6 +307,9 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): 'ip_version': share_network_subnet['ip_version'], 'mtu': share_network_subnet['mtu'], } + if self.label != 'admin': + data['share_network_subnet_id'] = ( + share_network_subnet['id']) allocations.append( self.db.network_allocation_create(context, data)) return allocations @@ -354,6 +361,9 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): 'ip_version': share_network_subnet['ip_version'], 'mtu': share_network_subnet['mtu'], } + if self.label != 'admin': + data['share_network_subnet_id'] = ( + share_network_subnet['id']) self.db.network_allocation_create(context, data) remaining_allocations.remove(allocation) diff --git a/manila/policies/share_network.py b/manila/policies/share_network.py index 6d736acf8e..4991d91cf8 100644 --- a/manila/policies/share_network.py +++ b/manila/policies/share_network.py @@ -99,6 +99,11 @@ deprecated_share_network_reset_status = policy.DeprecatedRule( deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) +deprecated_share_network_subnet_create_check = policy.DeprecatedRule( + name=BASE_POLICY_NAME % 'subnet_create_check', + check_str=base.RULE_DEFAULT +) + share_network_policies = [ policy.DocumentedRuleDefault( @@ -284,6 +289,20 @@ share_network_policies = [ ], deprecated_rule=deprecated_share_network_get_all ), + policy.DocumentedRuleDefault( + name=BASE_POLICY_NAME % 'subnet_create_check', + check_str=base.SYSTEM_ADMIN_OR_PROJECT_MEMBER, + scope_types=['system', 'project'], + description="Check the feasibility of create a new share network " + "subnet for share network.", + operations=[ + { + 'method': 'POST', + 'path': '/share-networks/{share_network_id}/action' + } + ], + deprecated_rule=deprecated_share_network_subnet_create_check + ), ] diff --git a/manila/scheduler/filters/availability_zone.py b/manila/scheduler/filters/availability_zone.py index 21991a34bc..eeea830f71 100644 --- a/manila/scheduler/filters/availability_zone.py +++ b/manila/scheduler/filters/availability_zone.py @@ -27,9 +27,13 @@ class AvailabilityZoneFilter(base_host.BaseHostFilter): props = spec.get('resource_properties', {}) request_az_id = props.get('availability_zone_id', spec.get('availability_zone_id')) + az_request_multiple_subnet_support_map = spec.get( + 'az_request_multiple_subnet_support_map', {}) request_azs = spec.get('availability_zones') host_az_id = host_state.service['availability_zone_id'] host_az = host_state.service['availability_zone']['name'] + host_single_subnet_only = ( + not host_state.share_server_multiple_subnet_support) host_satisfied = True if request_az_id is not None: @@ -38,4 +42,15 @@ class AvailabilityZoneFilter(base_host.BaseHostFilter): if request_azs: host_satisfied = host_satisfied and host_az in request_azs + # Only validates the multiple subnet support in case it can deny the + # host: + # 1. host is satisfying the AZ + # 2. There is a map to be checked + # 3. The host does not support a multiple subnet + if (host_satisfied and az_request_multiple_subnet_support_map and + host_single_subnet_only): + host_satisfied = ( + not az_request_multiple_subnet_support_map.get(host_az_id, + False)) + return host_satisfied diff --git a/manila/scheduler/host_manager.py b/manila/scheduler/host_manager.py index 4a24666d1f..7bac0f9694 100644 --- a/manila/scheduler/host_manager.py +++ b/manila/scheduler/host_manager.py @@ -151,6 +151,8 @@ class HostState(object): self.ipv4_support = None self.ipv6_support = None self.security_service_update_support = False + self.network_allocation_update_support = False + self.share_server_multiple_subnet_support = False # PoolState for all pools self.pools = {} @@ -346,6 +348,14 @@ class HostState(object): pool_cap['security_service_update_support'] = ( self.security_service_update_support) + if 'network_allocation_update_support' not in pool_cap: + pool_cap['network_allocation_update_support'] = ( + self.network_allocation_update_support) + + if 'share_server_multiple_subnet_support' not in pool_cap: + pool_cap['share_server_multiple_subnet_support'] = ( + self.share_server_multiple_subnet_support) + if self.ipv4_support is not None: pool_cap['ipv4_support'] = self.ipv4_support @@ -377,6 +387,10 @@ class HostState(object): self.ipv6_support = capability['ipv6_support'] self.security_service_update_support = capability.get( 'security_service_update_support', False) + self.network_allocation_update_support = capability.get( + 'network_allocation_update_support', False) + self.share_server_multiple_subnet_support = capability.get( + 'share_server_multiple_subnet_support', False) def consume_from_share(self, share): """Incrementally update host state from an share.""" @@ -477,6 +491,10 @@ class PoolState(HostState): 'sg_consistent_snapshot_support') self.security_service_update_support = capability.get( 'security_service_update_support', False) + self.network_allocation_update_support = capability.get( + 'network_allocation_update_support', False) + self.share_server_multiple_subnet_support = capability.get( + 'share_server_multiple_subnet_support', False) def update_pools(self, capability): # Do nothing, since we don't have pools within pool, yet diff --git a/manila/scheduler/utils.py b/manila/scheduler/utils.py index 07ab281ce2..b5f2493b34 100644 --- a/manila/scheduler/utils.py +++ b/manila/scheduler/utils.py @@ -60,7 +60,11 @@ def generate_stats(host_state, properties): 'ipv4_support': host_state.ipv4_support, 'ipv6_support': host_state.ipv6_support, 'security_service_update_support': ( - host_state.security_service_update_support) + host_state.security_service_update_support), + 'network_allocation_update_support': ( + host_state.network_allocation_update_support), + 'share_server_multiple_subnet_support': ( + host_state.share_server_multiple_subnet_support) } host_caps = host_state.capabilities diff --git a/manila/share/api.py b/manila/share/api.py index c7ba7605fa..41d1ffc36d 100644 --- a/manila/share/api.py +++ b/manila/share/api.py @@ -93,6 +93,30 @@ def locked_security_service_update_operation(operation): return wrapped +def locked_share_server_update_allocations_operation(operation): + """Lock decorator for share server update allocations operation. + + Takes a named lock prior to executing the operation. The lock is named with + the ids of the share network and the region to be updated. + """ + + def wrapped(*args, **kwargs): + az_id = kwargs.get('availability_zone_id') + share_net_id = kwargs.get('share_network_id') + + @coordination.synchronized( + 'locked-share-server-update-allocations-operation-%(net)s-%(az)s' + % { + 'net': share_net_id, + 'az': az_id, + }) + def locked_share_server_allocations_operation(*_args, **_kwargs): + return operation(*_args, **_kwargs) + return locked_share_server_allocations_operation(*args, **kwargs) + + return wrapped + + class API(base.Base): """API for interacting with the share manager.""" @@ -105,13 +129,17 @@ class API(base.Base): def _get_all_availability_zones_with_subnets(self, context, share_network_id): - compatible_azs = [] + compatible_azs_name = [] + compatible_azs_multiple = {} for az in self.db.availability_zone_get_all(context): - if self.db.share_network_subnet_get_by_availability_zone_id( + subnets = ( + self.db.share_network_subnets_get_all_by_availability_zone_id( context, share_network_id=share_network_id, - availability_zone_id=az['id']): - compatible_azs.append(az['name']) - return compatible_azs + availability_zone_id=az['id'])) + if subnets: + compatible_azs_multiple[az['id']] = len(subnets) > 1 + compatible_azs_name.append(az['name']) + return compatible_azs_name, compatible_azs_multiple def _check_if_share_quotas_exceeded(self, context, quota_exception, share_size, operation='create'): @@ -189,7 +217,8 @@ class API(base.Base): snapshot_id=None, availability_zone=None, metadata=None, share_network_id=None, share_type=None, is_public=False, share_group_id=None, share_group_snapshot_member=None, - availability_zones=None, scheduler_hints=None): + availability_zones=None, scheduler_hints=None, + az_request_multiple_subnet_support_map=None): """Create new share.""" self._check_metadata_properties(metadata) @@ -345,13 +374,15 @@ class API(base.Base): # 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) + compatible_azs_name, compatible_azs_multiple = ( + self._get_all_availability_zones_with_subnets( + context, share_network_id)) if not availability_zones: - availability_zones = azs_with_subnet + availability_zones = compatible_azs_name else: availability_zones = ( - [az for az in availability_zones if az in azs_with_subnet]) + [az for az in availability_zones + if az in compatible_azs_name]) if not availability_zones: msg = _( "The share network is not supported within any requested " @@ -359,6 +390,12 @@ class API(base.Base): "'availability_zones' extra-spec and the availability " "zones of the share network subnets") raise exception.InvalidInput(message=msg) + if az_request_multiple_subnet_support_map: + az_request_multiple_subnet_support_map.update( + compatible_azs_multiple) + else: + az_request_multiple_subnet_support_map = ( + compatible_azs_multiple) try: share = self.db.share_create(context, options, @@ -391,7 +428,9 @@ class API(base.Base): availability_zone=availability_zone, share_group=share_group, share_group_snapshot_member=share_group_snapshot_member, share_type_id=share_type_id, availability_zones=availability_zones, - snapshot_host=snapshot_host, scheduler_hints=scheduler_hints) + snapshot_host=snapshot_host, scheduler_hints=scheduler_hints, + az_request_multiple_subnet_support_map=( + az_request_multiple_subnet_support_map)) # Retrieve the share with instance details share = self.db.share_get(context, share['id']) @@ -468,7 +507,8 @@ class API(base.Base): host=None, availability_zone=None, share_group=None, share_group_snapshot_member=None, share_type_id=None, availability_zones=None, - snapshot_host=None, scheduler_hints=None): + snapshot_host=None, scheduler_hints=None, + az_request_multiple_subnet_support_map=None): request_spec, share_instance = ( self.create_share_instance_and_get_request_spec( context, share, availability_zone=availability_zone, @@ -476,7 +516,9 @@ class API(base.Base): share_network_id=share_network_id, share_type_id=share_type_id, availability_zones=availability_zones, - snapshot_host=snapshot_host)) + snapshot_host=snapshot_host, + az_request_multiple_subnet_support_map=( + az_request_multiple_subnet_support_map))) if share_group_snapshot_member: # Inherit properties from the share_group_snapshot_member @@ -518,7 +560,8 @@ class API(base.Base): self, context, share, availability_zone=None, share_group=None, host=None, share_network_id=None, share_type_id=None, cast_rules_to_readonly=False, - availability_zones=None, snapshot_host=None): + availability_zones=None, snapshot_host=None, + az_request_multiple_subnet_support_map=None): availability_zone_id = None if availability_zone: @@ -590,6 +633,8 @@ class API(base.Base): 'share_group': share_group, 'availability_zone_id': availability_zone_id, 'availability_zones': availability_zones, + 'az_request_multiple_subnet_support_map': ( + az_request_multiple_subnet_support_map), } return request_spec, share_instance @@ -643,6 +688,7 @@ class API(base.Base): except exception.OverQuota as e: self._check_if_replica_quotas_exceeded(context, e, share['size']) + az_request_multiple_subnet_support_map = {} if share_network_id: if availability_zone: try: @@ -652,24 +698,31 @@ class API(base.Base): 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: + az_id = az.get('id') + subnets = ( + self.db. + share_network_subnets_get_all_by_availability_zone_id( + context, share_network_id, az_id)) + if not subnets: msg = _("Share replica cannot be created because the " "share network is not available within the " "specified availability zone.") raise exception.InvalidShare(message=msg) + az_request_multiple_subnet_support_map[az_id] = ( + len(subnets) > 1) 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) + compatible_azs_name, compatible_azs_multiple = ( + self._get_all_availability_zones_with_subnets( + context, share_network_id)) if not type_azs: - type_azs = azs_subnet + type_azs = compatible_azs_name else: type_azs = ( - [az for az in type_azs if az in azs_subnet]) + [az for az in type_azs if az in compatible_azs_name]) if not type_azs: msg = _( "The share network is not supported within any " @@ -677,6 +730,8 @@ class API(base.Base): "'availability_zones' extra-spec and the availability " "zones of the share network subnets") raise exception.InvalidInput(message=msg) + az_request_multiple_subnet_support_map.update( + compatible_azs_multiple) if share['replication_type'] == constants.REPLICATION_TYPE_READABLE: cast_rules_to_readonly = True @@ -690,7 +745,9 @@ class API(base.Base): share_network_id=share_network_id, share_type_id=share['instance']['share_type_id'], cast_rules_to_readonly=cast_rules_to_readonly, - availability_zones=type_azs) + availability_zones=type_azs, + az_request_multiple_subnet_support_map=( + az_request_multiple_subnet_support_map)) ) QUOTAS.commit( context, reservations, project_id=share['project_id'], @@ -860,9 +917,8 @@ class API(base.Base): if share_server['status'] != constants.STATUS_ACTIVE: msg = _("The provided share server is not active.") raise exception.InvalidShareServer(reason=msg) - 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['share_network_id'] = ( + share_server['share_network_id']) try: share_network = self.db.share_network_get( @@ -1322,7 +1378,7 @@ class API(base.Base): values = { 'host': host, - 'share_network_subnet_id': share_net_subnet['id'], + 'share_network_subnets': [share_net_subnet], 'status': constants.STATUS_MANAGING, 'is_auto_deletable': False, 'identifier': identifier, @@ -2659,11 +2715,11 @@ class API(base.Base): # we should deny this operation. dest_az = self.db.availability_zone_get( context, service['availability_zone']['name']) - compatible_subnet = ( - self.db.share_network_subnet_get_by_availability_zone_id( + compatible_subnets = ( + self.db.share_network_subnets_get_all_by_availability_zone_id( context, new_share_network_id, dest_az['id'])) - if not compatible_subnet: + if not compatible_subnets: msg = _("The share network %(network)s does not have a subnet " "that spans the destination host availability zone.") payload = {'network': new_share_network_id} @@ -2671,11 +2727,8 @@ class API(base.Base): net_changes_identified = False if new_share_network: - current_subnet = self.db.share_network_subnet_get( - context, share_server['share_network_subnet_id']) - for key in ['neutron_net_id', 'neutron_subnet_id']: - if current_subnet[key] != compatible_subnet[key]: - net_changes_identified = True + net_changes_identified = not share_utils.is_az_subnets_compatible( + share_server['share_network_subnets'], compatible_subnets) # NOTE(carloss): Refreshing the list of shares since something could've # changed from the initial list. @@ -3118,28 +3171,44 @@ class API(base.Base): 'hosts_check', new_security_service_id, current_security_service_id=current_security_service_id) - # check if there is an entry being processed + return self._do_update_validate_hosts( + context, share_network['id'], backend_hosts, update_key, + new_security_service_id=new_security_service_id, + current_security_service_id=current_security_service_id) + + def _do_update_validate_hosts( + self, context, share_network_id, + backend_hosts, update_key, new_share_network_subnet=None, + new_security_service_id=None, current_security_service_id=None): + + # check if there is an entry being processed. update_value = self.db.async_operation_data_get( - context, share_network['id'], update_key) + context, share_network_id, update_key) if not update_value: - # Create a new entry, send all asynchronous rpcs and return + # Create a new entry, send all asynchronous rpcs and return. hosts_to_validate = {} for host in backend_hosts: hosts_to_validate[host] = None self.db.async_operation_data_update( - context, share_network['id'], + context, share_network_id, {update_key: json.dumps(hosts_to_validate)}) for host in backend_hosts: - (self.share_rpcapi. - check_update_share_network_security_service( - context, host, share_network['id'], - new_security_service_id, - current_security_service_id=( - current_security_service_id))) + if new_share_network_subnet: + (self.share_rpcapi. + check_update_share_server_network_allocations( + context, host, share_network_id, + new_share_network_subnet)) + else: + (self.share_rpcapi. + check_update_share_network_security_service( + context, host, share_network_id, + new_security_service_id, + current_security_service_id=( + current_security_service_id))) return None, hosts_to_validate else: - # process current existing hosts and update them if needed + # process current existing hosts and update them if needed. current_hosts = json.loads(update_value) hosts_to_include = ( set(backend_hosts).difference(set(current_hosts.keys()))) @@ -3147,24 +3216,30 @@ class API(base.Base): for host in backend_hosts: hosts_to_validate[host] = current_hosts.get(host, None) - # Check if there is any unsupported host + # Check if there is any unsupported host. if any(hosts_to_validate[host] is False for host in backend_hosts): return False, hosts_to_validate - # Update the list of hosts to be validated + # Update the list of hosts to be validated. if hosts_to_include: self.db.async_operation_data_update( - context, share_network['id'], + context, share_network_id, {update_key: json.dumps(hosts_to_validate)}) for host in hosts_to_include: - # send asynchronous check only for new backend hosts - (self.share_rpcapi. - check_update_share_network_security_service( - context, host, share_network['id'], - new_security_service_id, - current_security_service_id=( - current_security_service_id))) + # send asynchronous check only for new backend hosts. + if new_share_network_subnet: + (self.share_rpcapi. + check_update_share_server_network_allocations( + context, host, share_network_id, + new_share_network_subnet)) + else: + (self.share_rpcapi. + check_update_share_network_security_service( + context, host, share_network_id, + new_security_service_id, + current_security_service_id=( + current_security_service_id))) return None, hosts_to_validate @@ -3190,7 +3265,6 @@ class API(base.Base): curr_sec_serv_id = ( current_security_service['id'] if current_security_service else None) - key = self.get_security_service_update_key( 'hosts_check', new_security_service['id'], current_security_service_id=curr_sec_serv_id) @@ -3306,3 +3380,251 @@ class API(base.Base): LOG.info('Security service update has been started for share network ' '%(share_net_id)s.', {'share_net_id': share_network['id']}) + + @locked_share_server_update_allocations_operation + def _share_server_update_allocations_validate_hosts( + self, context, backend_hosts, update_key, share_network_id=None, + neutron_net_id=None, neutron_subnet_id=None, + availability_zone_id=None): + + new_share_network_subnet = { + 'neutron_net_id': neutron_net_id, + 'neutron_subnet_id': neutron_subnet_id, + 'availability_zone_id': availability_zone_id, + } + return self._do_update_validate_hosts( + context, share_network_id, backend_hosts, update_key, + new_share_network_subnet=new_share_network_subnet) + + def get_share_server_update_allocations_key( + self, share_network_id, availability_zone_id): + return ('share_server_update_allocations_' + share_network_id + '_' + + str(availability_zone_id) + '_' + 'hosts_check') + + def _share_server_update_allocations_initial_checks( + self, context, share_network, share_servers): + + api_common.check_share_network_is_active(share_network) + if not share_network['network_allocation_update_support']: + msg = _("Updating network allocations is not supported on this " + "share network (%(sn_id)s) while it has shares. " + "See the capability 'network_allocation_update_support'." + ) % {"sn_id": share_network["id"]} + raise exception.InvalidShareNetwork(reason=msg) + + backend_hosts = set() + for share_server in share_servers: + share_server_id = share_server['id'] + if share_server['status'] != constants.STATUS_ACTIVE: + msg = _('The share server %(server)s in the specified ' + 'availability zone subnet is not currently ' + 'available.') % {'server': share_server_id} + raise exception.InvalidShareNetwork(reason=msg) + + # We need an admin context to validate these hosts. + admin_ctx = manila_context.get_admin_context() + # Make sure the host is in the list of available hosts. + utils.validate_service_host(admin_ctx, share_server['host']) + + # Create a set of backend hosts. + backend_hosts.add(share_server['host']) + + shares = self.db.share_get_all_by_share_server( + context, share_server_id) + shares_not_available = [ + share['id'] + for share in shares if + share['status'] != constants.STATUS_AVAILABLE] + + if shares_not_available: + msg = _("The share server (%(server_id)s) in the specified " + "availability zone subnet has some shares that are " + "not available: " + "%(share_ids)s.") % { + 'server_id': share_server_id, + 'share_ids': shares_not_available, + } + raise exception.InvalidShareNetwork(reason=msg) + + shares_rules_not_available = [ + share['id'] for share in shares if + share['instance'][ + 'access_rules_status'] != constants.STATUS_ACTIVE] + + if shares_rules_not_available: + msg = _("The share server (%(server_id)s) in the specified " + "availability zone subnet has either these shares or " + "one of their replicas or migration copies that are " + "not available: %(share_ids)s.") % { + 'server_id': share_server_id, + 'share_ids': shares_rules_not_available, + } + raise exception.InvalidShareNetwork(reason=msg) + + busy_shares = [] + for share in shares: + try: + self._check_is_share_busy(share) + except exception.ShareBusyException: + busy_shares.append(share['id']) + if busy_shares: + msg = _("The share server (%(server_id)s) in the specified " + "availability zone subnet has some shares that are " + "busy as part of an active task: " + "%(share_ids)s.") % { + 'server_id': share_server_id, + 'share_ids': busy_shares, + } + raise exception.InvalidShareNetwork(reason=msg) + + return backend_hosts + + def check_update_share_server_network_allocations( + self, context, share_network, new_share_network_subnet, + reset_operation): + + backend_hosts = self._share_server_update_allocations_initial_checks( + context, share_network, new_share_network_subnet['share_servers']) + + update_key = self.get_share_server_update_allocations_key( + share_network['id'], + new_share_network_subnet['availability_zone_id']) + if reset_operation: + self.db.async_operation_data_delete(context, share_network['id'], + update_key) + try: + compatible, hosts_info = ( + self._share_server_update_allocations_validate_hosts( + context, backend_hosts, update_key, + share_network_id=share_network['id'], + neutron_net_id=( + new_share_network_subnet.get('neutron_net_id')), + neutron_subnet_id=( + new_share_network_subnet.get('neutron_subnet_id')), + availability_zone_id=new_share_network_subnet.get( + "availability_zone_id"))) + except Exception as e: + LOG.exception(e) + # Due to an internal error, we will delete the entry. + self.db.async_operation_data_delete( + context, share_network['id'], update_key) + msg = _( + "The server's allocations cannot be updated on availability " + "zone %(zone_id)s of the share network %(share_net_id)s, " + "since at least one of its backend hosts do not support this " + "operation.") % { + 'share_net_id': share_network['id'], + 'zone_id': new_share_network_subnet['availability_zone_id']} + raise exception.InvalidShareNetwork(reason=msg) + + return { + 'compatible': compatible, + 'hosts_check_result': hosts_info + } + + def update_share_server_network_allocations( + self, context, share_network, new_share_network_subnet): + + backend_hosts = self._share_server_update_allocations_initial_checks( + context, share_network, new_share_network_subnet['share_servers']) + + update_key = self.get_share_server_update_allocations_key( + share_network['id'], + new_share_network_subnet['availability_zone_id']) + + # check if there is an entry being processed at this moment. + update_value = self.db.async_operation_data_get( + context, share_network['id'], update_key) + if not update_value: + msg = _( + 'The share network %(share_net_id)s cannot start the update ' + 'process since no check operation was found. Before starting ' + 'the update operation, a "check" operation must be triggered ' + 'to validate if all backend hosts support the provided ' + 'configuration paramaters.') % { + 'share_net_id': share_network['id'] + } + raise exception.InvalidShareNetwork(reason=msg) + + subnet_info = { + 'availability_zone_id': + new_share_network_subnet.get("availability_zone_id"), + 'neutron_net_id': + new_share_network_subnet.get('neutron_net_id'), + 'neutron_subnet_id': + new_share_network_subnet.get('neutron_subnet_id'), + } + try: + result, __ = self._share_server_update_allocations_validate_hosts( + context, backend_hosts, update_key, + share_network_id=share_network['id'], + neutron_net_id=( + new_share_network_subnet.get('neutron_net_id')), + neutron_subnet_id=( + new_share_network_subnet.get('neutron_subnet_id')), + availability_zone_id=new_share_network_subnet.get( + "availability_zone_id")) + except Exception: + # Due to an internal error, we will delete the entry. + self.db.async_operation_data_delete( + context, share_network['id'], update_key) + msg = _( + "The server's allocations cannot be updated on availability " + "zone %(zone_id)s of the share network %(share_net_id)s, " + "since an internal error occurred." + "operation.") % { + 'share_net_id': share_network['id'], + 'zone_id': subnet_info['availability_zone_id'] + } + raise exception.InvalidShareNetwork(reason=msg) + + if result is False: + msg = _( + "The server's allocations cannot be updated on availability " + "zone %(zone_id)s of the share network %(share_net_id)s, " + "since at least one of its backend hosts do not support this " + "operation.") % { + 'share_net_id': share_network['id'], + 'zone_id': subnet_info['availability_zone_id'] + } + raise exception.InvalidShareNetwork(reason=msg) + elif result is None: + msg = _( + 'Not all of the validation has been completed yet. A ' + 'validation check is in progress. This operation can be ' + 'retried.') + raise exception.InvalidShareNetwork(reason=msg) + + # change db to start the update. + self.db.share_network_update( + context, share_network['id'], + {'status': constants.STATUS_NETWORK_CHANGE}) + share_servers_ids = [ss['id'] for ss in + new_share_network_subnet['share_servers']] + self.db.share_servers_update( + context, share_servers_ids, + {'status': constants.STATUS_SERVER_NETWORK_CHANGE}) + + # create the new subnet. + new_share_network_subnet_db = self.db.share_network_subnet_create( + context, new_share_network_subnet) + + # triggering the actual update. + for backend_host in backend_hosts: + self.share_rpcapi.update_share_server_network_allocations( + context, backend_host, share_network['id'], + new_share_network_subnet_db['id']) + + # Erase db entry, since we won't need it anymore. + self.db.async_operation_data_delete( + context, share_network['id'], update_key) + + LOG.info('Share servers allocations update have been started for ' + 'share network %(share_net_id)s on its availability zone ' + '%(az_id)s with new subnet %(subnet_id)s.', + { + 'share_net_id': share_network['id'], + 'az_id': new_share_network_subnet['availability_zone_id'], + 'subnet_id': new_share_network_subnet_db['id'], + }) + return new_share_network_subnet_db diff --git a/manila/share/driver.py b/manila/share/driver.py index 9b6b954c87..1afe8270a0 100644 --- a/manila/share/driver.py +++ b/manila/share/driver.py @@ -277,6 +277,10 @@ class ShareDriver(object): # in-use share networks. This property will be saved in every new share # server. self.security_service_update_support = False + # Indicates whether a driver supports adding subnet with its + # allocations to an in-use share network availability zone. This + # property will be saved in every new share server. + self.network_allocation_update_support = False self.dhss_mandatory_security_service_association = {} self.pools = [] @@ -1323,6 +1327,9 @@ class ShareDriver(object): goodness_function=self.get_goodness_function(), security_service_update_support=( self.security_service_update_support), + network_allocation_update_support=( + self.network_allocation_update_support), + share_server_multiple_subnet_support=False, ) if isinstance(data, dict): common.update(data) @@ -3341,3 +3348,265 @@ class ShareDriver(object): otherwise. """ raise NotImplementedError() + + def check_update_share_server_network_allocations( + self, context, share_server, current_network_allocations, + new_share_network_subnet, security_services, share_instances, + share_instances_rules): + """"Check if the share server network allocation update is supported. + + :param context: The 'context.RequestContext' object for the request. + :param share_server: Reference to the share server object that will be + updated. + :param current_network_allocations: All network allocations associated + with the share server that will be updated: + + Example:: + + { + 'admin_network_allocations': + [ + { + 'ip_address': '10.193.154.11', + 'ip_version': 4, + 'cidr': '10.193.154.0/28', + 'gateway': '10.193.154.1', + 'mtu': 1500, + 'network_type': 'vlan', + 'segmentation_id': 3000, + 'mac_address': ' AA:AA:AA:AA:AA:AA', + ... + }, + ], + 'subnets': + [ + { + 'share_network_subnet_id': '0bdeaa8c6db3-3bc10d67', + 'neutron_net_id': '2598-4122-bb62-0bdeaa8c6db3', + 'neutron_subnet_id': '3bc10d67-2598-4122-bb62', + 'network_allocations': + [ + { + 'ip_address': '10.193.154.10', + 'ip_version': 4, + 'cidr': '10.193.154.0/28', + 'gateway': '10.193.154.1', + 'mtu': 1500, + 'network_type': 'vlan', + 'segmentation_id': 3000, + 'mac_address': ' AA:AA:AA:AA:AA:AA', + ... + }, + ], + }, + ], + } + + :param new_share_network_subnet: dict containing the subnet data that + has to be checked if it can be added to the share server: + + Example:: + + { + 'availability_zone_id': '0bdeaa8c6db3-3bc10d67', + 'neutron_net_id': '2598-4122-bb62-0bdeaa8c6db3', + 'neutron_subnet_id': '3bc10d67-2598-4122-bb62', + 'ip_version': 4, + 'cidr': '10.193.154.0/28', + 'gateway': '10.193.154.1', + 'mtu': 1500, + 'network_type': 'vlan', + 'segmentation_id': 3000, + } + + :param security_services: list of security services configured with + this share server. + :param share_instances: A list of share instances that belong to the + share server that is affected by the update. + :param share_instances_rules: A list of access rules, grouped by share + instance, in the following format. + + Example:: + + [ + { + 'share_instance_id': '3bc10d67-2598-4122-bb62-0bdeaa8c6db3', + 'access_rules': + [ + { + 'access_id':'906d0094-3e34-4d6c-a184-d08a908033e3', + 'access_type':'ip', + 'access_key':None, + 'access_to':'10.0.0.1', + 'access_level':'rw' + ... + }, + ], + }, + ] + + :return Boolean indicating whether the update is possible or not. It is + the driver responsibility to log the reason why not accepting the + update. + """ + raise NotImplementedError() + + def update_share_server_network_allocations( + self, context, share_server, current_network_allocations, + new_network_allocations, security_services, shares, snapshots): + """Updates a share server's network allocations. + + :param context: The 'context.RequestContext' object for the request. + :param share_server: reference to the share server that have to update + network allocations. + :param current_network_allocations: all network allocations associated + with the share server that will be updated + + Example:: + + { + 'admin_network_allocations': + [ + { + 'ip_address': '10.193.154.11', + 'ip_version': 4, + 'cidr': '10.193.154.0/28', + 'gateway': '10.193.154.1', + 'mtu': 1500, + 'network_type': 'vlan', + 'segmentation_id': 3000, + 'mac_address': ' AA:AA:AA:AA:AA:AA', + }, + ... + ], + 'subnets': + [ + { + 'share_network_subnet_id': '0bdeaa8c6db3-3bc10d67', + 'neutron_net_id': '2598-4122-bb62-0bdeaa8c6db3', + 'neutron_subnet_id': '3bc10d67-2598-4122-bb62', + 'network_allocations': + [ + { + 'ip_address': '10.193.154.10', + 'ip_version': 4, + 'cidr': '10.193.154.0/28', + 'gateway': '10.193.154.1', + 'mtu': 1500, + 'network_type': 'vlan', + 'segmentation_id': 3000, + 'mac_address': ' AA:AA:AA:AA:AA:AA', + }, + ... + ], + }, + ], + } + + :param new_network_allocations: allocations that must be configured in + the share server. + + Example:: + + { + 'share_network_subnet_id': '0bdeaa8c6db3-3bc10d67', + 'neutron_net_id': '2598-4122-bb62-0bdeaa8c6db3', + 'neutron_subnet_id': '3bc10d67-2598-4122-bb62', + 'network_allocations': + [ + { + 'ip_address': '10.193.154.10', + 'ip_version': 4, + 'cidr': '10.193.154.0/28', + 'gateway': '10.193.154.1', + 'mtu': 1500, + 'network_type': 'vlan', + 'segmentation_id': 3000, + 'mac_address': 'AA:AA:AA:AA:AA:AA', + ... + }, + ], + }, + + :param security_services: list of security services configured with + this share server. + :param shares: All shares in the share server. + :param snapshots: All snapshots in the share server. + + :raises: Exception. + By raising an exception, the share server and all its shares and + snapshots instances will be set to 'error'. The error can contain + the field 'details_data' as a dict with the key 'server_details' + containing the backend details dict that will be saved to share + server. + + :return If the update changes the shares export locations or snapshots + export locations, this method should return a dictionary + containing a list of share instances and snapshot instances + indexed by their id's, where each instance should provide a + dict with the relevant information that need to be updated. + Also, the returned dict can contain the updated back end + details to be saved in the database. + + Example:: + + { + 'share_updates': + { + '4363eb92-23ca-4888-9e24-502387816e2a': + [ + { + 'path': '1.2.3.4:/foo', + 'metadata': {}, + 'is_admin_only': False + }, + { + 'path': '5.6.7.8:/foo', + 'metadata': {}, + 'is_admin_only': True + }, + ], + ... + }, + 'snapshot_updates': + { + 'bc4e3b28-0832-4168-b688-67fdc3e9d408': + { + 'provider_location': '/snapshots/foo/bar_1', + 'export_locations': + [ + { + 'path': '1.2.3.4:/snapshots/foo/bar_1', + 'is_admin_only': False, + }, + { + 'path': '5.6.7.8:/snapshots/foo/bar_1', + 'is_admin_only': True, + }, + ], + }, + '2e62b7ea-4e30-445f-bc05-fd523ca62941': + { + 'provider_location': '/snapshots/foo/bar_2', + 'export_locations': + [ + { + 'path': '1.2.3.4:/snapshots/foo/bar_2', + 'is_admin_only': False, + }, + { + 'path': '5.6.7.8:/snapshots/foo/bar_2', + 'is_admin_only': True, + }, + ], + }, + } + 'server_details': + { + 'new_share_server_info_key': + 'new_share_server_info_value', + }, + } + + """ + raise NotImplementedError() diff --git a/manila/share/drivers/container/driver.py b/manila/share/drivers/container/driver.py index 52a4d656b6..9831e2fc54 100644 --- a/manila/share/drivers/container/driver.py +++ b/manila/share/drivers/container/driver.py @@ -296,6 +296,8 @@ class ContainerShareDriver(driver.ShareDriver, driver.ExecuteMixin): @utils.synchronized("veth-lock", external=True) def _setup_server(self, network_info, metadata=None): + # NOTE(felipe_rodrigues): keep legacy network_info support as a dict. + network_info = network_info[0] msg = "Creating share server '%s'." server_id = self._get_container_name(network_info["server_id"]) LOG.debug(msg, server_id) diff --git a/manila/share/drivers/dell_emc/driver.py b/manila/share/drivers/dell_emc/driver.py index 9b95d6f2a3..f786eadc94 100644 --- a/manila/share/drivers/dell_emc/driver.py +++ b/manila/share/drivers/dell_emc/driver.py @@ -290,6 +290,9 @@ class EMCShareDriver(driver.ShareDriver): def _setup_server(self, network_info, metadata=None): """Set up and configures share server with given network parameters.""" + # NOTE(felipe_rodrigues): keep legacy network_info support as a dict. + network_info = network_info[0] + return self.plugin.setup_server(network_info, metadata) def _teardown_server(self, server_details, security_services=None): diff --git a/manila/share/drivers/generic.py b/manila/share/drivers/generic.py index 7ed87451c3..9d14aac824 100644 --- a/manila/share/drivers/generic.py +++ b/manila/share/drivers/generic.py @@ -892,6 +892,9 @@ class GenericShareDriver(driver.ExecuteMixin, driver.ShareDriver): return 0 def _setup_server(self, network_info, metadata=None): + # NOTE(felipe_rodrigues): keep legacy network_info support as a dict. + network_info = network_info[0] + msg = "Creating share server '%s'." LOG.debug(msg, network_info['server_id']) server = self.service_instance_manager.set_up_service_instance( diff --git a/manila/share/drivers/hpe/hpe_3par_driver.py b/manila/share/drivers/hpe/hpe_3par_driver.py index 8a2150c226..c587f82f17 100644 --- a/manila/share/drivers/hpe/hpe_3par_driver.py +++ b/manila/share/drivers/hpe/hpe_3par_driver.py @@ -432,6 +432,8 @@ class HPE3ParShareDriver(driver.ShareDriver): 'vfs': vfs} def _setup_server(self, network_info, metadata=None): + # NOTE(felipe_rodrigues): keep legacy network_info support as a dict. + network_info = network_info[0] LOG.debug("begin _setup_server with %s", network_info) diff --git a/manila/share/drivers/huawei/huawei_nas.py b/manila/share/drivers/huawei/huawei_nas.py index 01b4092646..8a4b0346df 100644 --- a/manila/share/drivers/huawei/huawei_nas.py +++ b/manila/share/drivers/huawei/huawei_nas.py @@ -223,6 +223,9 @@ class HuaweiNasDriver(driver.ShareDriver): def _setup_server(self, network_info, metadata=None): """Set up share server with given network parameters.""" + # NOTE(felipe_rodrigues): keep legacy network_info support as a dict. + network_info = network_info[0] + return self.plugin.setup_server(network_info, metadata) def _teardown_server(self, server_details, security_services=None): diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py index bef4c02f43..6cf84add23 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py @@ -135,6 +135,8 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver): self.admin_network_api) def _setup_server(self, network_info, metadata=None): + # NOTE(felipe_rodrigues): keep legacy network_info support as a dict. + network_info = network_info[0] return self.library.setup_server(network_info, metadata) def _teardown_server(self, server_details, **kwargs): diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py index 729f1b15ad..655d3a97ed 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py @@ -1658,6 +1658,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary( 2. Build the list of export_locations for each share 3. Release all resources from the source share server """ + new_network_alloc = new_network_alloc[0] src_backend_name = share_utils.extract_host( source_share_server['host'], level='backend_name') src_vserver, src_client = self._get_vserver( @@ -2013,7 +2014,7 @@ class NetAppCmodeMultiSVMFileStorageLibrary( dns_ips = set() domains = set() # Read all dns-ips and domains from other security services - for sec_svc in network_info['security_services']: + for sec_svc in network_info[0]['security_services']: if sec_svc['type'] == current_type: # skip the one that we are replacing continue diff --git a/manila/share/manager.py b/manila/share/manager.py index 3e82331ed4..00cea77ad9 100644 --- a/manila/share/manager.py +++ b/manila/share/manager.py @@ -243,7 +243,7 @@ def add_hooks(f): class ShareManager(manager.SchedulerDependentManager): """Manages NAS storages.""" - RPC_API_VERSION = '1.22' + RPC_API_VERSION = '1.23' def __init__(self, share_driver=None, service_name=None, *args, **kwargs): """Load the driver from args, or from flags.""" @@ -666,20 +666,25 @@ class ShareManager(manager.SchedulerDependentManager): raise exception.InvalidShareServer(reason=msg % error_params) parent_share_same_dest = (snapshot['share']['instance']['host'] == share_instance['host']) - share_network_subnet_id = None + share_network_subnets = None if share_network_id: - share_network_subnet = ( - self.db.share_network_subnet_get_by_availability_zone_id( + share_network_subnets = ( + self.db.share_network_subnets_get_all_by_availability_zone_id( context, share_network_id, availability_zone_id=share_instance.get( 'availability_zone_id'))) - if not share_network_subnet: + if not share_network_subnets: 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_subnets = ( + parent_share_server['share_network_subnets']) + + # NOTE(felipe_rodrigues): it can retrieve the available share + # servers using one single subnet_id from the availability zone + # subnets, because if the share server has one, it will have + # all subnets on that availability zone. + share_network_subnet_id = share_network_subnets[0]['id'] def get_available_share_servers(): if parent_share_server and parent_share_same_dest: @@ -725,10 +730,12 @@ class ShareManager(manager.SchedulerDependentManager): context, { 'host': self.host, - 'share_network_subnet_id': share_network_subnet_id, + 'share_network_subnets': share_network_subnets, 'status': constants.STATUS_CREATING, 'security_service_update_support': ( self.driver.security_service_update_support), + 'network_allocation_update_support': ( + self.driver.network_allocation_update_support), } ) @@ -796,28 +803,29 @@ class ShareManager(manager.SchedulerDependentManager): migration. """ - share_network_subnet = ( - self.db.share_network_subnet_get_by_availability_zone_id( + share_network_subnets = ( + self.db.share_network_subnets_get_all_by_availability_zone_id( context, new_share_network_id, availability_zone_id=availability_zone_id)) - if not share_network_subnet: + if not share_network_subnets: raise exception.ShareNetworkSubnetNotFound( share_network_subnet_id=None) - share_network_subnet_id = share_network_subnet['id'] server_metadata = {} if not server_metadata else server_metadata - @utils.synchronized("share_manager_%s" % share_network_subnet_id, - external=True) + @utils.synchronized( + "share_manager_%s" % share_network_subnets[0]['id'], external=True) def _wrapped_provide_share_server_for_migration(): destination_share_server = self.db.share_server_create( context, { 'host': self.host, - 'share_network_subnet_id': share_network_subnet_id, + 'share_network_subnets': share_network_subnets, 'status': constants.STATUS_CREATING, 'security_service_update_support': ( self.driver.security_service_update_support), + 'network_allocation_update_support': ( + self.driver.network_allocation_update_support), } ) @@ -917,7 +925,7 @@ class ShareManager(manager.SchedulerDependentManager): def _provide_share_server_for_share_group(self, context, share_network_id, - share_network_subnet_id, + share_network_subnets, share_group_ref, share_group_snapshot=None): """Gets or creates share_server and updates share group with its id. @@ -935,10 +943,8 @@ 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_network_subnets: Share network subnets where existing + share server should be found or created. :param share_group_ref: Share Group model :param share_group_snapshot: Optional -- ShareGroupSnapshot model. If supplied, driver will use it to choose @@ -962,6 +968,11 @@ class ShareManager(manager.SchedulerDependentManager): @utils.synchronized("share_manager_%s" % share_network_id, external=True) def _wrapped_provide_share_server_for_share_group(): + # NOTE(felipe_rodrigues): it can retrieve the available share + # servers using one single subnet_id from the availability zone + # subnets, because if the share server has one, it will have + # all subnets on that availability zone. + share_network_subnet_id = share_network_subnets[0]['id'] try: available_share_servers = ( self.db @@ -996,10 +1007,12 @@ class ShareManager(manager.SchedulerDependentManager): context, { 'host': self.host, - 'share_network_subnet_id': share_network_subnet_id, + 'share_network_subnets': share_network_subnets, 'status': constants.STATUS_CREATING, 'security_service_update_support': ( self.driver.security_service_update_support), + 'network_allocation_update_support': ( + self.driver.network_allocation_update_support), } ) @@ -3037,13 +3050,13 @@ 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 subnet %(subnet)s and share network " + "share network subnets %(subnets)s and share network " "%(network)s cannot be deleted unless this share server " "has been removed.") msg_args = { 'share': share_id, 'server': share_server['id'], - 'subnet': share_server['share_network_subnet_id'], + 'subnets': share_server['share_network_subnet_ids'], 'network': share_instance['share_network_id'] } LOG.warning(msg, msg_args) @@ -3130,8 +3143,11 @@ class ShareManager(manager.SchedulerDependentManager): server = self.db.share_server_get(context, share_server_id) try: + # NOTE(felipe_rodrigues): Manila does not support manage share + # server with multiple allocations, so it can get the first + # subnet_id element. share_network_subnet = self.db.share_network_subnet_get( - context, server['share_network_subnet_id']) + context, server['share_network_subnet_ids'][0]) share_network = self.db.share_network_get( context, share_network_subnet['share_network_id']) @@ -3217,7 +3233,9 @@ class ShareManager(manager.SchedulerDependentManager): self.db.share_server_update( context, share_server_id, {'status': constants.STATUS_ACTIVE, - 'identifier': new_identifier}) + 'identifier': new_identifier, + 'network_allocation_update_support': ( + self.driver.network_allocation_update_support)}) except Exception: msg = "Error managing share server %s" @@ -4039,56 +4057,105 @@ class ShareManager(manager.SchedulerDependentManager): self._publish_service_capabilities(context) def _form_server_setup_info(self, context, share_server, share_network, - share_network_subnet): + share_network_subnets): share_server_id = share_server['id'] # 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( - context, share_server_id, label='user') admin_network_allocations = ( self.db.network_allocations_get_for_share_server( context, share_server_id, label='admin')) - # NOTE(vponomaryov): following network_info fields are deprecated: - # 'segmentation_id', 'cidr' and 'network_type'. - # And they should be used from network allocations directly. - # They should be removed right after no one uses them. - network_info = { - 'server_id': share_server['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_subnet['network_type'], - } + + # NOTE(felipe_rodrigues): items in the network_info list contain + # same values for the keys: server_id, admin_network_allocations, + # security_services and backend_details. + network_info = [] + for share_network_subnet in share_network_subnets: + network_allocations = ( + self.db.network_allocations_get_for_share_server( + context, share_server_id, label='user', + subnet_id=share_network_subnet['id'])) + # NOTE(vponomaryov): following network_info fields are deprecated: + # 'segmentation_id', 'cidr' and 'network_type'. + # And they should be used from network allocations directly. + # They should be removed right after no one uses them. + network_info.append({ + 'server_id': share_server['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_subnet['network_type'], + }) return network_info + def _handle_setup_server_error(self, context, share_server_id, e): + details = getattr(e, "detail_data", {}) + if isinstance(details, dict): + server_details = details.get("server_details", {}) + if not isinstance(server_details, dict): + LOG.debug( + ("Cannot save non-dict data (%(data)s) provided as " + "'server details' of failed share server '%(server)s'."), + {"server": share_server_id, "data": server_details}) + else: + invalid_details = [] + for key, value in server_details.items(): + try: + self.db.share_server_backend_details_set( + context, share_server_id, {key: value}) + except Exception: + invalid_details.append("%(key)s: %(value)s" % { + 'key': str(key), + 'value': str(value) + }) + if invalid_details: + LOG.debug( + ("Following server details cannot be written to db : " + "%s"), str("\n".join(invalid_details))) + else: + LOG.debug( + ("Cannot save non-dict data (%(data)s) provided as 'detail " + "data' of failed share server '%(server)s'."), + {"server": share_server_id, "data": details}) + + self.db.share_server_update( + context, share_server_id, {'status': constants.STATUS_ERROR}) + def _setup_server(self, context, share_server, metadata): + subnets = share_server['share_network_subnets'] + if not subnets: + raise exception.NetworkBadConfigurationException( + reason="share server does not have subnet") + + # all subnets reside on same share network, get it from the first one. + share_network_id = subnets[0]['share_network_id'] 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_network_id) - self.driver.allocate_network(context, share_server, share_network, - share_network_subnet) + share_network = self.db.share_network_get(context, + share_network_id) + for share_network_subnet in subnets: + self.driver.allocate_network( + context, share_server, share_network, share_network_subnet) self.driver.allocate_admin_network(context, share_server) - # 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, share_network_subnet) - self._validate_segmentation_id(network_info) + # Get share_network_subnets in case they were updated. + share_network_subnets = ( + self.db.share_network_subnet_get_all_by_share_server_id( + context, share_server['id'])) + + network_info_list = self._form_server_setup_info( + context, share_server, share_network, share_network_subnets) + for network_info in network_info_list: + self._validate_segmentation_id(network_info) # NOTE(vponomaryov): Save security services data to share server # details table to remove dependency from share network after # creation operation. It will allow us to delete share server and # share network separately without dependency on each other. - for security_service in network_info['security_services']: + for security_service in network_info_list[0]['security_services']: ss_type = security_service['type'] data = { 'name': security_service['name'], @@ -4105,7 +4172,7 @@ class ShareManager(manager.SchedulerDependentManager): {'security_service_' + ss_type: jsonutils.dumps(data)}) server_info = self.driver.setup_server( - network_info, metadata=metadata) + network_info_list, metadata=metadata) self.driver.update_network_allocation(context, share_server) self.driver.update_admin_network_allocation(context, share_server) @@ -4120,42 +4187,7 @@ class ShareManager(manager.SchedulerDependentManager): 'identifier', share_server['id'])}) except Exception as e: with excutils.save_and_reraise_exception(): - details = getattr(e, "detail_data", {}) - - if isinstance(details, dict): - server_details = details.get("server_details", {}) - if not isinstance(server_details, dict): - LOG.debug( - ("Cannot save non-dict data (%(data)s) " - "provided as 'server details' of " - "failed share server '%(server)s'."), - {"server": share_server["id"], - "data": server_details}) - else: - invalid_details = [] - for key, value in server_details.items(): - try: - self.db.share_server_backend_details_set( - context, share_server['id'], {key: value}) - except Exception: - invalid_details.append("%(key)s: %(value)s" % { - 'key': key, - 'value': value - }) - if invalid_details: - LOG.debug( - ("Following server details " - "cannot be written to db : %s"), - "\n".join(invalid_details)) - else: - LOG.debug( - ("Cannot save non-dict data (%(data)s) provided as " - "'detail data' of failed share server '%(server)s'."), - {"server": share_server["id"], "data": details}) - - self.db.share_server_update( - context, share_server['id'], - {'status': constants.STATUS_ERROR}) + self._handle_setup_server_error(context, share_server['id'], e) self.driver.deallocate_network(context, share_server['id']) def _validate_segmentation_id(self, network_info): @@ -4200,8 +4232,11 @@ class ShareManager(manager.SchedulerDependentManager): @utils.require_driver_initialized def delete_share_server(self, context, share_server): + subnet_id = (share_server['share_network_subnet_ids'][0] + if share_server['share_network_subnet_ids'] else None) + @utils.synchronized( - "share_manager_%s" % share_server['share_network_subnet_id']) + "share_manager_%s" % 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: @@ -4443,8 +4478,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_subnet = share_server['share_network_subnet'] - share_network_id = share_network_subnet['share_network_id'] + share_network_id = ( + share_server['share_network_id']) if share_network_id and not self.driver.driver_handles_share_servers: self.db.share_group_update( @@ -4457,14 +4492,17 @@ class ShareManager(manager.SchedulerDependentManager): 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) + subnets = ( + self.db.share_network_subnets_get_all_by_availability_zone_id( + context, share_network_id, availability_zone_id)) + if not subnets: + raise exception.ShareNetworkSubnetNotFound( + share_network_subnet_id=None) try: share_server, share_group_ref = ( self._provide_share_server_for_share_group( - context, share_network_id, subnet.get('id'), - share_group_ref, + context, share_network_id, subnets, share_group_ref, share_group_snapshot=snap_ref, ) ) @@ -4764,8 +4802,8 @@ class ShareManager(manager.SchedulerDependentManager): 'share_network_id': share_server.get('share_network_id'), 'created_at': share_server.get('created_at'), 'backend_details': share_server.get('backend_details'), - 'share_network_subnet_id': - share_server.get('share_network_subnet_id', None), + 'share_network_subnet_ids': + share_server.get('share_network_subnet_ids', []), 'is_auto_deletable': share_server.get('is_auto_deletable', None), 'identifier': share_server.get('identifier', None), 'network_allocations': share_server.get('network_allocations', @@ -5101,11 +5139,10 @@ class ShareManager(manager.SchedulerDependentManager): if not create_server_on_backend: dest_share_server = self.db.share_server_get( context, dest_share_server['id']) - current_subnet = dest_share_server['share_network_subnet'] - old_subnet = source_share_server['share_network_subnet'] - for key in ['neutron_net_id', 'neutron_subnet_id']: - if current_subnet.get(key) != old_subnet.get(key): - net_changes_identified = True + net_changes_identified = ( + not share_utils.is_az_subnets_compatible( + dest_share_server['share_network_subnets'], + source_share_server['share_network_subnets'])) # NOTE(carloss): Even though the share back end won't need to # create a share server, if a network change was identified, @@ -5115,11 +5152,14 @@ class ShareManager(manager.SchedulerDependentManager): # In such case, the migration will be disruptive, since the old # allocations will be replaced by the new ones. if net_changes_identified: - share_network_subnet = self.db.share_network_subnet_get( - context, dest_share_server['share_network_subnet_id']) - self.driver.allocate_network( - context, dest_share_server, new_share_network, - share_network_subnet) + share_network_subnets = ( + self.db. + share_network_subnet_get_all_by_share_server_id( + context, dest_share_server['id'])) + for share_network_subnet in share_network_subnets: + self.driver.allocate_network( + context, dest_share_server, new_share_network, + share_network_subnet) self.driver.allocate_admin_network( context, dest_share_server) # Refresh the share server so it will have the network @@ -5503,11 +5543,11 @@ class ShareManager(manager.SchedulerDependentManager): {'task_state': constants.TASK_STATE_MIGRATION_COMPLETING}) # Retrieve network allocations reserved for the new share server - dest_sns = dest_share_server['share_network_subnet'] - dest_sns_id = dest_sns['id'] - dest_sn_id = dest_sns['share_network_id'] + dest_snss = dest_share_server['share_network_subnets'] + dest_sn_id = dest_snss[0]['share_network_id'] dest_sn = self.db.share_network_get(context, dest_sn_id) - dest_sns = self.db.share_network_subnet_get(context, dest_sns_id) + dest_snss = self.db.share_network_subnet_get_all_by_share_server_id( + context, dest_share_server['id']) migration_reused_network_allocations = (len( self.db.network_allocations_get_for_share_server( @@ -5519,16 +5559,20 @@ class ShareManager(manager.SchedulerDependentManager): else source_share_server) new_network_allocations = self._form_server_setup_info( - context, server_to_get_allocations, dest_sn, dest_sns) + context, server_to_get_allocations, dest_sn, dest_snss) model_update = self.driver.share_server_migration_complete( context, source_share_server, dest_share_server, share_instances, snapshot_instances, new_network_allocations) if not migration_reused_network_allocations: + network_allocations = [] + for net_allocation in new_network_allocations: + network_allocations += net_allocation['network_allocations'] + all_allocations = [ - new_network_allocations['network_allocations'], - new_network_allocations['admin_network_allocations'] + network_allocations, + new_network_allocations[0]['admin_network_allocations'] ] for allocations in all_allocations: for allocation in allocations: @@ -5726,7 +5770,8 @@ class ShareManager(manager.SchedulerDependentManager): snapshot_instances) @locked_share_network_operation - def _check_share_network_update_finished(self, context, share_network_id): + def _check_share_network_update_finished( + self, context, share_network_id=None): # Check if this share network is already active share_network = self.db.share_network_get(context, share_network_id) if share_network['status'] == constants.STATUS_NETWORK_ACTIVE: @@ -5775,14 +5820,14 @@ class ShareManager(manager.SchedulerDependentManager): filters={'share_network_id': share_network_id}) for share_server in share_servers: - share_network_subnet = share_server['share_network_subnet'] - share_network_subnet_id = share_network_subnet['id'] # Get share_network_subnet in case it was updated. - share_network_subnet = self.db.share_network_subnet_get( - context, share_network_subnet_id) + share_network_subnets = ( + self.db.share_network_subnet_get_all_by_share_server_id( + context, share_server['id'])) + network_info = self._form_server_setup_info( - context, share_server, share_network, share_network_subnet) + context, share_server, share_network, share_network_subnets) share_instances = ( self.db.share_instances_get_all_by_share_server( @@ -5883,7 +5928,8 @@ class ShareManager(manager.SchedulerDependentManager): # Check if all share servers have already finished their updates in # order to properly update share network status - self._check_share_network_update_finished(context, share_network['id']) + self._check_share_network_update_finished( + context, share_network_id=share_network['id']) def update_share_network_security_service( self, context, share_network_id, new_security_service_id, @@ -5925,3 +5971,234 @@ class ShareManager(manager.SchedulerDependentManager): LOG.debug('A share network security service check was requested ' 'but no entries were found in database. Ignoring call ' 'and returning.') + + @api.locked_share_server_update_allocations_operation + def _update_share_server_allocations_check_operation( + self, context, is_supported, share_network_id=None, + availability_zone_id=None): + update_key = self.share_api.get_share_server_update_allocations_key( + share_network_id, availability_zone_id) + current_hosts_info = self.db.async_operation_data_get( + context, share_network_id, update_key) + if current_hosts_info: + current_hosts = json.loads(current_hosts_info) + current_hosts[self.host] = is_supported + self.db.async_operation_data_update( + context, share_network_id, + {update_key: json.dumps(current_hosts)}) + else: + LOG.debug('A share network subnet create check was requested ' + 'but no entries were found in database. Ignoring call ' + 'and returning.') + + def _get_subnet_allocations(self, context, share_server_id, + share_network_subnet): + + network_allocations = ( + self.db.network_allocations_get_for_share_server( + context, share_server_id, label='user', + subnet_id=share_network_subnet['id'])) + + return { + 'share_network_subnet_id': share_network_subnet['id'], + 'neutron_net_id': share_network_subnet['neutron_net_id'], + 'neutron_subnet_id': share_network_subnet['neutron_subnet_id'], + 'network_allocations': network_allocations, + } + + def _form_network_allocations(self, context, share_server_id, + share_network_subnets): + + subnet_allocations = [] + for share_network_subnet in share_network_subnets: + subnet_allocations.append(self._get_subnet_allocations( + context, share_server_id, share_network_subnet)) + + admin_network_allocations = ( + self.db.network_allocations_get_for_share_server( + context, share_server_id, label='admin')) + + return { + 'admin_network_allocations': admin_network_allocations, + 'subnets': subnet_allocations, + } + + def check_update_share_server_network_allocations( + self, context, share_network_id, new_share_network_subnet): + + share_network = self.db.share_network_get( + context, share_network_id) + az_subnets = ( + self.db.share_network_subnets_get_all_by_availability_zone_id( + context, share_network_id, + new_share_network_subnet['availability_zone_id'], + fallback_to_default=False) + ) + self.driver.network_api.include_network_info(new_share_network_subnet) + + # all subnets have the same set of share servers, so do the check from + # servers in the first subnet. + share_servers = az_subnets[0]['share_servers'] if az_subnets else [] + is_supported = True + for share_server in share_servers: + + current_network_allocations = self._form_network_allocations( + context, share_server['id'], az_subnets) + + share_instances = ( + self.db.share_instances_get_all_by_share_server( + context, share_server['id'], with_share_data=True)) + share_instance_ids = [sn.id for sn in share_instances] + + share_instances_rules = [] + for share_instance_id in share_instance_ids: + instance_rules = { + 'share_instance_id': share_instance_id, + 'access_rules': ( + self.db.share_access_get_all_for_instance( + context, share_instance_id)) + } + share_instances_rules.append(instance_rules) + + if self.driver.check_update_share_server_network_allocations( + context, share_server, current_network_allocations, + new_share_network_subnet, + share_network['security_services'], + share_instances, share_instances_rules): + # Check the next share server. + continue + else: + # At least one share server doesn't support this update. + is_supported = False + break + + self._update_share_server_allocations_check_operation( + context, is_supported, share_network_id=share_network_id, + availability_zone_id=( + new_share_network_subnet['availability_zone_id'])) + + def _do_update_share_server_network_allocations( + self, context, share_server, share_network, new_subnet, + current_network_allocations, share_instances, + snapshot_instance_ids): + + self.driver.allocate_network( + context, share_server, share_network, new_subnet) + new_network_allocations = self._get_subnet_allocations( + context, share_server['id'], new_subnet) + if not new_network_allocations['network_allocations']: + raise exception.AllocationsNotFoundForShareServer( + share_server_id=share_server['id']) + + # NOTE(felipe_rodrigues): all allocations have the same network + # segmentation info, so validation from the first one. + self._validate_segmentation_id( + new_network_allocations['network_allocations'][0]) + + model_update = self.driver.update_share_server_network_allocations( + context, share_server, current_network_allocations, + new_network_allocations, share_network['security_services'], + share_instances, snapshot_instance_ids) + + self.driver.update_network_allocation(context, share_server) + + driver_backend_details = model_update.get('server_details') + if driver_backend_details: + self.db.share_server_backend_details_set( + context, share_server['id'], driver_backend_details) + + share_updates = model_update.get('share_updates', {}) + for share_instance_id, export_locations in share_updates.items(): + self.db.share_export_locations_update( + context, share_instance_id, export_locations) + + snapshot_updates = model_update.get('snapshot_updates', {}) + for snap_instance_id, model_update in snapshot_updates.items(): + snapshot_export_locations = model_update.pop( + 'export_locations', []) + if model_update: + self.db.share_snapshot_instance_update( + context, snap_instance_id, model_update) + + if snapshot_export_locations: + export_locations_update = [] + for exp_location in snapshot_export_locations: + updated_el = { + 'path': exp_location['path'], + 'is_admin_only': exp_location['is_admin_only'], + } + export_locations_update.append(updated_el) + self.db.share_snapshot_instance_export_locations_update( + context, snap_instance_id, export_locations_update) + + def update_share_server_network_allocations( + self, context, share_network_id, new_share_network_subnet_id): + + share_network = self.db.share_network_get( + context, share_network_id) + new_subnet = self.db.share_network_subnet_get( + context, new_share_network_subnet_id) + current_subnets = ( + self.db.share_network_subnets_get_all_by_availability_zone_id( + context, share_network_id, + new_subnet['availability_zone_id'], + fallback_to_default=False) + ) + current_subnets = [subnet for subnet in current_subnets + if subnet['id'] != new_share_network_subnet_id] + share_servers = ( + self.db.share_server_get_all_by_host_and_share_subnet_valid( + context, self.host, new_share_network_subnet_id, + server_status=constants.STATUS_SERVER_NETWORK_CHANGE)) + for share_server in share_servers: + + share_server_id = share_server['id'] + current_network_allocations = self._form_network_allocations( + context, share_server_id, current_subnets) + share_instances = ( + self.db.share_instances_get_all_by_share_server( + context, share_server_id, with_share_data=True)) + share_instance_ids = [x['id'] for x in share_instances] + snapshot_instances = ( + self.db.share_snapshot_instance_get_all_with_filters( + context, + {'share_instance_ids': share_instance_ids})) + snapshot_instance_ids = [x['id'] for x in snapshot_instances] + + try: + self._do_update_share_server_network_allocations( + context, share_server, share_network, new_subnet, + current_network_allocations, share_instances, + snapshot_instances) + except Exception as e: + msg = ('Failed to update allocations of share server ' + '%(server_id)s on subnet %(subnet_id)s: %(e)s.') + data = { + 'server_id': share_server_id, + 'subnet_id': new_share_network_subnet_id, + 'e': str(e), + } + LOG.exception(msg, data) + + # Set resources to error. Allocations configuration must be + # fixed before restoring it to active again. + self._handle_setup_server_error(context, share_server_id, e) + self._update_resource_status( + context, constants.STATUS_ERROR, + share_instance_ids=share_instance_ids, + snapshot_instance_ids=snapshot_instance_ids) + + continue + + msg = _( + "Network allocations was successfully updated on share " + "server %s.") % share_server['id'] + LOG.info(msg) + self.db.share_server_update( + context, share_server['id'], + {'status': constants.STATUS_ACTIVE}) + + # Check if all share servers have already finished their updates in + # order to properly update share network status. + self._check_share_network_update_finished( + context, share_network_id=share_network['id']) diff --git a/manila/share/rpcapi.py b/manila/share/rpcapi.py index 8170e66232..8526a91ac7 100644 --- a/manila/share/rpcapi.py +++ b/manila/share/rpcapi.py @@ -81,6 +81,8 @@ class ShareAPI(object): and share_server_get_progress() 1.22 - Add update_share_network_security_service() and check_update_share_network_security_service() + 1.23 - Add update_share_server_network_allocations() and + check_update_share_server_network_allocations() """ BASE_RPC_API_VERSION = '1.0' @@ -89,7 +91,7 @@ class ShareAPI(object): super(ShareAPI, self).__init__() target = messaging.Target(topic=CONF.share_topic, version=self.BASE_RPC_API_VERSION) - self.client = rpc.get_client(target, version_cap='1.22') + self.client = rpc.get_client(target, version_cap='1.23') def create_share_instance(self, context, share_instance, host, request_spec, filter_properties, @@ -462,3 +464,25 @@ class ShareAPI(object): share_network_id=share_network_id, new_security_service_id=new_security_service_id, current_security_service_id=current_security_service_id) + + def check_update_share_server_network_allocations( + self, context, dest_host, share_network_id, + new_share_network_subnet): + host = utils.extract_host(dest_host) + call_context = self.client.prepare(server=host, version='1.23') + call_context.cast( + context, + 'check_update_share_server_network_allocations', + share_network_id=share_network_id, + new_share_network_subnet=new_share_network_subnet) + + def update_share_server_network_allocations( + self, context, dest_host, share_network_id, + new_share_network_subnet_id): + host = utils.extract_host(dest_host) + call_context = self.client.prepare(server=host, version='1.23') + call_context.cast( + context, + 'update_share_server_network_allocations', + share_network_id=share_network_id, + new_share_network_subnet_id=new_share_network_subnet_id) diff --git a/manila/share/utils.py b/manila/share/utils.py index 9b53e83f6a..757e098dfa 100644 --- a/manila/share/utils.py +++ b/manila/share/utils.py @@ -152,3 +152,22 @@ def _usage_from_share(share_ref, share_instance_ref, **extra_usage_info): def get_recent_db_migration_id(): return migration.version() + + +def is_az_subnets_compatible(subnet_list, new_subnet_list): + if len(subnet_list) != len(new_subnet_list): + return False + + for subnet in subnet_list: + found_compatible = False + for new_subnet in new_subnet_list: + if (subnet.get('neutron_net_id') == + new_subnet.get('neutron_net_id') and + subnet.get('neutron_subnet_id') == + new_subnet.get('neutron_subnet_id')): + found_compatible = True + break + if not found_compatible: + return False + + return True diff --git a/manila/tests/api/test_common.py b/manila/tests/api/test_common.py index 549ab57070..dfb4320028 100644 --- a/manila/tests/api/test_common.py +++ b/manila/tests/api/test_common.py @@ -24,6 +24,7 @@ import webob import webob.exc from manila.api import common +from manila.db import api as db_api from manila import exception from manila import policy from manila import test @@ -354,6 +355,108 @@ class MiscFunctionsTest(test.TestCase): common.parse_is_public, 'fakefakefake') + @ddt.data(None, 'fake_az') + def test__get_existing_subnets(self, az): + default_subnets = 'fake_default_subnets' + mock_get_default_subnets = self.mock_object( + db_api, 'share_network_subnet_get_default_subnets', + mock.Mock(return_value=default_subnets)) + subnets = 'fake_subnets' + mock_get_subnets = self.mock_object( + db_api, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=subnets)) + + net_id = 'fake_net' + context = 'fake_context' + res_subnets = common._get_existing_subnets(context, net_id, az) + + if az: + self.assertEqual(subnets, res_subnets) + mock_get_subnets.assert_called_once_with(context, net_id, az, + fallback_to_default=False) + mock_get_default_subnets.assert_not_called() + else: + self.assertEqual(default_subnets, res_subnets) + mock_get_subnets.assert_not_called() + mock_get_default_subnets.assert_called_once_with(context, net_id) + + def test_validate_subnet_create(self): + mock_check_net = self.mock_object(common, 'check_net_id_and_subnet_id') + net = 'fake_net' + mock_get_net = self.mock_object(db_api, 'share_network_get', + mock.Mock(return_value=net)) + az_id = 'fake_az_id' + az = {'id': az_id} + mock_get_az = self.mock_object(db_api, 'availability_zone_get', + mock.Mock(return_value=az)) + subnets = 'fake_subnets' + mock_get_subnets = self.mock_object(common, '_get_existing_subnets', + mock.Mock(return_value=subnets)) + + net_id = 'fake_net_id' + context = 'fake_context' + az_name = 'fake_az' + data = {'availability_zone': az_name} + res_net, res_subnets = common.validate_subnet_create( + context, net_id, data, True) + + self.assertEqual(net, res_net) + self.assertEqual(subnets, res_subnets) + self.assertEqual(data['availability_zone_id'], az_id) + mock_check_net.assert_called_once_with(data) + mock_get_net.assert_called_once_with(context, net_id) + mock_get_az.assert_called_once_with(context, az_name) + mock_get_subnets.assert_called_once_with(context, net_id, az_id) + + def test_validate_subnet_create_net_not_found(self): + + self.mock_object(common, 'check_net_id_and_subnet_id') + self.mock_object(db_api, 'share_network_get', + mock.Mock(side_effect=exception.ShareNetworkNotFound( + share_network_id="fake_id"))) + + net_id = 'fake_net_id' + context = 'fake_context' + az_name = 'fake_az' + data = {'availability_zone': az_name} + self.assertRaises(webob.exc.HTTPNotFound, + common.validate_subnet_create, + context, net_id, data, True) + + def test_validate_subnet_create_az_not_found(self): + self.mock_object(common, 'check_net_id_and_subnet_id') + self.mock_object(db_api, 'share_network_get', + mock.Mock(return_value='fake_net')) + self.mock_object( + db_api, 'availability_zone_get', + mock.Mock(side_effect=exception.AvailabilityZoneNotFound( + id='fake_id'))) + + net_id = 'fake_net_id' + context = 'fake_context' + az_name = 'fake_az' + data = {'availability_zone': az_name} + self.assertRaises(webob.exc.HTTPBadRequest, + common.validate_subnet_create, + context, net_id, data, True) + + def test_validate_subnet_create_multiple_subnet_not_support(self): + self.mock_object(common, 'check_net_id_and_subnet_id') + self.mock_object(db_api, 'share_network_get', + mock.Mock(return_value='fake_net')) + self.mock_object(db_api, 'availability_zone_get', + mock.Mock(return_value={'id': 'fake_az_id'})) + self.mock_object(common, '_get_existing_subnets', + mock.Mock(return_value='fake_subnets')) + + net_id = 'fake_net_id' + context = 'fake_context' + az_name = 'fake_az' + data = {'availability_zone': az_name} + self.assertRaises(webob.exc.HTTPConflict, + common.validate_subnet_create, + context, net_id, data, False) + @ddt.ddt class ViewBuilderTest(test.TestCase): diff --git a/manila/tests/api/v1/test_share_servers.py b/manila/tests/api/v1/test_share_servers.py index 06e2612420..fd7d592a81 100644 --- a/manila/tests/api/v1/test_share_servers.py +++ b/manila/tests/api/v1/test_share_servers.py @@ -15,6 +15,7 @@ from unittest import mock +import copy import ddt from webob import exc @@ -36,14 +37,15 @@ fake_share_server_list = { 'host': 'fake_host', 'share_network_name': 'fake_sn_name', 'share_network_id': 'fake_sn_id', - 'share_network_subnet_id': 'fake_sns_id', + 'share_network_subnet_ids': ['fake_sns_id'], 'project_id': 'fake_project_id', 'id': 'fake_server_id', 'is_auto_deletable': False, 'task_state': None, 'source_share_server_id': None, 'identifier': 'fake_id', - 'security_service_update_support': False + 'security_service_update_support': False, + 'network_allocation_update_support': False }, { 'status': constants.STATUS_ERROR, @@ -51,14 +53,15 @@ fake_share_server_list = { 'host': 'fake_host_2', 'share_network_name': 'fake_sn_id_2', 'share_network_id': 'fake_sn_id_2', - 'share_network_subnet_id': 'fake_sns_id_2', + 'share_network_subnet_ids': ['fake_sns_id_2'], 'project_id': 'fake_project_id_2', 'id': 'fake_server_id_2', 'is_auto_deletable': True, 'task_state': None, 'source_share_server_id': None, 'identifier': 'fake_id_2', - 'security_service_update_support': False + 'security_service_update_support': False, + 'network_allocation_update_support': False }, ] @@ -87,7 +90,7 @@ 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', + 'share_network_subnet_ids': ['fake_sns_id'], 'project_id': 'fake_project_id', 'id': 'fake_server_id', 'backend_details': { @@ -98,7 +101,8 @@ fake_share_server_get_result = { 'task_state': None, 'source_share_server_id': None, 'identifier': 'fake_id', - 'security_service_update_support': False + 'security_service_update_support': False, + 'network_allocation_update_support': False } } @@ -124,10 +128,11 @@ 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_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.share_network_subnets = kwargs.get('share_network_subnets', [{ + 'id': 'fake_sns_id', 'share_network_id': 'fake_sn_id'}]) + self.share_network_subnet_ids = kwargs.get( + 'share_network_subnet_ids', + [sn['id'] for sn in self.share_network_subnets]) self.status = kwargs.get('status', constants.STATUS_ACTIVE) self.project_id = 'fake_project_id' self.identifier = kwargs.get('identifier', 'fake_id') @@ -137,6 +142,9 @@ class FakeShareServer(object): self.backend_details = share_server_backend_details self.security_service_update_support = kwargs.get( 'security_service_update_support', False) + self.network_allocation_update_support = kwargs.get( + 'network_allocation_update_support', False) + self.share_network_id = kwargs.get('share_network_id', 'fake_sn_id') def __getitem__(self, item): return getattr(self, item) @@ -147,15 +155,17 @@ def fake_share_server_get_all(): FakeShareServer(), FakeShareServer(id='fake_server_id_2', host='fake_host_2', - share_network_subnet={ + share_network_subnets=[{ 'id': 'fake_sns_id_2', 'share_network_id': 'fake_sn_id_2', - }, + }], + share_network_id='fake_sn_id_2', identifier='fake_id_2', task_state=None, is_auto_deletable=True, status=constants.STATUS_ERROR, - security_service_update_support=False) + security_service_update_support=False, + network_allocation_update_support=False), ] return fake_share_servers @@ -184,7 +194,7 @@ class FakeRequestWithProjectId(FakeRequestAdmin): class FakeRequestWithShareNetworkSubnetId(FakeRequestAdmin): GET = { 'share_network_subnet_id': - fake_share_server_get_all()[0].share_network_subnet_id, + fake_share_server_get_all()[0].share_network_subnet_ids, } @@ -347,20 +357,43 @@ class ShareServerAPITest(test.TestCase): ctxt, self.resource_name, 'index') self.assertEqual(0, len(result['share_servers'])) - def test_show(self): + @ddt.data({'version': '2.70', 'share_network_name': ''}, + {'version': '2.70', 'share_network_name': 'fake_sn_name'}, + {'version': '2.68', 'share_network_name': 'fake_sn_name'}) + @ddt.unpack + def test_show(self, version, share_network_name): 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) + request, ctxt = self._prepare_request('/show', use_admin_context=True, + version=version) + + share_network = copy.deepcopy( + fake_share_network_get_list['share_networks'][0]) + share_server = copy.deepcopy( + fake_share_server_get_result['share_server']) + + if version == '2.68': + share_server['share_network_subnet_id'] = \ + share_server['share_network_subnet_ids'][0] + share_server.pop('share_network_subnet_ids') + share_server.pop('network_allocation_update_support') + + share_network['name'] = share_network_name + if share_network['name']: + share_server['share_network_name'] = share_network['name'] + else: + share_server['share_network_name'] = share_network['id'] + self.mock_object(db_api, 'share_network_get', mock.Mock( - return_value=fake_share_network_get_list['share_networks'][0])) + return_value=share_network)) result = self.controller.show( request, - fake_share_server_get_result['share_server']['id']) + 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']) - self.assertEqual(fake_share_server_get_result['share_server'], + ctxt, share_server['id']) + self.assertEqual(share_server, result['share_server']) @ddt.data( @@ -389,7 +422,7 @@ class ShareServerAPITest(test.TestCase): 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']) + .share_network_subnets[0]['share_network_id']) db_api.share_network_get.assert_called_once_with( ctxt, exp_share_net_id) diff --git a/manila/tests/api/v1/test_shares.py b/manila/tests/api/v1/test_shares.py index 58c9259ddf..567b38e092 100644 --- a/manila/tests/api/v1/test_shares.py +++ b/manila/tests/api/v1/test_shares.py @@ -242,7 +242,7 @@ class ShareAPITest(test.TestCase): self.mock_object(common, 'check_share_network_is_active', mock.Mock(return_value=True)) self.mock_object( - db, 'share_network_subnet_get_by_availability_zone_id', + db, 'share_network_subnets_get_all_by_availability_zone_id', mock.Mock(return_value={'id': 'fakesubnetid'})) body = {"share": copy.deepcopy(shr)} @@ -339,6 +339,8 @@ class ShareAPITest(test.TestCase): } parent_share_net = 444 fake_share_net = {'id': parent_share_net} + share_net_subnets = [db_utils.create_share_network_subnet( + id='fake_subnet_id', share_network_id=fake_share_net['id'])] create_mock = mock.Mock(return_value=stubs.stub_share('1', display_name=shr['name'], display_description=shr['description'], @@ -361,7 +363,8 @@ class ShareAPITest(test.TestCase): self.mock_object(common, 'check_share_network_is_active', mock.Mock(return_value=True)) self.mock_object( - db, 'share_network_subnet_get_by_availability_zone_id') + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=share_net_subnets)) body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/v1/fake/shares') @@ -391,6 +394,8 @@ class ShareAPITest(test.TestCase): "share_network_id": parent_share_net } fake_share_net = {'id': parent_share_net} + share_net_subnets = [db_utils.create_share_network_subnet( + id='fake_subnet_id', share_network_id=fake_share_net['id'])] create_mock = mock.Mock(return_value=stubs.stub_share('1', display_name=shr['name'], display_description=shr['description'], @@ -413,7 +418,8 @@ class ShareAPITest(test.TestCase): self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value=fake_share_net)) self.mock_object( - db, 'share_network_subnet_get_by_availability_zone_id') + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=share_net_subnets)) body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/v1/fake/shares') @@ -468,6 +474,8 @@ class ShareAPITest(test.TestCase): "share_network_id": parent_share_net } fake_share_net = {'id': parent_share_net} + share_net_subnets = [db_utils.create_share_network_subnet( + id='fake_subnet_id', share_network_id=fake_share_net['id'])] create_mock = mock.Mock(return_value=stubs.stub_share('1', display_name=shr['name'], display_description=shr['description'], @@ -490,7 +498,8 @@ class ShareAPITest(test.TestCase): self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value=fake_share_net)) self.mock_object( - db, 'share_network_subnet_get_by_availability_zone_id') + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=share_net_subnets)) body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/v1/fake/shares', version=microversion) @@ -558,7 +567,7 @@ class ShareAPITest(test.TestCase): 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', + db, 'share_network_subnets_get_all_by_availability_zone_id', mock.Mock(return_value=None)) self.mock_object(common, 'check_share_network_is_active') diff --git a/manila/tests/api/v2/test_share_network_subnets.py b/manila/tests/api/v2/test_share_network_subnets.py index d00ba8927c..53461f9395 100644 --- a/manila/tests/api/v2/test_share_network_subnets.py +++ b/manila/tests/api/v2/test_share_network_subnets.py @@ -20,6 +20,7 @@ import ddt from oslo_db import exception as db_exception from manila.api import common +from manila.api.openstack import api_version_request as api_version from manila.api.v2 import share_network_subnets from manila.db import api as db_api from manila import exception @@ -61,16 +62,17 @@ class ShareNetworkSubnetControllerTest(test.TestCase): mock.Mock(return_value=fake_az)) self.share_network = db_utils.create_share_network( name='fake_network', id='fake_sn_id') - self.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_server = db_utils.create_share_server( + share_network_subnets=[self.subnet]) 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'] + self.subnet['share_servers'] = [self.share_server] mock_sns_get = self.mock_object( db_api, 'share_network_subnet_get', @@ -150,6 +152,7 @@ class ShareNetworkSubnetControllerTest(test.TestCase): req = fakes.HTTPRequest.blank('/subnets/%s' % self.subnet['id'], version="2.51") context = req.environ['manila.context'] + self.subnet['share_servers'] = [self.share_server] mock_sns_get = self.mock_object( db_api, 'share_network_subnet_get', @@ -182,7 +185,10 @@ class ShareNetworkSubnetControllerTest(test.TestCase): req = fakes.HTTPRequest.blank('/subnets/%s' % self.subnet['id'], version="2.51") context = req.environ['manila.context'] + self.subnet['share_servers'] = [self.share_server] + mock_network_get = self.mock_object( + db_api, 'share_network_get') mock_sns_get = self.mock_object( db_api, 'share_network_subnet_get', mock.Mock(return_value=self.subnet)) @@ -196,6 +202,8 @@ class ShareNetworkSubnetControllerTest(test.TestCase): self.share_network['id'], self.subnet['id']) + mock_network_get.assert_called_once_with( + context, self.share_network['id']) mock_sns_get.assert_called_once_with( context, self.subnet['id']) mock_all_get_all_shares_by_ss.assert_called_once_with( @@ -204,34 +212,6 @@ class ShareNetworkSubnetControllerTest(test.TestCase): 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'], @@ -241,131 +221,145 @@ class ShareNetworkSubnetControllerTest(test.TestCase): } return body - def test_subnet_create(self): - req = fakes.HTTPRequest.blank('/subnets', version="2.51") + @ddt.data({'version': "2.51", 'has_share_servers': False}, + {'version': "2.70", 'has_share_servers': False}, + {'version': "2.70", 'has_share_servers': True}) + @ddt.unpack + def test_subnet_create(self, version, has_share_servers): + req = fakes.HTTPRequest.blank('/subnets', version=version) + multiple_subnet_support = (req.api_version_request >= + api_version.APIVersionRequest("2.70")) + 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_subnet = copy.deepcopy(self.subnet) + if has_share_servers: + expected_subnet['share_servers'] = [self.share_server] + + mock_validate_subnet_create = self.mock_object( + common, 'validate_subnet_create', + mock.Mock(return_value=(self.share_network, [expected_subnet]))) + mock_subnet_create = self.mock_object( + db_api, 'share_network_subnet_create', + mock.Mock(return_value=expected_subnet)) + mock_update_net_allocations = self.mock_object( + self.controller.share_api, + 'update_share_server_network_allocations', + mock.Mock(return_value=expected_subnet)) + mock_share_network_subnet_get = self.mock_object( + db_api, 'share_network_subnet_get', + mock.Mock(return_value=expected_subnet)) + + fake_data = body['share-network-subnet'] + fake_data['share_network_id'] = self.share_network['id'] + res = self.controller.create( + req, body['share-network-subnet']['share_network_id'], body) + + view_subnet = { + 'id': expected_subnet.get('id'), + 'availability_zone': expected_subnet.get('availability_zone'), + 'share_network_id': expected_subnet.get('share_network_id'), + 'share_network_name': expected_subnet['share_network_name'], + 'created_at': expected_subnet.get('created_at'), + 'segmentation_id': expected_subnet.get('segmentation_id'), + 'neutron_subnet_id': expected_subnet.get('neutron_subnet_id'), + 'updated_at': expected_subnet.get('updated_at'), + 'neutron_net_id': expected_subnet.get('neutron_net_id'), + 'ip_version': expected_subnet.get('ip_version'), + 'cidr': expected_subnet.get('cidr'), + 'network_type': expected_subnet.get('network_type'), + 'mtu': expected_subnet.get('mtu'), + 'gateway': expected_subnet.get('gateway') + } + self.assertEqual(view_subnet, res['share_network_subnet']) + mock_share_network_subnet_get.assert_called_once_with( + context, expected_subnet['id']) + mock_validate_subnet_create.assert_called_once_with( + context, sn_id, fake_data, multiple_subnet_support) + if has_share_servers: + fake_data['share_servers'] = [self.share_server] + mock_update_net_allocations.assert_called_once_with( + context, self.share_network, fake_data) + else: + mock_subnet_create.assert_called_once_with( + context, fake_data) + + @ddt.data({'exception1': exception.ServiceIsDown(), + 'exc_raise': exc.HTTPInternalServerError}, + {'exception1': exception.InvalidShareNetwork(), + 'exc_raise': exc.HTTPBadRequest}, + {'exception1': db_exception.DBError(), + 'exc_raise': exc.HTTPInternalServerError}) + @ddt.unpack + def test_subnet_create_fail_update_network_allocation(self, exception1, + exc_raise): + req = fakes.HTTPRequest.blank('/subnets', version="2.70") + multiple_subnet_support = (req.api_version_request >= + api_version.APIVersionRequest("2.70")) 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)) + expected_subnet = copy.deepcopy(self.subnet) + expected_subnet['share_servers'] = [self.share_server] - self.controller.create( - req, body['share-network-subnet']['share_network_id'], body) + mock_validate_subnet_create = self.mock_object( + common, 'validate_subnet_create', + mock.Mock(return_value=(self.share_network, [expected_subnet]))) + mock_update_net_allocations = self.mock_object( + self.controller.share_api, + 'update_share_server_network_allocations', + mock.Mock(side_effect=exception1)) - 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']) + fake_data = body['share-network-subnet'] + fake_data['share_network_id'] = self.share_network['id'] + fake_data['share_servers'] = [self.share_server] - 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.assertRaises(exc_raise, self.controller.create, req, - fake_sn_id, + body['share-network-subnet']['share_network_id'], body) - mock_sn_get.assert_called_once_with(context, fake_sn_id) - def test_subnet_create_az_not_found(self): + mock_validate_subnet_create.assert_called_once_with( + context, sn_id, fake_data, multiple_subnet_support) + mock_update_net_allocations.assert_called_once_with( + context, self.share_network, fake_data) + + def test_subnet_create_invalid_body(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'] - + body = {} 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'] + @ddt.data("2.51", "2.70") + def test_subnet_create_subnet_db_error(self, version): + req = fakes.HTTPRequest.blank('/subnets', version=version) 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( + expected_subnet = copy.deepcopy(self.subnet) + self.mock_object( + common, 'validate_subnet_create', + mock.Mock(return_value=(self.share_network, [expected_subnet]))) + 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, + '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']) diff --git a/manila/tests/api/v2/test_share_networks.py b/manila/tests/api/v2/test_share_networks.py index 7d00bbbd2d..44cecf1179 100644 --- a/manila/tests/api/v2/test_share_networks.py +++ b/manila/tests/api/v2/test_share_networks.py @@ -776,6 +776,13 @@ class ShareNetworkAPITest(test.TestCase): share_nw, self.body) + def test_update_invalid_body(self): + self.assertRaises(webob_exc.HTTPUnprocessableEntity, + self.controller.update, + self.req, + 'fake_sn_id', + None) + @mock.patch.object(db_api, 'share_network_get', mock.Mock()) def test_update_invalid_key_in_use(self): share_nw = fake_share_network.copy() @@ -829,8 +836,8 @@ class ShareNetworkAPITest(test.TestCase): self.mock_object( self.controller, '_share_network_subnets_contain_share_servers', mock.Mock(return_value=False)) - self.mock_object(db_api, 'share_network_subnet_get_default_subnet', - mock.Mock(return_value=fake_share_network_subnet)) + self.mock_object(db_api, 'share_network_subnet_get_default_subnets', + mock.Mock(return_value=[fake_share_network_subnet])) self.mock_object(db_api, 'share_network_subnet_update') body = {share_networks.RESOURCE_NAME: {'neutron_subnet_id': @@ -844,18 +851,20 @@ class ShareNetworkAPITest(test.TestCase): self.req, share_nw, body) - db_api.share_network_subnet_get_default_subnet.assert_called_once_with( - self.context, share_nw) + (db_api.share_network_subnet_get_default_subnets. + assert_called_once_with(self.context, share_nw)) db_api.share_network_subnet_update.assert_called_once_with( self.context, fake_share_network_subnet['id'], body['share_network']) - @ddt.data((webob_exc.HTTPBadRequest, fake_share_network_subnet, None, + @ddt.data((webob_exc.HTTPBadRequest, 1, None, + 'new subnet'), + (webob_exc.HTTPBadRequest, 2, None, 'new subnet'), (webob_exc.HTTPBadRequest, None, 'neutron net', None)) @ddt.unpack def test_update_default_subnet_errors(self, exception_to_raise, - get_default_subnet_return, + get_default_subnet_return_length, neutron_net_id, neutron_subnet_id): share_nw = 'fake network id' self.mock_object(db_api, 'share_network_get', @@ -863,15 +872,21 @@ class ShareNetworkAPITest(test.TestCase): self.mock_object( self.controller, '_share_network_subnets_contain_share_servers', mock.Mock(return_value=False)) - self.mock_object(db_api, 'share_network_subnet_get_default_subnet', - mock.Mock(return_value=get_default_subnet_return)) + self.mock_object(db_api, 'share_network_subnet_get_default_subnets', + mock.Mock(return_value=None)) - if get_default_subnet_return: + if get_default_subnet_return_length: fake_subnet = copy.deepcopy(fake_share_network_subnet) fake_subnet['neutron_net_id'] = None fake_subnet['neutron_subnet_id'] = None - db_api.share_network_subnet_get_default_subnet.return_value = ( - fake_subnet) + + if get_default_subnet_return_length == 1: + (db_api.share_network_subnet_get_default_subnets. + return_value) = [fake_subnet] + elif get_default_subnet_return_length == 2: + (db_api.share_network_subnet_get_default_subnets. + return_value) = [fake_subnet, fake_subnet] + body = { share_networks.RESOURCE_NAME: { 'neutron_net_id': neutron_net_id, @@ -885,8 +900,8 @@ class ShareNetworkAPITest(test.TestCase): share_nw, body) - db_api.share_network_subnet_get_default_subnet.assert_called_once_with( - self.context, share_nw) + (db_api.share_network_subnet_get_default_subnets. + assert_called_once_with(self.context, share_nw)) @ddt.data(*set(("1.0", "2.25", "2.26", api_version._MAX_API_VERSION))) def test_add_security_service(self, microversion): @@ -1620,3 +1635,82 @@ class ShareNetworkAPITest(test.TestCase): db_api.share_network_update.assert_called_once_with( request.environ['manila.context'], share_network['id'], {'status': 'active'}) + + @ddt.data([], ['fake_server']) + def test_share_network_subnet_create_check(self, servers): + body = { + 'share_network_subnet_create_check': { + 'reset_operation': False, + 'availability_zone': 'fake_az', + } + } + request = fakes.HTTPRequest.blank( + '/share-networks', use_admin_context=True, version='2.70') + context = request.environ['manila.context'] + + share_net = 'fake_net' + subnet = {'share_servers': servers} + existing_subnets = [subnet] + mock_validate_subnet = self.mock_object( + common, 'validate_subnet_create', + mock.Mock(return_value=(share_net, existing_subnets))) + share_api_return = { + 'compatible': not bool(servers), + 'hosts_check_result': {} + } + mock_check_update = self.mock_object( + self.controller.share_api, + 'check_update_share_server_network_allocations', + mock.Mock(return_value=share_api_return)) + subnet_view = 'fake_subnet' + mock_view = self.mock_object( + self.controller._view_builder, + 'build_share_network_subnet_create_check', + mock.Mock(return_value=subnet_view)) + + net_id = 'fake_net_id' + response = self.controller.share_network_subnet_create_check( + request, net_id, body) + + self.assertEqual(subnet_view, response) + data = body['share_network_subnet_create_check'] + mock_validate_subnet.assert_called_once_with( + context, net_id, data, True) + if servers: + data['share_servers'] = servers + mock_check_update.assert_called_once_with( + context, share_net, data, False) + else: + mock_check_update.assert_not_called() + mock_view.assert_called_once_with(request, share_api_return) + + @ddt.data( + (exception.ServiceIsDown(message='fake'), + webob_exc.HTTPInternalServerError), + (exception.InvalidShareNetwork(message='fake'), + webob_exc.HTTPBadRequest)) + @ddt.unpack + def test_share_network_subnet_create_check_api_failed( + self, captured_exception, exception_to_be_raised): + body = { + 'share_network_subnet_create_check': { + 'reset_operation': False, + 'availability_zone': 'fake_az', + } + } + request = fakes.HTTPRequest.blank( + '/share-networks', use_admin_context=True, version='2.70') + share_net = 'fake_net' + subnet = {'share_servers': 'fake_server'} + existing_subnets = [subnet] + self.mock_object( + common, 'validate_subnet_create', + mock.Mock(return_value=(share_net, existing_subnets))) + self.mock_object( + self.controller.share_api, + 'check_update_share_server_network_allocations', + mock.Mock(side_effect=captured_exception)) + + self.assertRaises(exception_to_be_raised, + self.controller.share_network_subnet_create_check, + request, 'fake_net_id', body) diff --git a/manila/tests/api/v2/test_share_servers.py b/manila/tests/api/v2/test_share_servers.py index fcd7ed2161..0e2c56cece 100644 --- a/manila/tests/api/v2/test_share_servers.py +++ b/manila/tests/api/v2/test_share_servers.py @@ -120,17 +120,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_net_subnet = [db_utils.create_share_network_subnet( + share_network_id=share_network['id'])] share_server = db_utils.create_share_server( - share_network_subnet_id=share_net_subnet['id'], + share_network_subnet_id=share_net_subnet[0]['id'], host='fake_host', identifier='fake_identifier', - is_auto_deletable=False) + is_auto_deletable=False, + share_network_subnets=share_net_subnet) 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', + self.mock_object(db_api, 'share_network_subnet_get_default_subnets', mock.Mock(return_value=share_net_subnet)) self.mock_object(utils, 'validate_service_host') @@ -151,7 +152,8 @@ class ShareServerControllerTest(test.TestCase): 'status': constants.STATUS_ACTIVE, 'host': 'fake_host', 'share_network_id': - share_server['share_network_subnet']['share_network_id'], + (share_server['share_network_subnets'][0] + ['share_network_id']), 'created_at': share_server['created_at'], 'backend_details': {}, 'identifier': share_server['identifier'], @@ -163,12 +165,12 @@ class ShareServerControllerTest(test.TestCase): 'fake_net_name') else: expected_result['share_server']['share_network_name'] = ( - share_net_subnet['share_network_id']) + share_net_subnet[0]['share_network_id']) req_params = body['share_server'] manage_share_server_mock.assert_called_once_with( context, req_params['identifier'], req_params['host'], - share_net_subnet, req_params['driver_options']) + share_net_subnet[0], req_params['driver_options']) self.assertEqual(expected_result, result) @@ -178,32 +180,23 @@ class ShareServerControllerTest(test.TestCase): def test_manage_invalid(self): req = fakes.HTTPRequest.blank('/manage_share_server', 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']) + share_net_subnet = [db_utils.create_share_network_subnet( + share_network_id=share_network['id'])] body = { 'share_server': self._setup_manage_test_request_body() } + body['share_server']['driver_options'] = [] 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', + self.mock_object(db_api, 'share_network_subnet_get_default_subnets', mock.Mock(return_value=share_net_subnet)) - manage_share_server_mock = self.mock_object( - share_api.API, 'manage_share_server', - mock.Mock(side_effect=exception.InvalidInput('foobar'))) - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.manage, req, body) - req_params = body['share_server'] - manage_share_server_mock.assert_called_once_with( - context, req_params['identifier'], req_params['host'], - 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', version="2.49") @@ -211,12 +204,12 @@ class ShareServerControllerTest(test.TestCase): 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']) + 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', + self.mock_object(db_api, 'share_network_subnet_get_default_subnets', mock.Mock(return_value=share_net_subnet)) self.mock_object(utils, 'validate_service_host') @@ -281,12 +274,12 @@ class ShareServerControllerTest(test.TestCase): 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']) + 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', + self.mock_object(db_api, 'share_network_subnet_get_default_subnets', mock.Mock(return_value=share_net_subnet)) self.mock_object(common, 'check_share_network_is_active', mock.Mock(return_value=True)) @@ -296,7 +289,7 @@ class ShareServerControllerTest(test.TestCase): {'share_server': self._setup_manage_test_request_body()}) common.check_share_network_is_active.assert_called_once_with( - share_net_subnet['share_network']) + share_net_subnet[0]['share_network']) policy.check_policy.assert_called_once_with( context, self.resource_name, 'manage_share_server') @@ -305,12 +298,12 @@ class ShareServerControllerTest(test.TestCase): 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']) + 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', + self.mock_object(db_api, 'share_network_subnet_get_default_subnets', mock.Mock(return_value=share_net_subnet)) self.mock_object(utils, 'validate_service_host') self.mock_object(common, 'check_share_network_is_active', @@ -321,7 +314,7 @@ class ShareServerControllerTest(test.TestCase): {'share_server': self._setup_manage_test_request_body()}) common.check_share_network_is_active.assert_called_once_with( - share_net_subnet['share_network']) + share_net_subnet[0]['share_network']) policy.check_policy.assert_called_once_with( context, self.resource_name, 'manage_share_server') @@ -377,16 +370,16 @@ class ShareServerControllerTest(test.TestCase): 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']) + 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) + share_net_subnet[0]['id'] if body_contains_subnet else None) self.mock_object( - db_api, 'share_network_subnet_get', + db_api, 'share_network_subnet_get_all_with_same_az', mock.Mock(side_effect=exception.ShareNetworkSubnetNotFound( share_network_subnet_id='fake'))) - self.mock_object(db_api, 'share_network_subnet_get_default_subnet', + self.mock_object(db_api, 'share_network_subnet_get_default_subnets', mock.Mock(return_value=None)) self.assertRaises(webob.exc.HTTPBadRequest, @@ -397,10 +390,47 @@ class ShareServerControllerTest(test.TestCase): 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']) + (db_api.share_network_subnet_get_all_with_same_az. + assert_called_once_with(context, share_net_subnet[0]['id'])) else: - (db_api.share_network_subnet_get_default_subnet + (db_api.share_network_subnet_get_default_subnets + .assert_called_once_with( + context, body['share_server']['share_network_id'])) + + @ddt.data(True, False) + def test__validate_manage_share_server_error_multiple_subnet( + self, body_contains_subnet): + req = fakes.HTTPRequest.blank('/manage', version="2.70") + context = req.environ['manila.context'] + share_network = db_utils.create_share_network() + body = {'share_server': self._setup_manage_test_request_body()} + share_net_subnets = [ + db_utils.create_share_network_subnet( + share_network_id=share_network['id']), + db_utils.create_share_network_subnet( + share_network_id=share_network['id'], id='fake_sns_id_2'), + ] + body['share_server']['share_network_subnet_id'] = ( + share_net_subnets[0]['id'] if body_contains_subnet else None) + + self.mock_object( + db_api, 'share_network_subnet_get_all_with_same_az', + mock.Mock(return_value=share_net_subnets)) + self.mock_object(db_api, 'share_network_subnet_get_default_subnets', + mock.Mock(return_value=share_net_subnets)) + + 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_all_with_same_az. + assert_called_once_with(context, share_net_subnets[0]['id'])) + else: + (db_api.share_network_subnet_get_default_subnets .assert_called_once_with( context, body['share_server']['share_network_id'])) @@ -442,6 +472,23 @@ class ShareServerControllerTest(test.TestCase): get_mock.assert_called_once_with(context, 'fake_server_id') + def test_unmanage_share_server_multiple_subnets_fail(self): + """Tests unmanaging share servers""" + server = self._setup_unmanage_tests(multiple_subnets=True) + get_mock = self.mock_object(db_api, 'share_server_get', + mock.Mock(return_value=server)) + req = fakes.HTTPRequest.blank('/unmanage_share_server', version="2.70") + context = req.environ['manila.context'] + body = {'unmanage': {'force': True}} + + self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.unmanage, + req, + server['id'], + body) + + get_mock.assert_called_once_with(context, server['id']) + @ddt.data(constants.STATUS_MANAGING, constants.STATUS_DELETING, constants.STATUS_CREATING, constants.STATUS_UNMANAGING) def test_unmanage_share_server_invalid_statuses(self, status): @@ -461,18 +508,24 @@ class ShareServerControllerTest(test.TestCase): get_mock.assert_called_once_with(context, server['id']) - def _setup_unmanage_tests(self, status=constants.STATUS_ACTIVE): - server = db_utils.create_share_server( - id='fake_server_id', status=status) + def _setup_unmanage_tests(self, status=constants.STATUS_ACTIVE, + multiple_subnets=False): share_network = db_utils.create_share_network() - network_subnet = db_utils.create_share_network_subnet( - share_network_id=share_network['id']) + network_subnets = [db_utils.create_share_network_subnet( + id='fake_sns_id', share_network_id=share_network['id'])] + if multiple_subnets: + share_network1 = db_utils.create_share_network() + network_subnets.append(db_utils.create_share_network_subnet( + share_network_id=share_network1['id'], id='fake_sns_id_2')) + server = db_utils.create_share_server( + id='fake_server_id', status=status, + share_network_subnets=network_subnets) self.mock_object(db_api, 'share_server_get', mock.Mock(return_value=server)) self.mock_object(db_api, 'share_network_get', mock.Mock(return_value=share_network)) self.mock_object(db_api, 'share_network_subnet_get', - mock.Mock(return_value=network_subnet)) + mock.Mock(return_value=network_subnets)) return server @ddt.data(exception.ShareServerInUse, exception.PolicyNotAuthorized) @@ -505,13 +558,11 @@ class ShareServerControllerTest(test.TestCase): '/v2/share-servers/fake_server_id/', version="2.63") context = req.environ['manila.context'] share_server = db_utils.create_share_server() - network_subnet = db_utils.create_share_network_subnet() + network_subnets = [db_utils.create_share_network_subnet()] + share_server['share_network_subnets'] = network_subnets share_network = db_utils.create_share_network() get_mock = self.mock_object( db_api, 'share_server_get', mock.Mock(return_value=share_server)) - get_subnet_mock = self.mock_object( - db_api, 'share_network_subnet_get', - mock.Mock(return_value=network_subnet)) get_network_mock = self.mock_object( db_api, 'share_network_get', mock.Mock(return_value=share_network)) @@ -526,10 +577,9 @@ class ShareServerControllerTest(test.TestCase): 'fake_server_id', body) get_mock.assert_called_once_with(context, 'fake_server_id') - get_subnet_mock.assert_called_once_with( - context, share_server.get('share_network_subnet_id')) get_network_mock.assert_called_once_with( - context, network_subnet['share_network_id']) + context, + share_server['share_network_subnets'][0]['share_network_id']) is_active_mock.assert_called_once_with(share_network) def _get_server_migration_request(self, server_id, version='2.57'): @@ -589,11 +639,12 @@ class ShareServerControllerTest(test.TestCase): def test__share_server_migration_start_conflict(self, api_exception, expected_exception): share_network = db_utils.create_share_network() - share_network_subnet = db_utils.create_share_network_subnet( - share_network_id=share_network['id']) + share_network_subnet = [db_utils.create_share_network_subnet( + share_network_id=share_network['id'])] server = db_utils.create_share_server( id='fake_server_id', status=constants.STATUS_ACTIVE, - share_network_subnet_id=share_network_subnet['id']) + share_network_subnet_id=share_network_subnet[0]['id']) + server['share_network_subnets'] = share_network_subnet req = self._get_server_migration_request(server['id']) context = req.environ['manila.context'] body = { diff --git a/manila/tests/api/v2/test_shares.py b/manila/tests/api/v2/test_shares.py index aae30f7125..59f6bef3f8 100644 --- a/manila/tests/api/v2/test_shares.py +++ b/manila/tests/api/v2/test_shares.py @@ -715,6 +715,8 @@ class ShareAPITest(test.TestCase): "share_network_id": "fakenetid" } fake_network = {'id': 'fakenetid'} + share_net_subnets = [db_utils.create_share_network_subnet( + id='fake_subnet_id', share_network_id=fake_network['id'])] create_mock = mock.Mock(return_value=stubs.stub_share('1', display_name=shr['name'], display_description=shr['description'], @@ -728,7 +730,8 @@ class ShareAPITest(test.TestCase): self.mock_object(common, 'check_share_network_is_active', mock.Mock(return_value=True)) self.mock_object( - db, 'share_network_subnet_get_by_availability_zone_id') + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=share_net_subnets)) body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/v2/fake/shares', version='2.7') @@ -1342,6 +1345,8 @@ class ShareAPITest(test.TestCase): } parent_share_net = 444 fake_network = {'id': parent_share_net} + share_net_subnets = [db_utils.create_share_network_subnet( + id='fake_subnet_id', share_network_id=fake_network['id'])] create_mock = mock.Mock(return_value=stubs.stub_share('1', display_name=shr['name'], display_description=shr['description'], @@ -1364,7 +1369,8 @@ class ShareAPITest(test.TestCase): self.mock_object(share_api.API, 'get_share_network', mock.Mock( return_value=fake_network)) self.mock_object( - db, 'share_network_subnet_get_by_availability_zone_id') + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=share_net_subnets)) body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/v2/fake/shares', version='2.7') @@ -1391,6 +1397,8 @@ class ShareAPITest(test.TestCase): "snapshot_id": 333, "share_network_id": parent_share_net, } + share_net_subnets = [db_utils.create_share_network_subnet( + id='fake_subnet_id', share_network_id=parent_share_net)] create_mock = mock.Mock(return_value=stubs.stub_share('1', display_name=shr['name'], display_description=shr['description'], @@ -1413,7 +1421,8 @@ class ShareAPITest(test.TestCase): self.mock_object(common, 'check_share_network_is_active', mock.Mock(return_value=True)) self.mock_object( - db, 'share_network_subnet_get_by_availability_zone_id') + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=share_net_subnets)) body = {"share": copy.deepcopy(shr)} req = fakes.HTTPRequest.blank('/v2/fake/shares', version='2.7') diff --git a/manila/tests/api/views/test_share_networks.py b/manila/tests/api/views/test_share_networks.py index 44c2397042..98e6583fbb 100644 --- a/manila/tests/api/views/test_share_networks.py +++ b/manila/tests/api/views/test_share_networks.py @@ -65,6 +65,9 @@ class ViewBuilderTestCase(test.TestCase): status_and_sec_serv_update = ( api_version.APIVersionRequest(microversion) >= api_version.APIVersionRequest('2.63')) + network_allocation_update_support = ( + api_version.APIVersionRequest(microversion) >= + api_version.APIVersionRequest('2.69')) req = fakes.HTTPRequest.blank('/share-networks', version=microversion) expected_keys = { 'id', 'name', 'project_id', 'created_at', 'updated_at', @@ -85,6 +88,8 @@ class ViewBuilderTestCase(test.TestCase): expected_keys.add('nova_net_id') if status_and_sec_serv_update: expected_keys.update({'status', 'security_service_update_support'}) + if network_allocation_update_support: + expected_keys.add('network_allocation_update_support') result = self.builder.build_share_network(req, share_network_data) self.assertEqual(1, len(result)) @@ -137,6 +142,10 @@ class ViewBuilderTestCase(test.TestCase): status_and_sec_serv_update = ( api_version.APIVersionRequest(microversion) >= api_version.APIVersionRequest('2.63')) + network_allocation_update_support = ( + api_version.APIVersionRequest(microversion) >= + api_version.APIVersionRequest('2.69')) + req = fakes.HTTPRequest.blank('/share-networks', version=microversion) expected_networks_list = [] for share_network in share_networks: @@ -181,7 +190,13 @@ class ViewBuilderTestCase(test.TestCase): expected_data.update( {'status': 'active', 'security_service_update_support': False}) + if network_allocation_update_support: + share_network.update( + {'network_allocation_update_support': None}) + expected_data.update( + {'network_allocation_update_support': None}) expected_networks_list.append(expected_data) + expected = {'share_networks': expected_networks_list} result = self.builder.build_share_networks(req, share_networks, @@ -248,3 +263,20 @@ class ViewBuilderTestCase(test.TestCase): hosts_result) self.assertEqual(expected, result) + + @ddt.data(True, False) + def test_build_share_network_subnet_create_check(self, is_admin): + req = fakes.HTTPRequest.blank('/share-networks', + use_admin_context=is_admin) + hosts_result = { + 'compatible': True, + 'hosts_check_result': {'hostA': True} + } + expected = {'compatible': True} + if is_admin: + expected['hosts_check_result'] = hosts_result['hosts_check_result'] + + result = self.builder.build_share_network_subnet_create_check( + req, hosts_result) + + self.assertEqual(expected, result) diff --git a/manila/tests/cmd/test_manage.py b/manila/tests/cmd/test_manage.py index ed6e43f715..7d6020c6e0 100644 --- a/manila/tests/cmd/test_manage.py +++ b/manila/tests/cmd/test_manage.py @@ -446,26 +446,28 @@ class ManilaCmdManageTestCase(test.TestCase): share_servers = 'server_id_a,server_id_b' share_server_list = [server.strip() for server in share_servers.split(",")] - capability = 'security_service_update_support' - values_to_update = { - capability: True - } + capabilities = "security_service_update_support" \ + ",network_allocation_update_support" + capabilities_list = capabilities.split(",") + values_to_update = [ + {capabilities_list[0]: True, + capabilities_list[1]: True}] with mock.patch('sys.stdout', new=io.StringIO()) as output: self.server_cmds.update_share_server_capabilities( - share_servers, capability, True) + share_servers, capabilities, True) expected_op = ("The capability(ies) %(cap)s of the following share " "server(s) %(servers)s was(were) updated to " "%(value)s.") % { - 'cap': [capability], + 'cap': capabilities_list, 'servers': share_server_list, 'value': True, } self.assertEqual(expected_op, output.getvalue().strip()) db.share_servers_update.assert_called_once_with( - 'admin_ctxt', share_server_list, values_to_update) + 'admin_ctxt', share_server_list, values_to_update[0]) def test_share_server_update_capability_not_supported(self): share_servers = 'server_id_a' diff --git a/manila/tests/db/migrations/alembic/migrations_data_checks.py b/manila/tests/db/migrations/alembic/migrations_data_checks.py index 2801387e5e..6da7949603 100644 --- a/manila/tests/db/migrations/alembic/migrations_data_checks.py +++ b/manila/tests/db/migrations/alembic/migrations_data_checks.py @@ -3084,3 +3084,94 @@ class ShareIsSoftDeleted(BaseMigrationChecks): self.test_case.assertFalse(hasattr(s, 'is_soft_deleted')) self.test_case.assertFalse(hasattr(s, 'scheduled_to_be_deleted_at')) + + +@map_to_migration('a87e0fb17dee') +class ShareServerMultipleSubnets(BaseMigrationChecks): + + def setup_upgrade_data(self, engine): + user_id = 'user_id_multiple_subnets' + project_id = 'project_id_multiple_subnets' + + # Create share network + share_network_data = { + 'id': uuidutils.generate_uuid(), + 'user_id': user_id, + 'project_id': project_id, + } + sn_table = utils.load_table('share_networks', engine) + engine.execute(sn_table.insert(share_network_data)) + + # Create share network subnets + share_network_subnet_data = { + 'id': uuidutils.generate_uuid(), + 'share_network_id': share_network_data['id'] + } + sns_table = utils.load_table('share_network_subnets', engine) + engine.execute(sns_table.insert(share_network_subnet_data)) + + # Create share server + share_server_data = { + 'id': uuidutils.generate_uuid(), + 'host': 'fake_host', + 'status': 'active', + 'share_network_subnet_id': share_network_subnet_data['id'], + } + ss_table = utils.load_table('share_servers', engine) + engine.execute(ss_table.insert(share_server_data)) + + def check_upgrade(self, engine, data): + ss_sns_map_table = utils.load_table( + 'share_server_share_network_subnet_mappings', engine) + ss_table = utils.load_table('share_servers', engine) + sns_table = utils.load_table('share_network_subnets', engine) + na_table = utils.load_table('network_allocations', engine) + + na_record = engine.execute(na_table.select()).first() + self.test_case.assertFalse(na_record is None) + self.test_case.assertTrue( + hasattr(na_record, 'share_network_subnet_id')) + + for map_record in engine.execute(ss_sns_map_table.select()): + self.test_case.assertTrue( + hasattr(map_record, 'share_network_subnet_id')) + self.test_case.assertTrue( + hasattr(map_record, 'share_server_id')) + + ss_record = engine.execute( + ss_table + .select() + .where(ss_table.c.id == map_record['share_server_id']) + ).first() + self.test_case.assertFalse(ss_record is None) + self.test_case.assertFalse( + hasattr(ss_record, 'share_network_subnet_id')) + self.test_case.assertTrue( + hasattr(ss_record, 'network_allocation_update_support')) + + sns_record = engine.execute( + sns_table + .select() + .where(sns_table.c.id == map_record['share_network_subnet_id']) + ).first() + self.test_case.assertFalse(sns_record is None) + + def check_downgrade(self, engine): + ss_table = utils.load_table('share_servers', engine) + na_table = utils.load_table('network_allocations', engine) + self.test_case.assertRaises( + sa_exc.NoSuchTableError, utils.load_table, + 'share_server_share_network_subnet_mappings', engine) + + for ss_record in engine.execute(ss_table.select()): + self.test_case.assertTrue( + hasattr(ss_record, 'share_network_subnet_id')) + self.test_case.assertFalse( + hasattr(ss_record, 'network_allocation_update_support')) + + na_record = engine.execute( + na_table + .select() + ).first() + self.test_case.assertFalse( + hasattr(na_record, 'share_network_subnet_id')) diff --git a/manila/tests/db/sqlalchemy/test_api.py b/manila/tests/db/sqlalchemy/test_api.py index e1d9fcb87f..e55f524c02 100644 --- a/manila/tests/db/sqlalchemy/test_api.py +++ b/manila/tests/db/sqlalchemy/test_api.py @@ -366,8 +366,7 @@ class ShareDatabaseAPITestCase(test.TestCase): def test_share_filter_all_by_share_server(self): share_network = db_utils.create_share_network() - share_server = db_utils.create_share_server( - share_network_id=share_network['id']) + share_server = db_utils.create_share_server() share = db_utils.create_share(share_server_id=share_server['id'], share_network_id=share_network['id']) @@ -379,8 +378,7 @@ class ShareDatabaseAPITestCase(test.TestCase): def test_share_in_recycle_bin_filter_all_by_share_server(self): share_network = db_utils.create_share_network() - share_server = db_utils.create_share_server( - share_network_id=share_network['id']) + share_server = db_utils.create_share_server() share = db_utils.create_share(share_server_id=share_server['id'], share_network_id=share_network['id'], is_soft_deleted=True) @@ -393,8 +391,7 @@ class ShareDatabaseAPITestCase(test.TestCase): def test_share_in_recycle_bin_filter_all_by_share_network(self): share_network = db_utils.create_share_network() - share_server = db_utils.create_share_server( - share_network_id=share_network['id']) + share_server = db_utils.create_share_server() share = db_utils.create_share(share_server_id=share_server['id'], share_network_id=share_network['id'], is_soft_deleted=True) @@ -798,7 +795,7 @@ class ShareDatabaseAPITestCase(test.TestCase): db_utils.create_share_replica(share_id=share_2['id']) expected_ss_keys = { 'backend_details', 'host', 'id', - 'share_network_subnet_id', 'status', + 'share_network_subnet_ids', 'status', } expected_share_keys = { 'project_id', 'share_type_id', 'display_name', @@ -846,7 +843,7 @@ class ShareDatabaseAPITestCase(test.TestCase): share_server_id=share_server['id']) expected_ss_keys = { 'backend_details', 'host', 'id', - 'share_network_subnet_id', 'status', + 'share_network_subnet_ids', 'status', } expected_share_keys = { 'project_id', 'share_type_id', 'display_name', @@ -911,7 +908,7 @@ class ShareDatabaseAPITestCase(test.TestCase): session = db_api.get_session() expected_ss_keys = { 'backend_details', 'host', 'id', - 'share_network_subnet_id', 'status', + 'share_network_subnet_ids', 'status', } expected_share_keys = { 'project_id', 'share_type_id', 'display_name', @@ -999,7 +996,7 @@ class ShareDatabaseAPITestCase(test.TestCase): ) expected_extra_keys = { 'backend_details', 'host', 'id', - 'share_network_subnet_id', 'status', + 'share_network_subnet_ids', 'status', } with session.begin(): share_replica = db_api.share_replica_get( @@ -2866,11 +2863,12 @@ class ShareNetworkSubnetDatabaseAPITestCase(BaseDatabaseAPITestCase): {'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) + share_net_subnets = [ + 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'] + share_server['share_network_subnets'] = share_net_subnets db_api.share_server_create(self.fake_context, share_server) result = db_api.share_network_subnet_get(self.fake_context, @@ -2880,8 +2878,11 @@ class ShareNetworkSubnetDatabaseAPITestCase(BaseDatabaseAPITestCase): len(result['share_servers'])) for index, share_server in enumerate(share_servers): - self._check_fields(expected=share_server, - actual=result['share_servers'][index]) + result = db_api.share_network_subnet_get_all_by_share_server_id( + self.fake_context, share_server['id']) + for key, value in share_server['share_network_subnets'][0].items(): + if key != 'share_servers': + self.assertEqual(value, result[0][key]) def test_get_not_found(self): db_api.share_network_subnet_create(self.fake_context, self.subnet_dict) @@ -2967,18 +2968,43 @@ class ShareNetworkSubnetDatabaseAPITestCase(BaseDatabaseAPITestCase): 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( + result = db_api.share_network_subnets_get_all_by_availability_zone_id( self.fake_context, self.subnet_dict['share_network_id'], az['id']) - self._check_fields(expected=self.subnet_dict, actual=result) + self._check_fields(expected=self.subnet_dict, actual=result[0]) + + def test_get_az_subnets(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_all_with_same_az( + self.fake_context, self.subnet_dict['id']) + + self.subnet_dict['share_network'] = None + + self._check_fields(expected=self.subnet_dict, actual=result[0]) + + def test_get_az_subnets_not_found(self): + self.assertRaises( + exception.ShareNetworkSubnetNotFound, + db_api.share_network_subnet_get_all_with_same_az, + self.fake_context, 'share_network_subnet_id') 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( + result = db_api.share_network_subnet_get_default_subnets( self.fake_context, self.subnet_dict['share_network_id']) - self._check_fields(expected=self.subnet_dict, actual=result) + self._check_fields(expected=self.subnet_dict, actual=result[0]) + + def test_get_by_share_server_id_not_found(self): + self.assertRaises( + exception.ShareNetworkSubnetNotFoundByShareServer, + db_api.share_network_subnet_get_all_by_share_server_id, + self.fake_context, 'share_server_id') @ddt.ddt @@ -3144,13 +3170,21 @@ class ShareServerDatabaseAPITestCase(test.TestCase): self.ctxt = context.RequestContext(user_id='user_id', project_id='project_id', is_admin=True) + self.share_net_subnets = [ + db_utils.create_share_network_subnet( + id=uuidutils.generate_uuid(), + share_network_id=uuidutils.generate_uuid())] def test_share_server_get(self): - expected = db_utils.create_share_server() + expected = db_utils.create_share_server( + share_network_subnets=self.share_net_subnets) server = db_api.share_server_get(self.ctxt, expected['id']) self.assertEqual(expected['id'], server['id']) - self.assertEqual(expected.share_network_subnet_id, - server.share_network_subnet_id) + self.assertEqual(expected.share_network_subnets[0]['id'], + server.share_network_subnets[0]['id']) + self.assertEqual( + expected.share_network_subnets[0]['share_network_id'], + server.share_network_subnets[0]['share_network_id']) self.assertEqual(expected.host, server.host) self.assertEqual(expected.status, server.status) @@ -3160,10 +3194,14 @@ class ShareServerDatabaseAPITestCase(test.TestCase): db_api.share_server_get, self.ctxt, fake_id) def test_create(self): - server = db_utils.create_share_server() + server = db_utils.create_share_server( + share_network_subnets=self.share_net_subnets) self.assertTrue(server['id']) - self.assertEqual(server.share_network_subnet_id, - server['share_network_subnet_id']) + self.assertEqual(server.share_network_subnets[0]['id'], + server['share_network_subnets'][0]['id']) + self.assertEqual( + server.share_network_subnets[0]['share_network_id'], + server['share_network_subnets'][0]['share_network_id']) self.assertEqual(server.host, server['host']) self.assertEqual(server.status, server['status']) @@ -3181,17 +3219,23 @@ class ShareServerDatabaseAPITestCase(test.TestCase): self.ctxt, fake_id) def test_update(self): + share_net_subnets_update = [ + db_utils.create_share_network_subnet( + id=uuidutils.generate_uuid(), + share_network_id=uuidutils.generate_uuid())] update = { - 'share_network_id': 'update_net', + 'share_network_subnets': share_net_subnets_update, 'host': 'update_host', 'status': constants.STATUS_ACTIVE, } - server = db_utils.create_share_server() + server = db_utils.create_share_server( + share_network_subnets=self.share_net_subnets) updated_server = db_api.share_server_update(self.ctxt, server['id'], update) self.assertEqual(server['id'], updated_server['id']) - self.assertEqual(update['share_network_id'], - updated_server.share_network_id) + self.assertEqual( + update['share_network_subnets'][0]['share_network_id'], + updated_server.share_network_subnets[0]['share_network_id']) self.assertEqual(update['host'], updated_server.host) self.assertEqual(update['status'], updated_server.status) @@ -3201,7 +3245,8 @@ class ShareServerDatabaseAPITestCase(test.TestCase): db_api.share_server_update, self.ctxt, fake_id, {}) - def test_get_all_by_host_and_share_net_valid(self): + @ddt.data(None, constants.STATUS_SERVER_NETWORK_CHANGE) + def test_get_all_by_host_and_share_net_valid(self, server_status): subnet_1 = { 'id': '1', 'share_network_id': '1', @@ -3210,31 +3255,41 @@ class ShareServerDatabaseAPITestCase(test.TestCase): 'id': '2', 'share_network_id': '2', } - valid = { - 'share_network_subnet_id': '1', + share_net_subnets1 = db_utils.create_share_network_subnet(**subnet_1) + share_net_subnets2 = db_utils.create_share_network_subnet(**subnet_2) + valid_no_status = { + 'share_network_subnets': [share_net_subnets1], 'host': 'host1', 'status': constants.STATUS_ACTIVE, } + valid_with_status = { + 'share_network_subnets': [share_net_subnets1], + 'host': 'host1', + 'status': constants.STATUS_SERVER_NETWORK_CHANGE, + } invalid = { - 'share_network_subnet_id': '2', + 'share_network_subnets': [share_net_subnets2], 'host': 'host1', 'status': constants.STATUS_ERROR, } other = { - 'share_network_subnet_id': '1', + 'share_network_subnets': [share_net_subnets1], '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) + if server_status: + valid = db_utils.create_share_server(**valid_with_status) + else: + valid = db_utils.create_share_server(**valid_no_status) db_utils.create_share_server(**invalid) db_utils.create_share_server(**other) servers = db_api.share_server_get_all_by_host_and_share_subnet_valid( self.ctxt, host='host1', - share_subnet_id='1') + share_subnet_id='1', + server_status=server_status) + self.assertEqual(valid['id'], servers[0]['id']) def test_get_all_by_host_and_share_net_not_found(self): @@ -3246,17 +3301,14 @@ class ShareServerDatabaseAPITestCase(test.TestCase): def test_get_all(self): srv1 = { - 'share_network_id': '1', 'host': 'host1', 'status': constants.STATUS_ACTIVE, } srv2 = { - 'share_network_id': '1', 'host': 'host1', 'status': constants.STATUS_ERROR, } srv3 = { - 'share_network_id': '2', 'host': 'host2', 'status': constants.STATUS_ACTIVE, } @@ -3296,7 +3348,10 @@ class ShareServerDatabaseAPITestCase(test.TestCase): def test_get_with_details(self): values = { - 'share_network_subnet_id': 'fake-share-net-id', + 'share_network_subnets': [ + db_utils.create_share_network_subnet( + id='fake_subnet_id', + share_network_id='fake_share_net_id')], 'host': 'hostname', 'status': constants.STATUS_ACTIVE, } @@ -3308,8 +3363,11 @@ 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_subnet_id'], - server.share_network_subnet_id) + self.assertEqual(values['share_network_subnets'][0]['id'], + server.share_network_subnets[0]['id']) + self.assertEqual( + values['share_network_subnets'][0]['share_network_id'], + server.share_network_subnets[0]['share_network_id']) self.assertEqual(values['host'], server.host) self.assertEqual(values['status'], server.status) self.assertDictEqual(server['backend_details'], details) @@ -3331,7 +3389,6 @@ class ShareServerDatabaseAPITestCase(test.TestCase): def test_share_server_search_by_identifier(self, identifier): server = { - 'share_network_id': 'fake-share-net-id', 'host': 'hostname', 'status': constants.STATUS_ACTIVE, 'is_auto_deletable': True, @@ -3360,21 +3417,18 @@ class ShareServerDatabaseAPITestCase(test.TestCase): server_3_is_auto_deletable, expected_len): server1 = { - 'share_network_id': 'fake-share-net-id', 'host': 'hostname', 'status': constants.STATUS_ACTIVE, 'is_auto_deletable': server_1_is_auto_deletable, 'updated_at': datetime.datetime(2018, 5, 1) } server2 = { - 'share_network_id': 'fake-share-net-id', 'host': 'hostname', 'status': constants.STATUS_ACTIVE, 'is_auto_deletable': server_2_is_auto_deletable, 'updated_at': datetime.datetime(2018, 5, 1) } server3 = { - 'share_network_id': 'fake-share-net-id', 'host': 'hostname', 'status': constants.STATUS_ACTIVE, 'is_auto_deletable': server_3_is_auto_deletable, @@ -3403,7 +3457,7 @@ class ShareServerDatabaseAPITestCase(test.TestCase): share_network_subnet = db_utils.create_share_network_subnet( id=uuidutils.generate_uuid(), share_network_id=share_network_id) - server_data['share_network_subnet_id'] = share_network_subnet['id'] + server_data['share_network_subnets'] = [share_network_subnet] db_utils.create_share_server(**server_data) db_utils.create_share_server() filter_keys = filters.keys() @@ -3417,7 +3471,7 @@ class ShareServerDatabaseAPITestCase(test.TestCase): self.assertEqual(share_network_subnet['share_network_id'], filters[key]) self.assertEqual(share_network_subnet['id'], - result['share_network_subnet_id']) + result['share_network_subnets'][0]['id']) else: self.assertEqual(result[key], filters[key]) @@ -3534,27 +3588,32 @@ class NetworkAllocationsDatabaseAPITestCase(test.TestCase): self.user_id = 'user_id' self.project_id = 'project_id' self.share_server_id = 'foo_share_server_id' + self.share_network_subnet_id = 'foo_share_network_subnet_id' self.ctxt = context.RequestContext( user_id=self.user_id, project_id=self.project_id, is_admin=True) self.user_network_allocations = [ {'share_server_id': self.share_server_id, 'ip_address': '1.1.1.1', 'status': constants.STATUS_ACTIVE, - 'label': None}, + 'label': None, + 'share_network_subnet_id': self.share_network_subnet_id}, {'share_server_id': self.share_server_id, 'ip_address': '2.2.2.2', 'status': constants.STATUS_ACTIVE, - 'label': 'user'}, + 'label': 'user', + 'share_network_subnet_id': self.share_network_subnet_id}, ] self.admin_network_allocations = [ {'share_server_id': self.share_server_id, 'ip_address': '3.3.3.3', 'status': constants.STATUS_ACTIVE, - 'label': 'admin'}, + 'label': 'admin', + 'share_network_subnet_id': None}, {'share_server_id': self.share_server_id, 'ip_address': '4.4.4.4', 'status': constants.STATUS_ACTIVE, - 'label': 'admin'}, + 'label': 'admin', + 'share_network_subnet_id': None}, ] def _setup_network_allocations_get_for_share_server(self): @@ -3566,10 +3625,17 @@ class NetworkAllocationsDatabaseAPITestCase(test.TestCase): } db_api.share_network_create(self.ctxt, share_network_data) + # Create share network subnet + share_network_subnet_data = { + 'id': self.share_network_subnet_id, + 'share_network_id': self.user_id, + } + db_api.share_network_subnet_create(self.ctxt, + share_network_subnet_data) + # Create share server share_server_data = { 'id': self.share_server_id, - 'share_network_id': share_network_data['id'], 'host': 'fake_host', 'status': 'active', } @@ -3644,6 +3710,20 @@ class NetworkAllocationsDatabaseAPITestCase(test.TestCase): self.ctxt, id='fake') + def test_network_allocation_get_by_subnet_id(self): + self._setup_network_allocations_get_for_share_server() + + result = db_api.network_allocations_get_for_share_server( + self.ctxt, self.share_server_id, + subnet_id=self.share_network_subnet_id) + + self.assertEqual(2, len(result)) + + for network_allocation in result: + self.assertIsInstance(network_allocation, models.NetworkAllocation) + self.assertEqual(self.share_network_subnet_id, + network_allocation.share_network_subnet_id) + @ddt.data(True, False) def test_network_allocation_get_read_deleted(self, read_deleted): self._setup_network_allocations_get_for_share_server() @@ -3798,8 +3878,7 @@ class PurgeDeletedTest(test.TestCase): # create share server db_utils.create_share_server( id=uuidutils.generate_uuid(), - deleted_at=self._days_ago(start, end), - share_network_id=network.id) + deleted_at=self._days_ago(start, end)) # create snapshot db_api.share_snapshot_create( self.context, {'share_id': share['id'], @@ -4360,6 +4439,9 @@ class ShareResourcesAPITestCase(test.TestCase): share_id = uuidutils.generate_uuid() share_network_id = uuidutils.generate_uuid() share_network_subnet_id = uuidutils.generate_uuid() + share_net_subnets = [db_utils.create_share_network_subnet( + id=share_network_subnet_id, + share_network_id=share_network_id)] if '@' in current_host: if '#' in current_host: new_host = 'new-controller-X@backendX#poolX' @@ -4400,15 +4482,15 @@ class ShareResourcesAPITestCase(test.TestCase): status=constants.STATUS_DELETING), # share servers db_utils.create_share_server( - share_network_subnet_id=share_network_subnet_id, + share_network_subnets=share_net_subnets, host='controller-0@fancystore01', status=constants.STATUS_ACTIVE), db_utils.create_share_server( - share_network_subnet_id=share_network_subnet_id, + share_network_subnets=share_net_subnets, host='controller-0@otherstore02#pool100', status=constants.STATUS_ERROR), db_utils.create_share_server( - share_network_subnet_id=share_network_subnet_id, + share_network_subnets=share_net_subnets, host='controller-2@beststore07', status=constants.STATUS_DELETING), @@ -4425,8 +4507,9 @@ class ShareResourcesAPITestCase(test.TestCase): self.context, filters={'share_id': share_id}) share_groups = db_api.share_group_get_all( self.context, filters={'share_network_id': share_network_id}) - share_servers = db_api._server_get_query(self.context).filter_by( - share_network_subnet_id=share_network_subnet_id).all() + share_servers = db_api._server_get_query(self.context).filter( + models.ShareServer.share_network_subnets.any( + id=share_net_subnets[0]['id'])).all() self.assertEqual(3, len(share_instances)) self.assertEqual(3, len(share_groups)) self.assertEqual(3, len(share_servers)) @@ -4450,6 +4533,9 @@ class ShareResourcesAPITestCase(test.TestCase): share_id = uuidutils.generate_uuid() share_network_id = uuidutils.generate_uuid() share_network_subnet_id = uuidutils.generate_uuid() + share_net_subnets = [db_utils.create_share_network_subnet( + id=share_network_subnet_id, + share_network_id=share_network_id)] if '@' in current_host: if '#' in current_host: new_host = 'new-controller-X@backendX#poolX' @@ -4493,15 +4579,15 @@ class ShareResourcesAPITestCase(test.TestCase): status=constants.STATUS_DELETING), # share servers db_utils.create_share_server( - share_network_subnet_id=share_network_subnet_id, + share_network_subnets=share_net_subnets, host='controller-0@fancystore01#pool100', status=constants.STATUS_ACTIVE), db_utils.create_share_server( - share_network_subnet_id=share_network_subnet_id, + share_network_subnets=share_net_subnets, host='controller-2@fancystore01', status=constants.STATUS_ERROR), db_utils.create_share_server( - share_network_subnet_id=share_network_subnet_id, + share_network_subnets=share_net_subnets, host='controller-2@beststore07#pool200', status=constants.STATUS_DELETING), ] @@ -4513,8 +4599,9 @@ class ShareResourcesAPITestCase(test.TestCase): self.context, filters={'share_id': share_id}) share_groups = db_api.share_group_get_all( self.context, filters={'share_network_id': share_network_id}) - share_servers = db_api._server_get_query(self.context).filter_by( - share_network_subnet_id=share_network_subnet_id).all() + share_servers = db_api._server_get_query(self.context).filter( + models.ShareServer.share_network_subnets.any( + id=share_net_subnets[0]['id'])).all() updated_resources = [ res for res in share_instances + share_groups + share_servers diff --git a/manila/tests/db_utils.py b/manila/tests/db_utils.py index c0792f052c..a5429a46cd 100644 --- a/manila/tests/db_utils.py +++ b/manila/tests/db_utils.py @@ -214,7 +214,6 @@ def create_share_server(**kwargs): backend_details = kwargs.pop('backend_details', {}) srv = { 'host': 'host1', - 'share_network_subnet_id': 'fake_srv_id', 'status': constants.STATUS_ACTIVE } share_srv = _create_db_row(db.share_server_create, srv, kwargs) diff --git a/manila/tests/fake_share.py b/manila/tests/fake_share.py index e1140f2773..9255f548a2 100644 --- a/manila/tests/fake_share.py +++ b/manila/tests/fake_share.py @@ -305,7 +305,6 @@ def fake_share_server_get(): 'status': constants.STATUS_ACTIVE, 'updated_at': None, 'host': 'fake_host', - '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 982c83e750..dcb3153489 100644 --- a/manila/tests/network/neutron/test_neutron_plugin.py +++ b/manila/tests/network/neutron/test_neutron_plugin.py @@ -122,6 +122,7 @@ fake_network_allocation = { 'cidr': fake_share_network_subnet['cidr'], 'gateway': fake_share_network_subnet['gateway'], 'mtu': 1509, + 'share_network_subnet_id': fake_share_network_subnet['id'], } fake_nw_info = { @@ -187,6 +188,7 @@ fake_network_allocation_multi = { 'cidr': fake_neutron_subnet['cidr'], 'gateway': fake_neutron_subnet['gateway_ip'], 'mtu': fake_neutron_network_multi['mtu'], + 'share_network_subnet_id': fake_share_network['id'], } fake_binding_profile = { @@ -240,9 +242,11 @@ class NeutronNetworkPluginTest(test.TestCase): has_provider_nw_ext.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) self.plugin.neutron_api.create_port.assert_called_once_with( fake_share_network['project_id'], network_id=fake_share_network_subnet['neutron_net_id'], @@ -401,6 +405,7 @@ class NeutronNetworkPluginTest(test.TestCase): 'ip_version': fake_share_network_subnet['ip_version'], 'cidr': fake_share_network_subnet['cidr'], 'mtu': fake_share_network_subnet['mtu'], + 'share_network_subnet_id': fake_share_network_subnet['id'], } for x in ['192.168.0.11', '192.168.0.12']] if side_effect: @@ -961,9 +966,11 @@ class NeutronBindNetworkPluginTest(test.TestCase): self.bind_plugin._has_provider_network_extension.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) expected_kwargs = { 'binding:vnic_type': 'baremetal', 'host_id': 'foohost1', @@ -1486,9 +1493,11 @@ class NeutronBindSingleNetworkPluginTest(test.TestCase): self.bind_plugin._has_provider_network_extension.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) expected_kwargs = { 'binding:vnic_type': 'baremetal', 'host_id': 'foohost1', @@ -1713,9 +1722,11 @@ class NeutronBindNetworkPluginWithNormalTypeTest(test.TestCase): self.bind_plugin._has_provider_network_extension.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) expected_kwargs = { 'binding:vnic_type': 'normal', 'host_id': 'foohost1', @@ -1800,9 +1811,11 @@ class NeutronBindSingleNetworkPluginWithNormalTypeTest(test.TestCase): self.bind_plugin._has_provider_network_extension.assert_any_call() save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network_subnet) + fake_share_network_subnet, + save_db=True) expected_kwargs = { 'binding:vnic_type': 'normal', 'host_id': 'foohost1', @@ -1866,3 +1879,23 @@ class NeutronBindSingleNetworkPluginWithNormalTypeTest(test.TestCase): self.assertRaises(exception.NetworkBadConfigurationException, self.bind_plugin._get_matched_ip_address, fix_ips, version) + + def _setup_include_network_info(self): + data = { + 'DEFAULT': { + 'neutron_net_id': 'fake net id', + 'neutron_subnet_id': 'fake subnet id', + 'neutron_physical_net_name': 'net1', + } + } + with test_utils.create_temp_config_with_opts(data): + instance = plugin.NeutronNetworkPlugin() + + return instance + + def test_include_network_info(self): + instance = self._setup_include_network_info() + self.mock_object(instance, '_store_neutron_net_info') + instance.include_network_info(fake_share_network) + instance._store_neutron_net_info.assert_called_once_with( + None, fake_share_network, save_db=False) diff --git a/manila/tests/network/test_standalone_network_plugin.py b/manila/tests/network/test_standalone_network_plugin.py index ad07aee2b9..50539a4422 100644 --- a/manila/tests/network/test_standalone_network_plugin.py +++ b/manila/tests/network/test_standalone_network_plugin.py @@ -346,7 +346,8 @@ class StandaloneNetworkPluginTest(test.TestCase): ip_version=6, mtu=1500)) - def test_allocate_network_one_ip_address_ipv4_no_usages_exist(self): + @ddt.data('admin', 'user') + def test_allocate_network_one_ip_address_ipv4_no_usages_exist(self, label): data = { 'DEFAULT': { 'standalone_network_plugin_network_type': 'vlan', @@ -356,8 +357,9 @@ class StandaloneNetworkPluginTest(test.TestCase): }, } with test_utils.create_temp_config_with_opts(data): - instance = plugin.StandaloneNetworkPlugin() - self.mock_object(instance.db, 'share_network_subnet_update') + instance = plugin.StandaloneNetworkPlugin(label=label) + if label != 'admin': + 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', @@ -376,15 +378,18 @@ class StandaloneNetworkPluginTest(test.TestCase): 'ip_version': 4, 'mtu': 1500, } - instance.db.share_network_subnet_update.assert_called_once_with( - fake_context, fake_share_network_subnet['id'], na_data) + if label != 'admin': + instance.db.share_network_subnet_update.assert_called_once_with( + fake_context, fake_share_network_subnet['id'], na_data) + na_data['share_network_subnet_id'] = \ + fake_share_network_subnet['id'] 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( fake_context, dict(share_server_id=fake_share_server['id'], ip_address='10.0.0.2', status=constants.STATUS_ACTIVE, - label='user', **na_data)) + label=label, **na_data)) def test_allocate_network_two_ip_addresses_ipv4_two_usages_exist(self): ctxt = type('FakeCtxt', (object,), {'fake': ['10.0.0.2', '10.0.0.4']}) @@ -428,6 +433,7 @@ class StandaloneNetworkPluginTest(test.TestCase): 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')]) + na_data['share_network_subnet_id'] = fake_share_network_subnet['id'] instance.db.network_allocation_create.assert_has_calls([ mock.call( ctxt, @@ -522,6 +528,10 @@ class StandaloneNetworkPluginTest(test.TestCase): if not label: instance.db.share_network_subnet_update.assert_called_once_with( fake_context, fake_share_network_subnet['id'], network_data) + data_list[0]['share_network_subnet_id'] = ( + fake_share_network_subnet['id']) + data_list[1]['share_network_subnet_id'] = ( + fake_share_network_subnet['id']) instance._verify_share_network_subnet.assert_called_once_with( fake_share_server['id'], fake_share_network_subnet) @@ -536,3 +546,22 @@ class StandaloneNetworkPluginTest(test.TestCase): instance.unmanage_network_allocations('context', 'server_id') instance.deallocate_network.assert_called_once_with( 'context', 'server_id') + + def _setup_include_network_info(self): + data = { + 'DEFAULT': { + 'standalone_network_plugin_gateway': '192.168.0.1', + 'standalone_network_plugin_mask': '24', + }, + } + with test_utils.create_temp_config_with_opts(data): + instance = plugin.StandaloneNetworkPlugin() + + return instance + + def test_include_network_info(self): + instance = self._setup_include_network_info() + self.mock_object(instance, '_save_network_info') + instance.include_network_info(fake_share_network) + instance._save_network_info.assert_called_once_with( + None, fake_share_network, save_db=False) diff --git a/manila/tests/scheduler/filters/test_availability_zone.py b/manila/tests/scheduler/filters/test_availability_zone.py index b9cd0d5f29..c74f38181a 100644 --- a/manila/tests/scheduler/filters/test_availability_zone.py +++ b/manila/tests/scheduler/filters/test_availability_zone.py @@ -101,6 +101,8 @@ class HostFiltersTestCase(test.TestCase): } request = self._make_zone_request(None) request['request_spec']['availability_zones'] = supported_azs + request['request_spec']['az_request_multiple_subnet_support_map'] = \ + {'zone2': 2} host = fakes.FakeHostState('host1', {'service': service}) self.assertEqual(host_passes, self.filter.host_passes(host, request)) diff --git a/manila/tests/scheduler/test_host_manager.py b/manila/tests/scheduler/test_host_manager.py index 7f45121555..6db925bb53 100644 --- a/manila/tests/scheduler/test_host_manager.py +++ b/manila/tests/scheduler/test_host_manager.py @@ -214,6 +214,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, { 'name': 'host2@back1#BBB', @@ -244,6 +246,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, { 'name': 'host2@back2#CCC', @@ -274,6 +278,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, ] @@ -326,6 +332,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, { 'name': 'host2@BBB#pool2', @@ -357,6 +365,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, { 'name': 'host3@CCC#pool3', @@ -388,6 +398,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, { 'name': 'host4@DDD#pool4a', @@ -419,6 +431,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, { 'name': 'host4@DDD#pool4b', @@ -450,6 +464,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, ] @@ -514,6 +530,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, { 'name': 'host2@back1#BBB', @@ -544,6 +562,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, ] @@ -602,6 +622,8 @@ class HostManagerTestCase(test.TestCase): 'replication_domain': None, 'sg_consistent_snapshot_support': None, 'security_service_update_support': False, + 'network_allocation_update_support': False, + 'share_server_multiple_subnet_support': False, }, }, ] diff --git a/manila/tests/share/drivers/container/test_driver.py b/manila/tests/share/drivers/container/test_driver.py index e151fdad44..2a3bd78f4c 100644 --- a/manila/tests/share/drivers/container/test_driver.py +++ b/manila/tests/share/drivers/container/test_driver.py @@ -372,7 +372,7 @@ class ContainerShareDriverTestCase(test.TestCase): before, after) def test__setup_server_container_fails(self): - network_info = cont_fakes.fake_network() + network_info = [cont_fakes.fake_network()] self.mock_object(self._driver.container, 'start_container') self._driver.container.start_container.side_effect = KeyError() @@ -380,21 +380,21 @@ class ContainerShareDriverTestCase(test.TestCase): self._driver._setup_server, network_info) def test__setup_server_ok(self): - network_info = cont_fakes.fake_network() - server_id = self._driver._get_container_name(network_info["server_id"]) + network_info = [cont_fakes.fake_network()] + server_id = self._driver._get_container_name( + network_info[0]["server_id"]) self.mock_object(self._driver.container, 'start_container') self.mock_object(self._driver, '_get_veth_state') self.mock_object(self._driver, '_get_corresponding_veth', mock.Mock(return_value='veth0')) self.mock_object(self._driver, '_connect_to_network') - self.assertEqual(network_info['server_id'], + self.assertEqual(network_info[0]['server_id'], self._driver._setup_server(network_info)['id']) self._driver.container.start_container.assert_called_once_with( server_id) - self._driver._connect_to_network.assert_called_once_with(server_id, - network_info, - 'veth0') + self._driver._connect_to_network.assert_called_once_with( + server_id, network_info[0], 'veth0') def test_manage_existing(self): diff --git a/manila/tests/share/drivers/dell_emc/test_driver.py b/manila/tests/share/drivers/dell_emc/test_driver.py index abdb05e83c..da857fe6da 100644 --- a/manila/tests/share/drivers/dell_emc/test_driver.py +++ b/manila/tests/share/drivers/dell_emc/test_driver.py @@ -145,6 +145,8 @@ class EMCShareFrameworkTestCase(test.TestCase): data['max_shares_per_share_server'] = -1 data['max_share_server_size'] = -1 data['security_service_update_support'] = False + data['share_server_multiple_subnet_support'] = False + data['network_allocation_update_support'] = False self.assertEqual(data, self.driver._stats) def _fake_safe_get(self, value): @@ -214,3 +216,9 @@ class EMCShareFrameworkTestCase(test.TestCase): expected = None actual = self.driver.get_default_filter_function() self.assertEqual(expected, actual) + + def test_setup_server(self): + network_info = [{}] + expected = None + result = self.driver._setup_server(network_info) + self.assertEqual(expected, result) diff --git a/manila/tests/share/drivers/dummy.py b/manila/tests/share/drivers/dummy.py index e2afb616c9..e517e2754e 100644 --- a/manila/tests/share/drivers/dummy.py +++ b/manila/tests/share/drivers/dummy.py @@ -34,6 +34,7 @@ import time from oslo_config import cfg from oslo_log import log +from oslo_serialization import jsonutils from oslo_utils import timeutils from manila.common import constants @@ -141,6 +142,7 @@ class DummyDriver(driver.ShareDriver): "share_backend_name") or "DummyDriver" self.migration_progress = {} self.security_service_update_support = True + self.network_allocation_update_support = True def _verify_configuration(self): allowed_driver_methods = [m for m in dir(self) if m[0] != '_'] @@ -184,24 +186,40 @@ class DummyDriver(driver.ShareDriver): "s_id": snapshot["snapshot_id"].replace("-", "_"), "si_id": snapshot["id"].replace("-", "_")} - def _generate_export_locations(self, mountpoint, share_server=None): - details = share_server["backend_details"] if share_server else { - "primary_public_ip": "10.0.0.10", - "secondary_public_ip": "10.0.0.20", - "service_ip": "11.0.0.11", + def _get_export(self, mountpoint, ip, is_admin_only, preferred): + return { + "path": "%(ip)s:%(mp)s" % {"ip": ip, "mp": mountpoint}, + "metadata": { + "preferred": preferred, + }, + "is_admin_only": is_admin_only, } - return [ - { - "path": "%(ip)s:%(mp)s" % {"ip": ip, "mp": mountpoint}, - "metadata": { - "preferred": preferred, - }, - "is_admin_only": is_admin_only, - } for ip, is_admin_only, preferred in ( - (details["primary_public_ip"], False, True), - (details["secondary_public_ip"], False, False), - (details["service_ip"], True, False)) - ] + + def _generate_export_locations(self, mountpoint, share_server=None): + if share_server: + subnet_allocations = jsonutils.loads( + share_server["backend_details"]["subnet_allocations"]) + service_ip = share_server["backend_details"]["service_ip"] + else: + subnet_allocations = [{ + "primary_public_ip": "10.0.0.10", + "secondary_public_ip": "10.0.0.20", + }] + service_ip = "11.0.0.11" + + export_locations = [ + self._get_export(mountpoint, service_ip, True, False)] + for subnet_allocation in subnet_allocations: + export_locations.append( + self._get_export( + mountpoint, subnet_allocation["primary_public_ip"], + False, True)) + export_locations.append( + self._get_export( + mountpoint, subnet_allocation["secondary_public_ip"], + False, False)) + + return export_locations def _create_share(self, share, share_server=None): share_proto = share["share_proto"] @@ -410,16 +428,25 @@ class DummyDriver(driver.ShareDriver): Redefine it within share driver when it is going to handle share servers. """ + common_net_info = network_info[0] server_details = { - "primary_public_ip": network_info[ - "network_allocations"][0]["ip_address"], - "secondary_public_ip": network_info[ - "network_allocations"][1]["ip_address"], - "service_ip": network_info[ + "service_ip": common_net_info[ "admin_network_allocations"][0]["ip_address"], "username": "fake_username", - "server_id": network_info['server_id'] + "server_id": common_net_info['server_id'], } + + subnet_allocations = [] + for subnet_info in network_info: + subnet_allocations.append({ + "primary_public_ip": subnet_info[ + "network_allocations"][0]["ip_address"], + "secondary_public_ip": subnet_info[ + "network_allocations"][1]["ip_address"] + }) + + server_details['subnet_allocations'] = jsonutils.dumps( + subnet_allocations) return server_details @slow_me_down @@ -460,6 +487,7 @@ class DummyDriver(driver.ShareDriver): "share_group_stats": { "consistent_snapshot_support": "pool", }, + 'share_server_multiple_subnet_support': True, } if self.configuration.replication_domain: data["replication_type"] = "readable" @@ -828,9 +856,12 @@ class DummyDriver(driver.ShareDriver): "private storage." % identifier) raise exception.ShareBackendException(msg=msg) - return [server_details['primary_public_ip'], - server_details['secondary_public_ip'], - server_details['service_ip']] + ips = [server_details['service_ip']] + subnet_allocations = jsonutils.loads( + server_details['subnet_allocations']) + for subnet_allocation in subnet_allocations: + ips += list(subnet_allocation.values()) + return ips @slow_me_down def manage_server(self, context, share_server, identifier, driver_options): @@ -889,3 +920,65 @@ class DummyDriver(driver.ShareDriver): share_instance_rules, new_security_service, current_security_service=None): return True + + def check_update_share_server_network_allocations( + self, context, share_server, current_network_allocations, + new_share_network_subnet, security_services, share_instances, + share_instances_rules): + + LOG.debug("Share server %(server)s can be updated with allocations " + "from new subnet.", {'server': share_server['id']}) + return True + + def update_share_server_network_allocations( + self, context, share_server, current_network_allocations, + new_network_allocations, security_services, shares, snapshots): + + subnet_allocations = jsonutils.loads( + share_server['backend_details']['subnet_allocations']) + subnet_allocations.append({ + 'primary_public_ip': new_network_allocations[ + 'network_allocations'][0]['ip_address'], + 'secondary_public_ip': new_network_allocations[ + 'network_allocations'][1]['ip_address'], + }) + new_server = { + "backend_details": { + "subnet_allocations": jsonutils.dumps(subnet_allocations), + "service_ip": share_server["backend_details"]["service_ip"], + } + } + shares_updates = {} + for instance in shares: + + share_name = self._get_share_name(instance) + mountpoint = "/path/to/fake/share/%s" % share_name + export_locations = self._generate_export_locations( + mountpoint, share_server=new_server) + shares_updates.update( + {instance['id']: export_locations} + ) + + snapshot_updates = {} + for instance in snapshots: + snapshot_name = self._get_snapshot_name(instance) + mountpoint = "/path/to/fake/snapshot/%s" % snapshot_name + snap_export_locations = self._generate_export_locations( + mountpoint, share_server=new_server) + snapshot_updates.update( + {instance['id']: { + 'provider_location': mountpoint, + 'export_locations': snap_export_locations}} + ) + + LOG.debug( + "Network update allocations of dummy share server with ID '%s' " + "has been completed.", share_server["id"]) + return { + "share_updates": shares_updates, + "snapshot_updates": snapshot_updates, + "server_details": { + "subnet_allocations": ( + new_server["backend_details"]["subnet_allocations"]) + }, + } diff --git a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py index cf885960b2..2853d2058e 100644 --- a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py +++ b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py @@ -270,6 +270,8 @@ class GlusterfsNativeShareDriverTestCase(test.TestCase): 'ipv4_support': True, 'ipv6_support': False, 'security_service_update_support': False, + 'share_server_multiple_subnet_support': False, + 'network_allocation_update_support': False, } self.assertEqual(test_data, self._driver._stats) diff --git a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py index 1cd7b45291..642990a67c 100644 --- a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py +++ b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py @@ -750,6 +750,8 @@ class HPE3ParDriverTestCase(test.TestCase): 'max_share_server_size': -1, 'max_shares_per_share_server': -1, 'security_service_update_support': False, + 'share_server_multiple_subnet_support': False, + 'network_allocation_update_support': False, } result = self.driver.get_share_stats(refresh=True) @@ -824,6 +826,8 @@ class HPE3ParDriverTestCase(test.TestCase): 'create_share_from_snapshot_support': True, 'revert_to_snapshot_support': False, 'security_service_update_support': False, + 'share_server_multiple_subnet_support': False, + 'network_allocation_update_support': False, 'mount_snapshot_support': False, 'share_group_stats': { 'consistent_snapshot_support': None, @@ -872,6 +876,8 @@ class HPE3ParDriverTestCase(test.TestCase): 'create_share_from_snapshot_support': True, 'revert_to_snapshot_support': False, 'security_service_update_support': False, + 'share_server_multiple_subnet_support': False, + 'network_allocation_update_support': False, 'mount_snapshot_support': False, 'share_group_stats': { 'consistent_snapshot_support': None, @@ -926,7 +932,7 @@ class HPE3ParDriverTestCase(test.TestCase): self.init_driver() - network_info = { + network_info = [{ 'network_allocations': [ {'ip_address': constants.EXPECTED_IP_1234}], 'cidr': '/'.join((constants.EXPECTED_IP_1234, @@ -934,7 +940,7 @@ class HPE3ParDriverTestCase(test.TestCase): 'network_type': constants.EXPECTED_VLAN_TYPE, 'segmentation_id': constants.EXPECTED_VLAN_TAG, 'server_id': constants.EXPECTED_SERVER_ID, - } + }] expected_result = { 'share_server_name': constants.EXPECTED_SERVER_ID, @@ -964,7 +970,7 @@ class HPE3ParDriverTestCase(test.TestCase): self.init_driver() - network_info = { + network_info = [{ 'network_allocations': [ {'ip_address': constants.EXPECTED_IP_1234}], 'cidr': '/'.join((constants.EXPECTED_IP_1234, @@ -972,7 +978,7 @@ class HPE3ParDriverTestCase(test.TestCase): 'network_type': constants.EXPECTED_VXLAN_TYPE, 'segmentation_id': constants.EXPECTED_VLAN_TAG, 'server_id': constants.EXPECTED_SERVER_ID, - } + }] metadata = {'request_host': constants.EXPECTED_HOST} self.assertRaises(exception.NetworkBadConfigurationException, @@ -984,7 +990,7 @@ class HPE3ParDriverTestCase(test.TestCase): self.init_driver() - network_info = { + network_info = [{ 'network_allocations': [ {'ip_address': constants.EXPECTED_IP_1234}], 'cidr': '/'.join((constants.EXPECTED_IP_1234, @@ -992,7 +998,7 @@ class HPE3ParDriverTestCase(test.TestCase): 'network_type': constants.EXPECTED_VLAN_TYPE, 'segmentation_id': constants.EXPECTED_VLAN_TAG, 'server_id': constants.EXPECTED_SERVER_ID, - } + }] metadata = {'request_host': constants.EXPECTED_HOST} expected_vfs = self.driver.fpgs[ diff --git a/manila/tests/share/drivers/huawei/test_huawei_nas.py b/manila/tests/share/drivers/huawei/test_huawei_nas.py index 22071f8d34..5f6a85b393 100644 --- a/manila/tests/share/drivers/huawei/test_huawei_nas.py +++ b/manila/tests/share/drivers/huawei/test_huawei_nas.py @@ -1181,7 +1181,7 @@ class HuaweiShareDriverTestCase(test.TestCase): 'id': 'fake_network_allocation_id', 'ip_address': '111.111.111.109', }] - self.fake_network_info = { + self.fake_network_info = [{ 'server_id': '0', 'segmentation_id': '2', 'cidr': '111.111.111.0/24', @@ -1190,7 +1190,7 @@ class HuaweiShareDriverTestCase(test.TestCase): 'security_services': '', 'network_allocations': self.fake_network_allocations, 'network_type': 'vlan', - } + }] self.fake_active_directory = { 'type': 'active_directory', 'dns_ip': '100.97.5.5', @@ -2435,6 +2435,8 @@ class HuaweiShareDriverTestCase(test.TestCase): "ipv4_support": True, "ipv6_support": False, "security_service_update_support": False, + "share_server_multiple_subnet_support": False, + "network_allocation_update_support": False, } if replication_support: @@ -3323,7 +3325,7 @@ class HuaweiShareDriverTestCase(test.TestCase): def test_setup_server_invalid_ipv4(self): netwot_info_invali_ipv4 = self.fake_network_info - netwot_info_invali_ipv4['network_allocations'][0]['ip_address'] = ( + netwot_info_invali_ipv4[0]['network_allocations'][0]['ip_address'] = ( "::1/128") self.assertRaises(exception.InvalidInput, self.driver._setup_server, @@ -3332,7 +3334,7 @@ class HuaweiShareDriverTestCase(test.TestCase): @dec_driver_handles_share_servers def test_setup_server_network_type_error(self): vxlan_netwotk_info = self.fake_network_info - vxlan_netwotk_info['network_type'] = 'vxlan' + vxlan_netwotk_info[0]['network_type'] = 'vxlan' self.assertRaises(exception.NetworkBadConfigurationException, self.driver.setup_server, vxlan_netwotk_info) @@ -3412,13 +3414,13 @@ class HuaweiShareDriverTestCase(test.TestCase): logical_port='CTE0.A.H0;CTE0.A.H2;CTE0.B.H0;BOND0') self.driver.plugin.configuration.manila_huawei_conf_file = ( self.fake_conf_file) - fake_network_info = { + fake_network_info = [{ 'server_id': '0', 'segmentation_id': None, 'cidr': '111.111.111.0/24', 'network_allocations': self.fake_network_allocations, 'network_type': None, - } + }] self.mock_object(self.driver.plugin, '_get_online_port', mock.Mock(return_value=(['CTE0.A.H0', 'CTE0.A.H2', 'CTE0.B.H0'], ['BOND0']))) @@ -3476,7 +3478,7 @@ class HuaweiShareDriverTestCase(test.TestCase): return self.driver.plugin.helper.do_call(*args, **kwargs) fake_network_info = self.fake_network_info - fake_network_info['security_services'] = [ + fake_network_info[0]['security_services'] = [ self.fake_active_directory, self.fake_ldap] self.mock_object(self.driver.plugin.helper, "delete_vlan") self.mock_object(self.driver.plugin.helper, "delete_AD_config") @@ -3509,7 +3511,8 @@ class HuaweiShareDriverTestCase(test.TestCase): @dec_driver_handles_share_servers def test_setup_server_with_ad_domain_success(self): fake_network_info = self.fake_network_info - fake_network_info['security_services'] = [self.fake_active_directory] + fake_network_info[0]['security_services'] = ( + [self.fake_active_directory]) self.mock_object(self.driver.plugin.helper, "get_AD_config", mock.Mock( @@ -3531,8 +3534,8 @@ class HuaweiShareDriverTestCase(test.TestCase): @dec_driver_handles_share_servers def test_setup_server_with_ldap_domain_success(self, server_ips): fake_network_info = self.fake_network_info - fake_network_info['security_services'] = [self.fake_ldap] - fake_network_info['security_services'][0]['server'] = server_ips + fake_network_info[0]['security_services'] = [self.fake_ldap] + fake_network_info[0]['security_services'][0]['server'] = server_ips self.mock_object( self.driver.plugin.helper, "get_LDAP_config", @@ -3547,8 +3550,8 @@ class HuaweiShareDriverTestCase(test.TestCase): def test_setup_server_with_ldap_domain_fail(self): server_ips = "100.97.5.87,100.97.5.88,100.97.5.89,100.97.5.86" fake_network_info = self.fake_network_info - fake_network_info['security_services'] = [self.fake_ldap] - fake_network_info['security_services'][0]['server'] = server_ips + fake_network_info[0]['security_services'] = [self.fake_ldap] + fake_network_info[0]['security_services'][0]['server'] = server_ips self.mock_object( self.driver.plugin.helper, "get_LDAP_config", @@ -3573,7 +3576,7 @@ class HuaweiShareDriverTestCase(test.TestCase): @dec_driver_handles_share_servers def test_setup_server_with_security_service_invalid(self, data): fake_network_info = self.fake_network_info - fake_network_info['security_services'] = [data] + fake_network_info[0]['security_services'] = [data] self.assertRaises(exception.InvalidInput, self.driver.setup_server, fake_network_info) @@ -3592,7 +3595,7 @@ class HuaweiShareDriverTestCase(test.TestCase): 'server': '', 'domain': ''}, ] - fake_network_info['security_services'] = ss + fake_network_info[0]['security_services'] = ss self.assertRaises(exception.InvalidInput, self.driver.setup_server, fake_network_info) @@ -3600,7 +3603,8 @@ class HuaweiShareDriverTestCase(test.TestCase): @dec_driver_handles_share_servers def test_setup_server_dns_exist_error(self): fake_network_info = self.fake_network_info - fake_network_info['security_services'] = [self.fake_active_directory] + fake_network_info[0]['security_services'] = ( + [self.fake_active_directory]) self.mock_object(self.driver.plugin.helper, "get_DNS_ip_address", mock.Mock(return_value=['100.97.5.85'])) @@ -3612,7 +3616,8 @@ class HuaweiShareDriverTestCase(test.TestCase): @dec_driver_handles_share_servers def test_setup_server_ad_exist_error(self): fake_network_info = self.fake_network_info - fake_network_info['security_services'] = [self.fake_active_directory] + fake_network_info[0]['security_services'] = ( + [self.fake_active_directory]) self.mock_object(self.driver.plugin.helper, "get_AD_config", mock.Mock( @@ -3626,7 +3631,7 @@ class HuaweiShareDriverTestCase(test.TestCase): @dec_driver_handles_share_servers def test_setup_server_ldap_exist_error(self): fake_network_info = self.fake_network_info - fake_network_info['security_services'] = [self.fake_ldap] + fake_network_info[0]['security_services'] = [self.fake_ldap] self.mock_object(self.driver.plugin.helper, "get_LDAP_config", mock.Mock( @@ -3642,7 +3647,7 @@ class HuaweiShareDriverTestCase(test.TestCase): fake_active_directory = self.fake_active_directory ip_list = "100.97.5.5,100.97.5.6,100.97.5.7,100.97.5.8" fake_active_directory['dns_ip'] = ip_list - fake_network_info['security_services'] = [fake_active_directory] + fake_network_info[0]['security_services'] = [fake_active_directory] self.mock_object( self.driver.plugin.helper, "get_AD_config", @@ -3655,7 +3660,8 @@ class HuaweiShareDriverTestCase(test.TestCase): @dec_driver_handles_share_servers def test_setup_server_with_ad_domain_fail(self): fake_network_info = self.fake_network_info - fake_network_info['security_services'] = [self.fake_active_directory] + fake_network_info[0]['security_services'] = ( + [self.fake_active_directory]) self.mock_object(self.driver.plugin, '_get_wait_interval', mock.Mock(return_value=1)) diff --git a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py index c7b33ec4bc..a484f7f322 100644 --- a/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py +++ b/manila/tests/share/drivers/netapp/dataontap/cluster_mode/test_lib_multi_svm.py @@ -2852,7 +2852,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.fake_src_share_server, self.fake_dest_share_server, share_instances, [], - fake.NETWORK_INFO + [fake.NETWORK_INFO] ) expected_share_updates = { @@ -2932,7 +2932,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.fake_dest_share_server, self.fake_src_vserver, self.mock_src_client, [fake.SHARE_INSTANCE], - fake.NETWORK_INFO) + [fake.NETWORK_INFO]) dm_session_mock.update_snapmirror_svm.assert_called_once_with( self.fake_src_share_server, self.fake_dest_share_server @@ -2977,7 +2977,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.fake_src_share_server, self.fake_dest_share_server, [fake.SHARE_INSTANCE], [], - fake.NETWORK_INFO) + [fake.NETWORK_INFO]) self.library._get_vserver.assert_has_calls([ mock.call(share_server=self.fake_src_share_server, @@ -3513,7 +3513,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): fake_vserver_client, 'modify_active_directory_security_service') self.library.update_share_server_security_service( - fake_context, fake.SHARE_SERVER, fake_net_info, + fake_context, fake.SHARE_SERVER, [fake_net_info], new_sec_service, current_security_service=curr_sec_service) dns_ips = set() @@ -3530,7 +3530,7 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): mock_get_vserver.assert_called_once_with( share_server=fake.SHARE_SERVER) mock_check_update.assert_called_once_with( - fake_context, fake.SHARE_SERVER, fake_net_info, new_sec_service, + fake_context, fake.SHARE_SERVER, [fake_net_info], new_sec_service, current_security_service=curr_sec_service) if curr_sec_service is None: @@ -3565,13 +3565,13 @@ class NetAppFileStorageLibraryTestCase(test.TestCase): self.assertRaises( exception.NetAppException, self.library.update_share_server_security_service, - fake_context, fake.SHARE_SERVER, fake_net_info, + fake_context, fake.SHARE_SERVER, [fake_net_info], new_sec_service, current_security_service=curr_sec_service) mock_get_vserver.assert_called_once_with( share_server=fake.SHARE_SERVER) mock_check_update.assert_called_once_with( - fake_context, fake.SHARE_SERVER, fake_net_info, + fake_context, fake.SHARE_SERVER, [fake_net_info], new_sec_service, current_security_service=curr_sec_service) @ddt.data( diff --git a/manila/tests/share/drivers/test_generic.py b/manila/tests/share/drivers/test_generic.py index f192c644f9..f547401a14 100644 --- a/manila/tests/share/drivers/test_generic.py +++ b/manila/tests/share/drivers/test_generic.py @@ -1182,23 +1182,23 @@ class GenericShareDriverTestCase(test.TestCase): def test__setup_server(self): sim = self._driver.instance_manager - net_info = { + net_info = [{ 'server_id': 'fake', 'neutron_net_id': 'fake-net-id', 'neutron_subnet_id': 'fake-subnet-id', - } + }] self._driver.setup_server(net_info) sim.set_up_service_instance.assert_called_once_with( - self._context, net_info) + self._context, net_info[0]) def test__setup_server_revert(self): def raise_exception(*args, **kwargs): raise exception.ServiceInstanceException - net_info = {'server_id': 'fake', - 'neutron_net_id': 'fake-net-id', - 'neutron_subnet_id': 'fake-subnet-id'} + net_info = [{'server_id': 'fake', + 'neutron_net_id': 'fake-net-id', + 'neutron_subnet_id': 'fake-subnet-id'}] self.mock_object(self._driver.service_instance_manager, 'set_up_service_instance', mock.Mock(side_effect=raise_exception)) diff --git a/manila/tests/share/drivers/veritas/test_veritas_isa.py b/manila/tests/share/drivers/veritas/test_veritas_isa.py index d41ea3593b..cf911acf4b 100644 --- a/manila/tests/share/drivers/veritas/test_veritas_isa.py +++ b/manila/tests/share/drivers/veritas/test_veritas_isa.py @@ -447,6 +447,8 @@ class ACCESSShareDriverTestCase(test.TestCase): 'share_group_stats': {'consistent_snapshot_support': None}, 'snapshot_support': True, 'security_service_update_support': False, + 'share_server_multiple_subnet_support': False, + 'network_allocation_update_support': False, } self.assertEqual(data, self._driver._stats) diff --git a/manila/tests/share/drivers/zfsonlinux/test_driver.py b/manila/tests/share/drivers/zfsonlinux/test_driver.py index 4b9e3b4898..984892de19 100644 --- a/manila/tests/share/drivers/zfsonlinux/test_driver.py +++ b/manila/tests/share/drivers/zfsonlinux/test_driver.py @@ -374,6 +374,8 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase): 'ipv4_support': True, 'ipv6_support': False, 'security_service_update_support': False, + 'share_server_multiple_subnet_support': False, + 'network_allocation_update_support': False, } if replication_domain: expected['replication_type'] = 'readable' diff --git a/manila/tests/share/test_api.py b/manila/tests/share/test_api.py index fc2154f570..7f8e2eb21a 100644 --- a/manila/tests/share/test_api.py +++ b/manila/tests/share/test_api.py @@ -737,17 +737,17 @@ class ShareAPITestCase(test.TestCase): 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 = [] + self.mock_object( + db_api, 'share_network_subnets_get_all_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']) + expected_az_names = ([value['name']], {value['id']: True}) get_all_subnets = self.api._get_all_availability_zones_with_subnets compatible_azs = get_all_subnets(self.context, fake_share_network_id) @@ -755,7 +755,7 @@ class ShareAPITestCase(test.TestCase): 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_api.share_network_subnets_get_all_by_availability_zone_id) db_get_azs_with_subnet.assert_has_calls(expected_get_az_calls) self.assertEqual(expected_az_names, compatible_azs) @@ -779,21 +779,25 @@ class ShareAPITestCase(test.TestCase): share_type=self.sized_sha_type) @ddt.data( - {'availability_zones': None, 'azs_with_subnet': ['fake_az_1']}, + {'availability_zones': None, 'compatible_azs_name': ['fake_az_1'], + 'compatible_azs_multiple': {}}, {'availability_zones': ['fake_az_2'], - 'azs_with_subnet': ['fake_az_2']}, + 'compatible_azs_name': ['fake_az_2'], 'compatible_azs_multiple': {}}, {'availability_zones': ['fake_az_1', 'faze_az_2', 'fake_az_3'], - 'azs_with_subnet': ['fake_az_3']} + 'compatible_azs_name': ['fake_az_3'], + 'compatible_azs_multiple': {'fake_az_3': 1}} ) @ddt.unpack def test_create_share_with_subnets(self, availability_zones, - azs_with_subnet): + compatible_azs_name, + compatible_azs_multiple): 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)) + mock.Mock(return_value=[compatible_azs_name, + compatible_azs_multiple])) self.mock_object(quota.QUOTAS, 'commit') self.mock_object(self.api, 'create_instance') self.mock_object(db_api, 'share_get') @@ -801,9 +805,13 @@ class ShareAPITestCase(test.TestCase): if availability_zones: expected_azs = ( - [az for az in availability_zones if az in azs_with_subnet]) + [az for az in availability_zones if az in compatible_azs_name]) else: - expected_azs = azs_with_subnet + expected_azs = compatible_azs_name + + az_multiple_sn_support_map = None + if compatible_azs_multiple != {}: + az_multiple_sn_support_map = compatible_azs_multiple self.api.create( self.context, @@ -812,7 +820,8 @@ class ShareAPITestCase(test.TestCase): share_data['display_name'], share_data['display_description'], share_network_id=fake_share_network_id, - availability_zones=availability_zones) + availability_zones=availability_zones, + az_request_multiple_subnet_support_map=az_multiple_sn_support_map) share['status'] = constants.STATUS_CREATING share['host'] = None @@ -825,25 +834,30 @@ class ShareAPITestCase(test.TestCase): 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, snapshot_host=None, + availability_zones=expected_azs, + az_request_multiple_subnet_support_map=compatible_azs_multiple, + snapshot_host=None, scheduler_hints=None ) db_api.share_get.assert_called_once() @ddt.data( - {'availability_zones': None, 'azs_with_subnet': []}, + {'availability_zones': None, 'compatible_azs_name': [], + 'compatible_azs_multiple': []}, {'availability_zones': ['fake_az_1'], - 'azs_with_subnet': ['fake_az_2']} + 'compatible_azs_name': ['fake_az_2'], 'compatible_azs_multiple': []} ) @ddt.unpack def test_create_share_with_subnets_invalid_azs(self, availability_zones, - azs_with_subnet): + compatible_azs_name, + compatible_azs_multiple): 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)) + mock.Mock(return_value=[compatible_azs_name, + compatible_azs_multiple])) self.mock_object(quota.QUOTAS, 'commit') self.mock_object(self.api, 'create_instance') self.mock_object(db_api, 'share_get') @@ -879,19 +893,45 @@ class ShareAPITestCase(test.TestCase): share_data['display_description']) @ddt.data({'overs': {'gigabytes': 'fake'}, - 'expected_exception': exception.ShareSizeExceedsAvailableQuota}, + 'expected_exception': exception.ShareSizeExceedsAvailableQuota, + 'replication_type': None}, {'overs': {'shares': 'fake'}, - 'expected_exception': exception.ShareLimitExceeded}) + 'expected_exception': exception.ShareLimitExceeded, + 'replication_type': None}, + {'overs': {'replica_gigabytes': 'fake'}, + 'expected_exception': + exception.ShareReplicaSizeExceedsAvailableQuota, + 'replication_type': constants.REPLICATION_TYPE_READABLE}, + {'overs': {'share_replicas': 'fake'}, + 'expected_exception': exception.ShareReplicasLimitExceeded, + 'replication_type': constants.REPLICATION_TYPE_READABLE}) @ddt.unpack - def test_create_share_over_quota(self, overs, expected_exception): - share, share_data = self._setup_create_mocks() + def test_create_share_over_quota(self, overs, expected_exception, + replication_type): + extra_specs = {'replication_type': replication_type} + share_type = db_utils.create_share_type(extra_specs=extra_specs) + share_type = db_api.share_type_get(self.context, share_type['id']) + share, share_data = self._setup_create_mocks( + share_type_id=share_type['id']) + + az = share_data.pop('availability_zone') usages = {'gigabytes': {'reserved': 5, 'in_use': 5}, - 'shares': {'reserved': 10, 'in_use': 10}} - quotas = {'gigabytes': 5, 'shares': 10} + 'shares': {'reserved': 10, 'in_use': 10}, + 'replica_gigabytes': {'reserved': 5, 'in_use': 5}, + 'share_replicas': {'reserved': 10, 'in_use': 10}} + + quotas = {'gigabytes': 5, 'shares': 10, + 'replica_gigabytes': 5, 'share_replicas': 10} + exc = exception.OverQuota(overs=overs, usages=usages, quotas=quotas) self.mock_object(quota.QUOTAS, 'reserve', mock.Mock(side_effect=exc)) + if replication_type: + # Prevent the raising of an exception, to force the call to the + # function _check_if_replica_quotas_exceeded + self.mock_object(self.api, '_check_if_share_quotas_exceeded') + self.assertRaises( expected_exception, self.api.create, @@ -899,11 +939,19 @@ class ShareAPITestCase(test.TestCase): share_data['share_proto'], share_data['size'], share_data['display_name'], - share_data['display_description'] + share_data['display_description'], + availability_zone=az, + share_type=share_type ) - quota.QUOTAS.reserve.assert_called_once_with( - self.context, share_type_id=None, - shares=1, gigabytes=share_data['size']) + + if replication_type: + quota.QUOTAS.reserve.assert_called_once_with( + self.context, share_type_id=share_type['id'], + gigabytes=1, shares=1, share_replicas=1, replica_gigabytes=1) + else: + quota.QUOTAS.reserve.assert_called_once_with( + self.context, share_type_id=share_type['id'], + shares=1, gigabytes=share_data['size']) @ddt.data({'overs': {'per_share_gigabytes': 'fake'}, 'expected_exception': exception.ShareSizeExceedsLimit}) @@ -1114,7 +1162,7 @@ class ShareAPITestCase(test.TestCase): 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']) + share_network_subnets=[fake_subnet]) share_network = db_utils.create_share_network(id='fake') fake_share_data = { 'id': 'fakeid', @@ -1144,8 +1192,6 @@ class ShareAPITestCase(test.TestCase): 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(db_api, 'share_instances_get_all', mock.Mock(return_value=[])) self.mock_object(db_api, 'share_network_get', @@ -1194,8 +1240,6 @@ class ShareAPITestCase(test.TestCase): 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), @@ -1646,14 +1690,14 @@ class ShareAPITestCase(test.TestCase): fake_share_network = { 'id': 'fake_net_id' } - fake_share_net_subnet = { + fake_share_net_subnet = [{ 'id': 'fake_subnet_id', 'share_network_id': fake_share_network['id'] - } + }] identifier = 'fake_identifier' values = { 'host': host, - 'share_network_subnet_id': fake_share_net_subnet['id'], + 'share_network_subnets': [fake_share_net_subnet], 'status': constants.STATUS_MANAGING, 'is_auto_deletable': False, 'identifier': identifier, @@ -1663,7 +1707,7 @@ class ShareAPITestCase(test.TestCase): 'id': 'fake_server_id', 'status': constants.STATUS_MANAGING, 'host': host, - 'share_network_subnet_id': fake_share_net_subnet['id'], + 'share_network_subnets': [fake_share_net_subnet], 'is_auto_deletable': False, 'identifier': identifier, } @@ -1697,7 +1741,7 @@ class ShareAPITestCase(test.TestCase): result_dict = { 'host': result['host'], - 'share_network_subnet_id': result['share_network_subnet_id'], + 'share_network_subnets': result['share_network_subnets'], 'status': result['status'], 'is_auto_deletable': result['is_auto_deletable'], 'identifier': result['identifier'], @@ -2298,7 +2342,6 @@ class ShareAPITestCase(test.TestCase): mock_get_share_type_call = self.mock_object( share_types, 'get_share_type', mock.Mock(return_value=share_type)) - az = snapshot['share']['availability_zone'] if not az else az self.api.create( self.context, share_data['share_proto'], @@ -2309,6 +2352,7 @@ class ShareAPITestCase(test.TestCase): availability_zone=az, ) + expected_az = snapshot['share']['availability_zone'] if not az else az share_data.pop('availability_zone') mock_get_share_type_call.assert_called_once_with( @@ -2318,9 +2362,10 @@ class ShareAPITestCase(test.TestCase): self.api.create_instance.assert_called_once_with( self.context, share, share_network_id=share['share_network_id'], host=valid_host, share_type_id=share_type['id'], - availability_zone=az, + availability_zone=expected_az, share_group=None, share_group_snapshot_member=None, availability_zones=None, + az_request_multiple_subnet_support_map=None, snapshot_host=snapshot['share']['instance']['host'], scheduler_hints=None) share_api.policy.check_policy.assert_called_once_with( @@ -2331,6 +2376,48 @@ class ShareAPITestCase(test.TestCase): quota.QUOTAS.commit.assert_called_once_with( self.context, 'reservation', share_type_id=share_type['id']) + def test_create_share_with_share_group(self): + extra_specs = {'replication_type': constants.REPLICATION_TYPE_READABLE} + share_type = db_utils.create_share_type(extra_specs=extra_specs) + share_type = db_api.share_type_get(self.context, share_type['id']) + group = db_utils.create_share_group( + status=constants.STATUS_AVAILABLE, + share_types=[share_type['id']]) + share, share_data = self._setup_create_mocks( + share_type_id=share_type['id'], + share_group_id=group['id']) + + share_instance = db_utils.create_share_instance( + share_id=share['id']) + sg_snap_member = { + 'id': 'fake_sg_snap_member_id', + 'share_instance': share_instance + } + + az = share_data.pop('availability_zone') + + self.mock_object(quota.QUOTAS, 'reserve', + mock.Mock(return_value='reservation')) + self.mock_object(quota.QUOTAS, 'commit') + + self.api.create( + self.context, + share_data['share_proto'], + share_data['size'], + share_data['display_name'], + share_data['display_description'], + availability_zone=az, + share_type=share_type, + share_group_id=group['id'], + share_group_snapshot_member=sg_snap_member, + ) + quota.QUOTAS.reserve.assert_called_once_with( + self.context, share_type_id=share_type['id'], + gigabytes=1, shares=1, share_replicas=1, replica_gigabytes=1) + quota.QUOTAS.commit.assert_called_once_with( + self.context, 'reservation', share_type_id=share_type['id'] + ) + def test_create_share_share_type_contains_replication_type(self): extra_specs = {'replication_type': constants.REPLICATION_TYPE_READABLE} share_type = db_utils.create_share_type(extra_specs=extra_specs) @@ -3768,9 +3855,9 @@ class ShareAPITestCase(test.TestCase): 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.mock_object( + db_api, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=None)) self.assertRaises(exception.InvalidShare, self.api.create_share_replica, @@ -3784,7 +3871,8 @@ class ShareAPITestCase(test.TestCase): 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) + (db_api.share_network_subnets_get_all_by_availability_zone_id. + called)) def test_create_share_replica_az_not_found(self): request_spec = fakes.fake_replica_request_spec() @@ -3820,13 +3908,15 @@ class ShareAPITestCase(test.TestCase): self.context, az_name) @ddt.data( - {'availability_zones': '', 'azs_with_subnet': ['fake_az_1']}, + {'availability_zones': '', 'compatible_azs_name': ['fake_az_1'], + 'compatible_azs_multiple': []}, {'availability_zones': 'fake_az_1,fake_az_2', - 'azs_with_subnet': ['fake_az_2']} + 'compatible_azs_name': ['fake_az_2'], 'compatible_azs_multiple': []} ) @ddt.unpack def test_create_share_replica_azs_with_subnets(self, availability_zones, - azs_with_subnet): + compatible_azs_name, + compatible_azs_multiple): request_spec = fakes.fake_replica_request_spec() replica = request_spec['share_instance_properties'] share_network_id = 'fake_share_network_id' @@ -3850,15 +3940,16 @@ class ShareAPITestCase(test.TestCase): 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)) + mock.Mock(return_value=[compatible_azs_name, + compatible_azs_multiple])) if availability_zones == '': - expected_azs = azs_with_subnet + expected_azs = compatible_azs_name 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]) + [az for az in availability_zones if az in compatible_azs_name]) self.mock_object( self.api, 'create_share_instance_and_get_request_spec', @@ -3884,19 +3975,22 @@ class ShareAPITestCase(test.TestCase): self.context, share, availability_zone=None, share_network_id=share_network_id, share_type_id=share_type['id'], availability_zones=expected_azs, + az_request_multiple_subnet_support_map={}, 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': '', 'compatible_azs_name': [], + 'compatible_azs_multiple': []}, {'availability_zones': 'fake_az_1,fake_az_2', - 'azs_with_subnet': ['fake_az_3']} + 'compatible_azs_name': ['fake_az_3'], 'compatible_azs_multiple': []} ) @ddt.unpack def test_create_share_replica_azs_with_subnets_invalid_input( - self, availability_zones, azs_with_subnet): + self, availability_zones, + compatible_azs_name, compatible_azs_multiple): request_spec = fakes.fake_replica_request_spec() replica = request_spec['share_instance_properties'] share_network_id = 'fake_share_network_id' @@ -3916,7 +4010,8 @@ class ShareAPITestCase(test.TestCase): 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)) + mock.Mock(return_value=[compatible_azs_name, + compatible_azs_multiple])) self.assertRaises( exception.InvalidInput, @@ -3931,30 +4026,37 @@ class ShareAPITestCase(test.TestCase): ) @ddt.data({'has_snapshots': True, + 'az_id': {}, 'extra_specs': { 'replication_type': constants.REPLICATION_TYPE_DR, }, 'share_network_id': None}, {'has_snapshots': False, + 'az_id': {}, 'extra_specs': { 'availability_zones': 'FAKE_AZ,FAKE_AZ2', 'replication_type': constants.REPLICATION_TYPE_DR, }, 'share_network_id': None}, {'has_snapshots': True, + 'az_id': {}, 'extra_specs': { 'availability_zones': 'FAKE_AZ,FAKE_AZ2', 'replication_type': constants.REPLICATION_TYPE_READABLE, }, 'share_network_id': None}, {'has_snapshots': False, + 'az_id': {'fake_zone_id': 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, - share_network_id): + share_network_id, az_id): + subnets = db_utils.create_share_network_subnet( + id='fakeid', share_network_id='fake_network_id') + az = {'id': 'fake_zone_id'} request_spec = fakes.fake_replica_request_spec() replication_type = extra_specs['replication_type'] replica = request_spec['share_instance_properties'] @@ -3976,9 +4078,11 @@ 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(db_api, 'availability_zone_get', + mock.Mock(return_value=az)) + self.mock_object( + db_api, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=[subnets])) self.mock_object( share_api.API, 'create_share_instance_and_get_request_spec', mock.Mock(return_value=(fake_request_spec, fake_replica))) @@ -4011,6 +4115,7 @@ class ShareAPITestCase(test.TestCase): self.context, share, availability_zone='FAKE_AZ', share_network_id=share_network_id, share_type_id=share_type['id'], availability_zones=expected_azs, + az_request_multiple_subnet_support_map=az_id, cast_rules_to_readonly=cast_rules_to_readonly)) def test_delete_last_active_replica(self): @@ -4248,6 +4353,7 @@ class ShareAPITestCase(test.TestCase): share_network_id=share_network_id, share_type_id=share_type['id'], availability_zones=expected_azs, + az_request_multiple_subnet_support_map={}, cast_rules_to_readonly=False)) def test_migration_complete(self): @@ -4641,13 +4747,6 @@ class ShareAPITestCase(test.TestCase): } fake_share_network = ( db_utils.create_share_network() if create_share_network else None) - current_subnet_data = { - 'id': 'current_subnet_id', - 'neutron_net_id': 'current_neutron_net_id', - 'neutron_subnet_id': 'current_neutron_subnet_id', - } - fake_network_subnet = db_utils.create_share_network_subnet( - **current_subnet_data) expected_network_change = create_share_network is True fake_share_network_id = ( @@ -4675,11 +4774,8 @@ class ShareAPITestCase(test.TestCase): mock_az_get = self.mock_object( db_api, 'availability_zone_get', mock.Mock(return_value=fake_az)) mock_get_subnet = self.mock_object( - db_api, 'share_network_subnet_get_by_availability_zone_id', - mock.Mock(return_value=fake_subnet)) - mock_get_current_subnet = self.mock_object( - db_api, 'share_network_subnet_get', - mock.Mock(return_value=fake_network_subnet)) + db_api, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=[fake_subnet])) exp_shares, exp_types, exp_service, exp_network_id, net_change = ( self.api._migration_initial_checks( @@ -4705,9 +4801,6 @@ class ShareAPITestCase(test.TestCase): mock_az_get.assert_called_once_with( self.context, service['availability_zone']['name'] ) - if create_share_network: - mock_get_current_subnet.assert_called_once_with( - self.context, fake_share_server['share_network_subnet_id']) def test_share_server_migration_get_destination(self): fake_source_server_id = 'fake_source_id' @@ -4858,7 +4951,7 @@ class ShareAPITestCase(test.TestCase): self.mock_object( db_api, 'availability_zone_get', mock.Mock(return_value=fake_az)) self.mock_object( - db_api, 'share_network_subnet_get_by_availability_zone_id', + db_api, 'share_network_subnets_get_all_by_availability_zone_id', mock.Mock(return_value=fake_subnet)) def test__migration_initial_checks_share_not_available(self): @@ -4907,7 +5000,7 @@ class ShareAPITestCase(test.TestCase): db_api.availability_zone_get.assert_called_once_with( self.context, service['availability_zone']['name'] ) - (db_api.share_network_subnet_get_by_availability_zone_id. + (db_api.share_network_subnets_get_all_by_availability_zone_id. assert_called_once_with( self.context, fake_share_network_id, fake_az['id'])) db_api.share_group_get_all_by_share_server.assert_called_once_with( @@ -4963,7 +5056,7 @@ class ShareAPITestCase(test.TestCase): db_api.availability_zone_get.assert_called_once_with( self.context, service['availability_zone']['name'] ) - (db_api.share_network_subnet_get_by_availability_zone_id. + (db_api.share_network_subnets_get_all_by_availability_zone_id. assert_called_once_with( self.context, fake_share_network_id, fake_az['id'])) db_api.share_group_get_all_by_share_server.assert_called_once_with( @@ -5018,7 +5111,7 @@ class ShareAPITestCase(test.TestCase): db_api.availability_zone_get.assert_called_once_with( self.context, service['availability_zone']['name'] ) - (db_api.share_network_subnet_get_by_availability_zone_id. + (db_api.share_network_subnets_get_all_by_availability_zone_id. assert_called_once_with( self.context, fake_share_network_id, fake_az['id'])) mock_snapshots_get.assert_called_once_with( @@ -5168,7 +5261,7 @@ class ShareAPITestCase(test.TestCase): db_api.availability_zone_get.assert_called_once_with( self.context, service['availability_zone']['name'] ) - (db_api.share_network_subnet_get_by_availability_zone_id. + (db_api.share_network_subnets_get_all_by_availability_zone_id. assert_called_once_with( self.context, fake_share_network_id, fake_az['id'])) mock_snapshots_get.assert_called_once_with( @@ -5256,7 +5349,7 @@ class ShareAPITestCase(test.TestCase): mock_az_get = self.mock_object( db_api, 'availability_zone_get', mock.Mock(return_value=fake_az)) mock_get_subnet = self.mock_object( - db_api, 'share_network_subnet_get_by_availability_zone_id', + db_api, 'share_network_subnets_get_all_by_availability_zone_id', mock.Mock(return_value=None)) self.assertRaises( @@ -5858,11 +5951,12 @@ class ShareAPITestCase(test.TestCase): ) def test__share_network_update_initial_checks_server_not_active(self): - db_utils.create_share_server( - share_network_subnet_id='fakeid', status=constants.STATUS_ERROR, - security_service_update_support=True) - db_utils.create_share_network_subnet( + share_subnet = db_utils.create_share_network_subnet( id='fakeid', share_network_id='fakenetid') + db_utils.create_share_server( + share_network_subnets=[share_subnet], + status=constants.STATUS_ERROR, + security_service_update_support=True) share_network = db_utils.create_share_network(id='fakenetid') new_sec_service = db_utils.create_security_service( share_network_id='fakenetid', type='ldap') @@ -5874,10 +5968,10 @@ class ShareAPITestCase(test.TestCase): ) def test__share_network_update_initial_checks_shares_not_available(self): - db_utils.create_share_server(share_network_subnet_id='fakeid', - security_service_update_support=True) - db_utils.create_share_network_subnet( + share_subnet = db_utils.create_share_network_subnet( id='fakeid', share_network_id='fake_network_id') + db_utils.create_share_server(share_network_subnets=[share_subnet], + security_service_update_support=True) share_network = db_utils.create_share_network( id='fake_network_id') new_sec_service = db_utils.create_security_service( @@ -5900,10 +5994,10 @@ class ShareAPITestCase(test.TestCase): search_opts={'share_network_id': share_network['id']}) def test__share_network_update_initial_checks_rules_in_error(self): - db_utils.create_share_server(share_network_subnet_id='fakeid', - security_service_update_support=True) - db_utils.create_share_network_subnet( + share_subnet = db_utils.create_share_network_subnet( id='fakeid', share_network_id='fake_network_id') + db_utils.create_share_server(share_network_subnets=[share_subnet], + security_service_update_support=True) share_network = db_utils.create_share_network( id='fake_network_id') new_sec_service = db_utils.create_security_service( @@ -5928,10 +6022,10 @@ class ShareAPITestCase(test.TestCase): search_opts={'share_network_id': share_network['id']}) def test__share_network_update_initial_checks_share_is_busy(self): - db_utils.create_share_server(share_network_subnet_id='fakeid', - security_service_update_support=True) - db_utils.create_share_network_subnet( + share_subnet = db_utils.create_share_network_subnet( id='fakeid', share_network_id='fake_net_id') + db_utils.create_share_server(share_network_subnets=[share_subnet], + security_service_update_support=True) share_network = db_utils.create_share_network(id='fake_net_id') new_sec_service = db_utils.create_security_service( share_network_id='fake_net_id', type='ldap') @@ -5958,10 +6052,10 @@ class ShareAPITestCase(test.TestCase): self.api._check_is_share_busy.assert_called_once_with(shares[0]) def test__share_network_update_initial_checks_unsupported_server(self): - db_utils.create_share_server(share_network_subnet_id='fakeid', - security_service_update_support=False) - db_utils.create_share_network_subnet( + share_subnet = db_utils.create_share_network_subnet( id='fakeid', share_network_id='fake_net_id') + db_utils.create_share_server(share_network_subnets=[share_subnet], + security_service_update_support=False) share_network = db_utils.create_share_network(id='fake_net_id') self.assertRaises( @@ -6184,60 +6278,125 @@ class ShareAPITestCase(test.TestCase): '_' + 'hosts_check') fake_share_network = {'id': 'fake_network_id'} backend_hosts = {'hostA', 'hostB'} - hosts_to_validate = {} - for bh in backend_hosts: - hosts_to_validate[bh] = None + fake_return = 'fake_update' mock_get_key = self.mock_object( self.api, 'get_security_service_update_key', mock.Mock(return_value=fake_key)) + mock_do_update_validate = self.mock_object( + self.api, '_do_update_validate_hosts', + mock.Mock(return_value=fake_return)) + + res = self.api._security_service_update_validate_hosts( + self.context, fake_share_network, backend_hosts, None, + new_security_service_id=new_sec_service_id, + current_security_service_id=curr_sec_service_id) + + self.assertEqual(fake_return, res) + mock_get_key.assert_called_once_with( + 'hosts_check', new_sec_service_id, + current_security_service_id=curr_sec_service_id) + mock_do_update_validate.assert_called_once_with( + self.context, fake_share_network['id'], backend_hosts, fake_key, + new_security_service_id=new_sec_service_id, + current_security_service_id=curr_sec_service_id) + + @ddt.data(True, False) + def test__do_update_validate_hosts(self, update_security_service): + curr_sec_service_id = None + new_sec_service_id = None + new_share_network_subnet = 'fake_new_share_network_subnet' + if update_security_service: + curr_sec_service_id = "fake_curr_sec_serv_id" + new_sec_service_id = "fake_new_sec_serv_id" + new_share_network_subnet = None + + fake_key = 'fake_key' + fake_share_network_id = 'fake_network_id' + backend_hosts = {'hostA', 'hostB'} + hosts_to_validate = {} + for bh in backend_hosts: + hosts_to_validate[bh] = None mock_async_data_get = self.mock_object( db_api, 'async_operation_data_get', mock.Mock(return_value=None)) mock_async_data_update = self.mock_object( db_api, 'async_operation_data_update') - mock_shareapi_check = self.mock_object( + mock_check_update_allocations = self.mock_object( + self.share_rpcapi, 'check_update_share_server_network_allocations') + mock_check_update_services = self.mock_object( self.share_rpcapi, 'check_update_share_network_security_service') - compatible, hosts_info = ( - self.api._security_service_update_validate_hosts( - self.context, fake_share_network, backend_hosts, None, - new_security_service_id=new_sec_service_id, - current_security_service_id=curr_sec_service_id)) + compatible, hosts_info = self.api._do_update_validate_hosts( + self.context, fake_share_network_id, backend_hosts, fake_key, + new_share_network_subnet=new_share_network_subnet, + new_security_service_id=new_sec_service_id, + current_security_service_id=curr_sec_service_id) self.assertIsNone(compatible) self.assertEqual(hosts_to_validate, hosts_info) - - mock_get_key.assert_called_once_with( - 'hosts_check', new_sec_service_id, - current_security_service_id=curr_sec_service_id) mock_async_data_get.assert_called_once_with( - self.context, fake_share_network['id'], fake_key) + self.context, fake_share_network_id, fake_key) mock_async_data_update.assert_called_once_with( - self.context, fake_share_network['id'], + self.context, fake_share_network_id, {fake_key: json.dumps(hosts_to_validate)}) - mock_shareapi_check_calls = [] + mock_share_api_check_calls = [] for host in backend_hosts: - mock_shareapi_check_calls.append( - mock.call(self.context, host, fake_share_network['id'], - new_sec_service_id, - current_security_service_id=curr_sec_service_id)) - mock_shareapi_check.assert_has_calls(mock_shareapi_check_calls) + if update_security_service: + mock_share_api_check_calls.append( + mock.call(self.context, host, fake_share_network_id, + new_sec_service_id, + current_security_service_id=curr_sec_service_id)) + else: + mock_share_api_check_calls.append( + mock.call(self.context, host, fake_share_network_id, + new_share_network_subnet)) + if update_security_service: + mock_check_update_services.assert_has_calls( + mock_share_api_check_calls) + mock_check_update_allocations.assert_not_called() + else: + mock_check_update_allocations.assert_has_calls( + mock_share_api_check_calls) + mock_check_update_services.assert_not_called() @ddt.data( - {'new_host': None, 'host_support': None, 'exp_result': None}, - {'new_host': None, 'host_support': False, 'exp_result': False}, - {'new_host': None, 'host_support': True, 'exp_result': True}, - {'new_host': 'hostC', 'host_support': None, 'exp_result': None}, - {'new_host': 'hostC', 'host_support': False, 'exp_result': False}, - {'new_host': 'hostC', 'host_support': True, 'exp_result': None}) + {'update_security_service': True, 'new_host': None, + 'host_support': None, 'exp_result': None}, + {'update_security_service': True, 'new_host': None, + 'host_support': False, 'exp_result': False}, + {'update_security_service': True, 'new_host': None, + 'host_support': True, 'exp_result': True}, + {'update_security_service': True, 'new_host': 'hostC', + 'host_support': None, 'exp_result': None}, + {'update_security_service': True, 'new_host': 'hostC', + 'host_support': False, 'exp_result': False}, + {'update_security_service': True, 'new_host': 'hostC', + 'host_support': True, 'exp_result': None}, + {'update_security_service': False, 'new_host': None, + 'host_support': None, 'exp_result': None}, + {'update_security_service': False, 'new_host': None, + 'host_support': False, 'exp_result': False}, + {'update_security_service': False, 'new_host': None, + 'host_support': True, 'exp_result': True}, + {'update_security_service': False, 'new_host': 'hostC', + 'host_support': None, 'exp_result': None}, + {'update_security_service': False, 'new_host': 'hostC', + 'host_support': False, 'exp_result': False}, + {'update_security_service': False, 'new_host': 'hostC', + 'host_support': True, 'exp_result': None}, + ) @ddt.unpack - def test__security_service_update_validate_hosts(self, new_host, - host_support, - exp_result): - curr_sec_service_id = "fake_curr_sec_serv_id" - new_sec_service_id = "fake_new_sec_serv_id" - fake_key = (curr_sec_service_id + '_' + new_sec_service_id + - '_' + 'hosts_check') - fake_share_network = {'id': 'fake_network_id'} + def test__do_update_validate_hosts_all( + self, update_security_service, new_host, host_support, exp_result): + curr_sec_service_id = None + new_sec_service_id = None + new_share_network_subnet = 'fake_new_share_network_subnet' + if update_security_service: + curr_sec_service_id = "fake_curr_sec_serv_id" + new_sec_service_id = "fake_new_sec_serv_id" + new_share_network_subnet = None + + fake_key = 'fake_key' + fake_share_network_id = 'fake_network_id' backend_hosts = ['hostA', 'hostB'] hosts_to_validate = {} for bh in backend_hosts: @@ -6248,41 +6407,41 @@ class ShareAPITestCase(test.TestCase): backend_hosts.append(new_host) hosts_to_validate[new_host] = None - mock_get_key = self.mock_object( - self.api, 'get_security_service_update_key', - mock.Mock(return_value=fake_key)) mock_async_data_get = self.mock_object( db_api, 'async_operation_data_get', mock.Mock(return_value=json_orig_hosts)) mock_async_data_update = self.mock_object( db_api, 'async_operation_data_update') - mock_shareapi_check = self.mock_object( + mock_check_update_allocations = self.mock_object( + self.share_rpcapi, 'check_update_share_server_network_allocations') + mock_check_update_services = self.mock_object( self.share_rpcapi, 'check_update_share_network_security_service') - result, hosts_info = ( - self.api._security_service_update_validate_hosts( - self.context, fake_share_network, backend_hosts, None, - new_security_service_id=new_sec_service_id, - current_security_service_id=curr_sec_service_id)) + result, hosts_info = self.api._do_update_validate_hosts( + self.context, fake_share_network_id, backend_hosts, fake_key, + new_share_network_subnet=new_share_network_subnet, + new_security_service_id=new_sec_service_id, + current_security_service_id=curr_sec_service_id) self.assertEqual(exp_result, result) self.assertEqual(hosts_to_validate, hosts_info) - - mock_get_key.assert_called_once_with( - 'hosts_check', new_sec_service_id, - current_security_service_id=curr_sec_service_id) mock_async_data_get.assert_called_once_with( - self.context, fake_share_network['id'], fake_key) + self.context, fake_share_network_id, fake_key) - # we fail earlier if one one the host answer False + # we fail earlier if one one the host answer False. if new_host and host_support is not False: mock_async_data_update.assert_called_once_with( - self.context, fake_share_network['id'], + self.context, fake_share_network_id, {fake_key: json.dumps(hosts_to_validate)}) - mock_shareapi_check.assert_called_once_with( - self.context, new_host, fake_share_network['id'], - new_sec_service_id, - current_security_service_id=curr_sec_service_id) + if update_security_service: + mock_check_update_services.assert_called_once_with( + self.context, new_host, fake_share_network_id, + new_sec_service_id, + current_security_service_id=curr_sec_service_id) + else: + mock_check_update_allocations.assert_called_once_with( + self.context, new_host, fake_share_network_id, + new_share_network_subnet) def test_soft_delete_share_already_soft_deleted(self): share = fakes.fake_share(id='fake_id', @@ -6357,6 +6516,408 @@ class ShareAPITestCase(test.TestCase): self.mock_object(db_api, 'share_restore') self.api.restore(self.context, share) + def test__share_server_update_allocations_validate_hosts(self): + update_return = 'fake_return' + mock_do_update = self.mock_object( + self.api, '_do_update_validate_hosts', + mock.Mock(return_value=update_return)) + + backend_hosts = 'fake_hosts' + update_key = 'fake_key' + share_network_id = 'fake_net_id' + subnet = { + 'neutron_net_id': 'fake_net_id', + 'neutron_subnet_id': 'fake_subnet_id', + 'availability_zone_id': 'fake_availability_zone_id', + } + res = self.api._share_server_update_allocations_validate_hosts( + self.context, backend_hosts, update_key, + share_network_id=share_network_id, + neutron_net_id=subnet['neutron_net_id'], + neutron_subnet_id=subnet['neutron_subnet_id'], + availability_zone_id=subnet['availability_zone_id']) + + self.assertEqual(update_return, res) + mock_do_update.assert_called_once_with( + self.context, share_network_id, backend_hosts, update_key, + new_share_network_subnet=subnet) + + def test_get_share_server_update_allocations_key(self): + availability_zone_id = None + share_network_id = 'fake_share_network_id' + expected_key = ('share_server_update_allocations_' + + share_network_id + '_' + str(availability_zone_id) + + '_' + 'hosts_check') + res = self.api.get_share_server_update_allocations_key( + share_network_id, availability_zone_id) + + self.assertEqual(expected_key, res) + + def test__share_server_update_allocations_initial_checks(self): + share_network = db_utils.create_share_network() + share1 = db_utils.create_share( + share_network_id=share_network['id'], + status=constants.STATUS_AVAILABLE) + server_host = 'fake_host' + share_server = db_utils.create_share_server(host=server_host) + mock_validate_service_host = self.mock_object( + utils, 'validate_service_host') + mock_share_get_all_by_share_server = self.mock_object( + self.api.db, 'share_get_all_by_share_server', + mock.Mock(return_value=[share1])) + mock_share_is_busy = self.mock_object( + self.api, '_check_is_share_busy') + + res_hosts = self.api._share_server_update_allocations_initial_checks( + self.context, share_network, [share_server]) + + self.assertEqual(set([server_host]), res_hosts) + mock_validate_service_host.assert_called_once() + mock_share_get_all_by_share_server.assert_called_once_with( + self.context, share_server['id']) + mock_share_is_busy.assert_called_once_with(share1) + + def test__share_server_update_allocations_initial_checks_no_support(self): + fake_share_network = { + 'id': 'fake_sn_id', + 'network_allocation_update_support': False, + 'status': constants.STATUS_NETWORK_ACTIVE, + } + sn_subnet = db_utils.create_share_network_subnet() + + self.assertRaises( + exception.InvalidShareNetwork, + self.api._share_server_update_allocations_initial_checks, + self.context, + fake_share_network, + sn_subnet) + + def test__share_server_update_allocations_initial_checks_inactive(self): + share_network = db_utils.create_share_network() + share_server = db_utils.create_share_server( + status=constants.STATUS_INACTIVE) + + self.assertRaises( + exception.InvalidShareNetwork, + self.api._share_server_update_allocations_initial_checks, + self.context, + share_network, + [share_server]) + + def test__share_server_update_allocations_initial_checks_shares_na(self): + share_network = db_utils.create_share_network() + share1 = db_utils.create_share( + share_network_id=share_network['id'], + status=constants.STATUS_ERROR) + share_server = db_utils.create_share_server() + mock_validate_service_host = self.mock_object( + utils, 'validate_service_host') + mock_share_get_all_by_share_server = self.mock_object( + self.api.db, 'share_get_all_by_share_server', + mock.Mock(return_value=[share1])) + + self.assertRaises( + exception.InvalidShareNetwork, + self.api._share_server_update_allocations_initial_checks, + self.context, + share_network, + [share_server]) + + mock_validate_service_host.assert_called_once() + mock_share_get_all_by_share_server.assert_called_once_with( + self.context, share_server['id']) + + def test__share_server_update_allocations_initial_checks_rules_na(self): + share_network = db_utils.create_share_network() + share1 = db_utils.create_share( + share_network_id=share_network['id'], + status=constants.STATUS_AVAILABLE) + share_server = db_utils.create_share_server() + share1['instance']['access_rules_status'] = constants.STATUS_INACTIVE + mock_validate_service_host = self.mock_object( + utils, 'validate_service_host') + mock_share_get_all_by_share_server = self.mock_object( + self.api.db, 'share_get_all_by_share_server', + mock.Mock(return_value=[share1])) + + self.assertRaises( + exception.InvalidShareNetwork, + self.api._share_server_update_allocations_initial_checks, + self.context, + share_network, + [share_server]) + + mock_validate_service_host.assert_called_once() + mock_share_get_all_by_share_server.assert_called_once_with( + self.context, share_server['id']) + + def test__share_server_update_allocations_initial_checks_share_busy(self): + share_network = db_utils.create_share_network() + share1 = db_utils.create_share( + share_network_id=share_network['id'], + status=constants.STATUS_AVAILABLE) + share_server = db_utils.create_share_server() + mock_validate_service_host = self.mock_object( + utils, 'validate_service_host') + mock_share_get_all_by_share_server = self.mock_object( + self.api.db, 'share_get_all_by_share_server', + mock.Mock(return_value=[share1])) + mock_share_is_busy = self.mock_object( + self.api, '_check_is_share_busy', + mock.Mock(side_effect=exception.ShareBusyException(message='fake')) + ) + + self.assertRaises( + exception.InvalidShareNetwork, + self.api._share_server_update_allocations_initial_checks, + self.context, + share_network, + [share_server]) + + mock_validate_service_host.assert_called_once() + mock_share_get_all_by_share_server.assert_called_once_with( + self.context, share_server['id']) + mock_share_is_busy.assert_called_once_with(share1) + + def test_check_update_share_server_network_allocations(self): + backend_hosts = 'fake_hosts' + mock_initial_check = self.mock_object( + self.api, '_share_server_update_allocations_initial_checks', + mock.Mock(return_value=backend_hosts)) + update_key = 'fake_key' + mock_get_key = self.mock_object( + self.api, 'get_share_server_update_allocations_key', + mock.Mock(return_value=update_key)) + mock_reset_data = self.mock_object( + self.api.db, 'async_operation_data_delete') + compatible = True + hosts_info = {'fake_host': True} + mock_validate_hosts = self.mock_object( + self.api, '_share_server_update_allocations_validate_hosts', + mock.Mock(return_value=(compatible, hosts_info))) + + share_network = {'id': 'fake_id'} + new_share_network_subnet = { + 'share_servers': 'fake_servers', + 'availability_zone_id': 'fake_availability_zone_id', + 'neutron_net_id': 'fake_neutron_net_id', + 'neutron_subnet_id': 'fake_neutron_subnet_id', + } + res = self.api.check_update_share_server_network_allocations( + self.context, share_network, new_share_network_subnet, True) + + self.assertEqual( + {'compatible': compatible, 'hosts_check_result': hosts_info}, res) + mock_initial_check.assert_called_once_with( + self.context, share_network, + new_share_network_subnet['share_servers']) + mock_get_key.assert_called_once_with( + share_network['id'], + new_share_network_subnet['availability_zone_id']) + mock_reset_data.assert_called_once_with( + self.context, share_network['id'], update_key) + mock_validate_hosts.assert_called_once_with( + self.context, backend_hosts, update_key, + share_network_id=share_network['id'], + neutron_net_id=new_share_network_subnet['neutron_net_id'], + neutron_subnet_id=new_share_network_subnet['neutron_subnet_id'], + availability_zone_id=( + new_share_network_subnet["availability_zone_id"])) + + def test_check_update_share_server_network_allocations_failed(self): + backend_hosts = 'fake_hosts' + self.mock_object( + self.api, '_share_server_update_allocations_initial_checks', + mock.Mock(return_value=backend_hosts)) + update_key = 'fake_key' + self.mock_object( + self.api, 'get_share_server_update_allocations_key', + mock.Mock(return_value=update_key)) + self.mock_object(self.api.db, 'async_operation_data_delete') + self.mock_object( + self.api, '_share_server_update_allocations_validate_hosts', + mock.Mock(side_effect=exception.InvalidShareNetwork(reason="msg"))) + + share_network = {'id': 'fake_id'} + new_share_network_subnet = { + 'share_servers': 'fake_servers', + 'availability_zone_id': 'fake_availability_zone_id', + 'neutron_net_id': 'fake_neutron_net_id', + 'neutron_subnet_id': 'fake_neutron_subnet_id', + } + self.assertRaises( + exception.InvalidShareNetwork, + self.api.check_update_share_server_network_allocations, + self.context, share_network, new_share_network_subnet, True) + + def test_update_share_server_network_allocations(self): + backend_host = 'fake_host' + backend_hosts = [backend_host] + mock_initial_check = self.mock_object( + self.api, '_share_server_update_allocations_initial_checks', + mock.Mock(return_value=backend_hosts)) + update_key = 'fake_key' + mock_get_key = self.mock_object( + self.api, 'get_share_server_update_allocations_key', + mock.Mock(return_value=update_key)) + mock_get_data = self.mock_object( + self.api.db, 'async_operation_data_get', + mock.Mock(return_value='fake_update_value')) + mock_validate_hosts = self.mock_object( + self.api, '_share_server_update_allocations_validate_hosts', + mock.Mock(return_value=(True, 'fake_host'))) + mock_net_update = self.mock_object(self.api.db, 'share_network_update') + mock_server_update = self.mock_object(self.api.db, + 'share_servers_update') + new_share_network_subnet_db = {'id': 'fake_subnet_id'} + mock_subnet_create = self.mock_object( + self.api.db, 'share_network_subnet_create', + mock.Mock(return_value=new_share_network_subnet_db)) + mock_update_allocations = self.mock_object( + self.api.share_rpcapi, 'update_share_server_network_allocations') + mock_delete_data = self.mock_object(self.api.db, + 'async_operation_data_delete') + + share_network = {'id': 'fake_id'} + server1 = {'id': 'fake_id'} + new_share_network_subnet = { + 'share_servers': [server1], + 'availability_zone_id': 'fake_availability_zone_id', + 'neutron_net_id': 'fake_neutron_net_id', + 'neutron_subnet_id': 'fake_neutron_subnet_id', + } + res_subnet = self.api.update_share_server_network_allocations( + self.context, share_network, new_share_network_subnet) + + self.assertEqual(new_share_network_subnet_db, res_subnet) + mock_initial_check.assert_called_once_with( + self.context, share_network, + new_share_network_subnet['share_servers']) + mock_get_key.assert_called_once_with( + share_network['id'], + new_share_network_subnet['availability_zone_id']) + mock_get_data.assert_called_once_with( + self.context, share_network['id'], update_key) + mock_validate_hosts.assert_called_once_with( + self.context, backend_hosts, update_key, + share_network_id=share_network['id'], + neutron_net_id=new_share_network_subnet['neutron_net_id'], + neutron_subnet_id=new_share_network_subnet['neutron_subnet_id'], + availability_zone_id=( + new_share_network_subnet["availability_zone_id"])) + mock_net_update.assert_called_once_with( + self.context, share_network['id'], + {'status': constants.STATUS_NETWORK_CHANGE}) + mock_server_update.assert_called_once_with( + self.context, [server1['id']], + {'status': constants.STATUS_SERVER_NETWORK_CHANGE}) + mock_subnet_create.assert_called_once_with( + self.context, new_share_network_subnet) + mock_update_allocations.assert_called_once_with( + self.context, backend_host, share_network['id'], + new_share_network_subnet_db['id']) + mock_delete_data.assert_called_once_with( + self.context, share_network['id'], update_key) + + def test_update_share_server_network_allocations_no_check(self): + backend_host = 'fake_host' + backend_hosts = [backend_host] + self.mock_object( + self.api, '_share_server_update_allocations_initial_checks', + mock.Mock(return_value=backend_hosts)) + update_key = 'fake_key' + self.mock_object( + self.api, 'get_share_server_update_allocations_key', + mock.Mock(return_value=update_key)) + self.mock_object( + self.api.db, 'async_operation_data_get', + mock.Mock(return_value=None)) + + share_network = {'id': 'fake_id'} + server1 = {'id': 'fake_id'} + new_share_network_subnet = { + 'share_servers': [server1], + 'availability_zone_id': 'fake_availability_zone_id', + 'neutron_net_id': 'fake_neutron_net_id', + 'neutron_subnet_id': 'fake_neutron_subnet_id', + } + self.assertRaises( + exception.InvalidShareNetwork, + self.api.update_share_server_network_allocations, + self.context, + share_network, + new_share_network_subnet) + + def test_update_share_server_network_allocations_fail_validation(self): + backend_host = 'fake_host' + backend_hosts = [backend_host] + self.mock_object( + self.api, '_share_server_update_allocations_initial_checks', + mock.Mock(return_value=backend_hosts)) + update_key = 'fake_key' + self.mock_object( + self.api, 'get_share_server_update_allocations_key', + mock.Mock(return_value=update_key)) + self.mock_object( + self.api.db, 'async_operation_data_get', + mock.Mock(return_value='fake_update_value')) + self.mock_object( + self.api, '_share_server_update_allocations_validate_hosts', + mock.Mock(side_effect=exception.InvalidShareNetwork)) + mock_delete_data = self.mock_object(self.api.db, + 'async_operation_data_delete') + + share_network = {'id': 'fake_id'} + server1 = {'id': 'fake_id'} + new_share_network_subnet = { + 'share_servers': [server1], + 'availability_zone_id': 'fake_availability_zone_id', + 'neutron_net_id': 'fake_neutron_net_id', + 'neutron_subnet_id': 'fake_neutron_subnet_id', + } + self.assertRaises( + exception.InvalidShareNetwork, + self.api.update_share_server_network_allocations, + self.context, + share_network, + new_share_network_subnet) + + mock_delete_data.assert_called_once_with( + self.context, share_network['id'], update_key) + + @ddt.data(False, None) + def test_update_share_server_network_allocations_check_fail(self, result): + backend_host = 'fake_host' + backend_hosts = [backend_host] + self.mock_object( + self.api, '_share_server_update_allocations_initial_checks', + mock.Mock(return_value=backend_hosts)) + update_key = 'fake_key' + self.mock_object( + self.api, 'get_share_server_update_allocations_key', + mock.Mock(return_value=update_key)) + self.mock_object( + self.api.db, 'async_operation_data_get', + mock.Mock(return_value='fake_update_value')) + self.mock_object( + self.api, '_share_server_update_allocations_validate_hosts', + mock.Mock(return_value=(result, 'fake_host'))) + + share_network = {'id': 'fake_id'} + server1 = {'id': 'fake_id'} + new_share_network_subnet = { + 'share_servers': [server1], + 'availability_zone_id': 'fake_availability_zone_id', + 'neutron_net_id': 'fake_neutron_net_id', + 'neutron_subnet_id': 'fake_neutron_subnet_id', + } + self.assertRaises( + exception.InvalidShareNetwork, + self.api.update_share_server_network_allocations, + self.context, + share_network, + new_share_network_subnet) + class OtherTenantsShareActionsTestCase(test.TestCase): def setUp(self): diff --git a/manila/tests/share/test_driver.py b/manila/tests/share/test_driver.py index 88a7a71d55..b576de379c 100644 --- a/manila/tests/share/test_driver.py +++ b/manila/tests/share/test_driver.py @@ -971,6 +971,37 @@ class ShareDriverTestCase(test.TestCase): {'id', 'fake_sec_service_id'}, current_security_service=None) + def test_check_update_share_server_network_allocations(self): + share_driver = self._instantiate_share_driver(None, True) + self.assertRaises( + NotImplementedError, + share_driver.check_update_share_server_network_allocations, + 'fake_context', + {'id', 'share_server_id'}, + {'admin_network_allocations': [], 'subnets': []}, + {"id": "fake_subnet_id"}, + [{"id": "fake_security_service_id"}], + [{'id', 'fake_share_instance_id'}], + [{"id": "fake_rule_id"}]) + + def test_update_share_server_network_allocations(self): + share_driver = self._instantiate_share_driver(None, True) + self.assertRaises( + NotImplementedError, + share_driver.update_share_server_network_allocations, + 'fake_context', + {'id', 'share_server_id'}, + {'admin_network_allocations': [], 'subnets': []}, + { + 'share_network_subnet_id': 'fake_share_network_subnet_id', + 'neutron_net_id': 'fake_neutron_net_id', + 'neutron_subnet_id': 'fake_neutron_subnet_id', + 'network_allocations': [] + }, + [{"id": "fake_security_service_id"}], + [{"id": "fake_share_id"}], + [{"id": "fake_snapshot_id"}]) + def test_create_share_group_from_sg_snapshot_with_no_members(self): share_driver = self._instantiate_share_driver(None, False) fake_share_group_dict = {} diff --git a/manila/tests/share/test_manager.py b/manila/tests/share/test_manager.py index 98ae7d45e7..6b91a281b4 100644 --- a/manila/tests/share/test_manager.py +++ b/manila/tests/share/test_manager.py @@ -16,6 +16,7 @@ """Test of Share Manager for Manila.""" import datetime import hashlib +import json import random from unittest import mock @@ -40,6 +41,7 @@ from manila.share import manager from manila.share import migration as migration_api from manila.share import rpcapi from manila.share import share_types +from manila.share import utils as share_utils from manila import test from manila.tests.api import fakes as test_fakes from manila.tests import db_utils @@ -101,6 +103,7 @@ class ShareManagerTestCase(test.TestCase): self.mock_object(self.share_manager.message_api, 'create') self.context = context.get_admin_context() self.share_manager.driver.initialized = True + self.share_manager.host = 'fake_host' mock.patch.object( lockutils, 'lock', fake_utils.get_fake_lock_context()) self.synchronized_lock_decorator_call = self.mock_object( @@ -735,7 +738,7 @@ class ShareManagerTestCase(test.TestCase): subnet = db_utils.create_share_network_subnet( share_network_id=network['id']) server = db_utils.create_share_server( - share_network_subnet_id=subnet['id'], host='fake_host', + share_network_subnets=[subnet], host='fake_host', backend_details=dict(fake='fake')) parent_share = db_utils.create_share(share_network_id='net-id', share_server_id=server['id']) @@ -941,9 +944,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)) + self.mock_object( + db, 'share_network_subnets_get_all_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') @@ -2170,7 +2173,8 @@ class ShareManagerTestCase(test.TestCase): 'share_type_id': 'fake_share_type_id', } fake_server = db_utils.create_share_server( - id='fake_srv_id', status=constants.STATUS_CREATING) + id='fake_srv_id', status=constants.STATUS_CREATING, + share_network_subnets=[share_net_subnet]) self.mock_object(self.share_manager, '_build_server_metadata', mock.Mock(return_value=fake_metadata)) @@ -2180,9 +2184,9 @@ class ShareManagerTestCase(test.TestCase): 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( + db, 'share_network_subnets_get_all_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): @@ -2472,7 +2476,7 @@ class ShareManagerTestCase(test.TestCase): ) share = db_utils.create_share(share_network_id=share_net['id']) share_srv = db_utils.create_share_server( - share_network_subnet_id=share_net_subnet['id'], + share_network_subnets=[share_net_subnet], host=self.share_manager.host) share_id = share['id'] @@ -2697,15 +2701,15 @@ class ShareManagerTestCase(test.TestCase): def test_provide_share_server_for_share_incompatible_servers(self): fake_exception = exception.ManilaException("fake") fake_share_network = db_utils.create_share_network(id='fake_sn_id') - fake_share_net_subnet = db_utils.create_share_network_subnet( + fake_share_net_subnets = [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)) + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=fake_share_net_subnets)) self.mock_object(db, 'share_server_get_all_by_host_and_share_subnet_valid', mock.Mock(return_value=[fake_share_server])) @@ -2788,9 +2792,14 @@ class ShareManagerTestCase(test.TestCase): def test_provide_share_server_for_share_group_incompatible_servers(self): fake_exception = exception.ManilaException("fake") - fake_share_server = {'id': 'fake'} sg = db_utils.create_share_group() - + share_network = {'id': 'fake_sn_id'} + share_net_subnets = [{'id': 'fake_sns_id', + 'share_network_id': share_network['id']}] + fake_share_server = { + 'id': 'fake_id', + 'share_network_subnets': share_net_subnets, + } self.mock_object(db, 'share_server_get_all_by_host_and_share_subnet_valid', mock.Mock(return_value=[fake_share_server])) @@ -2803,7 +2812,7 @@ class ShareManagerTestCase(test.TestCase): self.assertRaises( exception.ManilaException, self.share_manager._provide_share_server_for_share_group, - self.context, "fake_sn_id", "fake_sns_id", sg) + self.context, "fake_sn_id", share_net_subnets, sg) driver_mock = self.share_manager.driver driver_method_mock = ( @@ -2875,7 +2884,7 @@ class ShareManagerTestCase(test.TestCase): fake_data = self._setup_provide_server_for_migration_test() mock_subnet_get = self.mock_object( - db, 'share_network_subnet_get_by_availability_zone_id', + db, 'share_network_subnets_get_all_by_availability_zone_id', mock.Mock(return_value=None)) self.assertRaises( exception.ShareNetworkSubnetNotFound, @@ -2892,12 +2901,14 @@ class ShareManagerTestCase(test.TestCase): def test__provide_share_server_for_migration(self): fake_data = self._setup_provide_server_for_migration_test() - dest_share_server = db_utils.create_share_server() + dest_share_server = db_utils.create_share_server( + share_network_subnets=[fake_data['fake_network_subnet']]) expected_share_server_data = { 'host': self.share_manager.host, - 'share_network_subnet_id': fake_data['fake_network_subnet']['id'], + 'share_network_subnets': [fake_data['fake_network_subnet']], 'status': constants.STATUS_CREATING, 'security_service_update_support': False, + 'network_allocation_update_support': False, } fake_metadata = { 'migration_destination': True, @@ -2906,8 +2917,8 @@ class ShareManagerTestCase(test.TestCase): } mock_subnet_get = self.mock_object( - db, 'share_network_subnet_get_by_availability_zone_id', - mock.Mock(return_value=fake_data['fake_network_subnet'])) + db, 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=[fake_data['fake_network_subnet']])) mock_server_create = self.mock_object( db, 'share_server_create', mock.Mock(return_value=dest_share_server)) @@ -3318,8 +3329,13 @@ class ShareManagerTestCase(test.TestCase): def test_delete_share_instance_share_server_not_found(self): share_net = db_utils.create_share_network() - share = db_utils.create_share(share_network_id=share_net['id'], - share_server_id='fake-id') + share_network_subnet = db_utils.create_share_network_subnet( + share_network_id=share_net['id'] + ) + share = db_utils.create_share( + share_network_id=share_net['id'], + share_server_id='fake-id', + share_network_subnets=[share_network_subnet]) self.assertRaises( exception.ShareServerNotFound, @@ -3332,19 +3348,22 @@ class ShareManagerTestCase(test.TestCase): def test_delete_share_instance_last_on_srv_with_sec_service( self, with_details): share_net = db_utils.create_share_network() + share_network_subnet = db_utils.create_share_network_subnet( + share_network_id=share_net['id'] + ) sec_service = db_utils.create_security_service( share_network_id=share_net['id']) backend_details = dict( security_service_ldap=jsonutils.dumps(sec_service)) if with_details: share_srv = db_utils.create_share_server( - share_network_id=share_net['id'], host=self.share_manager.host, - backend_details=backend_details) + backend_details=backend_details, + share_network_subnets=[share_network_subnet]) else: share_srv = db_utils.create_share_server( - share_network_id=share_net['id'], - host=self.share_manager.host) + host=self.share_manager.host, + share_network_subnets=[share_network_subnet]) db.share_server_backend_details_set( context.get_admin_context(), share_srv['id'], backend_details) share = db_utils.create_share(share_network_id=share_net['id'], @@ -3371,9 +3390,12 @@ class ShareManagerTestCase(test.TestCase): @ddt.unpack def test_delete_share_instance_last_on_server(self, force, side_effect): share_net = db_utils.create_share_network() + share_network_subnet = db_utils.create_share_network_subnet( + share_network_id=share_net['id'] + ) share_srv = db_utils.create_share_server( - share_network_id=share_net['id'], - host=self.share_manager.host + host=self.share_manager.host, + share_network_subnets=[share_network_subnet] ) share = db_utils.create_share(share_network_id=share_net['id'], share_server_id=share_srv['id']) @@ -3402,10 +3424,7 @@ class ShareManagerTestCase(test.TestCase): def test_delete_share_instance_last_on_server_deletion_disabled(self): share_net = db_utils.create_share_network() - share_srv = db_utils.create_share_server( - share_network_id=share_net['id'], - host=self.share_manager.host - ) + share_srv = db_utils.create_share_server(host=self.share_manager.host) share = db_utils.create_share(share_network_id=share_net['id'], share_server_id=share_srv['id']) share_srv = db.share_server_get(self.context, share_srv['id']) @@ -3428,7 +3447,6 @@ class ShareManagerTestCase(test.TestCase): def test_delete_share_instance_not_last_on_server(self): share_net = db_utils.create_share_network() share_srv = db_utils.create_share_server( - share_network_id=share_net['id'], host=self.share_manager.host ) share = db_utils.create_share(share_network_id=share_net['id'], @@ -3456,7 +3474,6 @@ class ShareManagerTestCase(test.TestCase): def test_delete_share_instance_not_found(self, side_effect): share_net = db_utils.create_share_network() share_srv = db_utils.create_share_server( - share_network_id=share_net['id'], host=self.share_manager.host) share = db_utils.create_share(share_network_id=share_net['id'], share_server_id=share_srv['id']) @@ -3505,11 +3522,11 @@ class ShareManagerTestCase(test.TestCase): # Setup required test data metadata = {'fake_metadata_key': 'fake_metadata_value'} share_network = db_utils.create_share_network(id='fake_sn_id') - share_net_subnet = db_utils.create_share_network_subnet( + share_net_subnets = [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']) + id='fake_id', share_network_subnets=share_net_subnets) network_info = {'security_services': []} for ss_type in constants.SECURITY_SERVICES_ALLOWED_TYPES: network_info['security_services'].append({ @@ -3527,13 +3544,14 @@ 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_subnet_get_all_by_share_server_id', + mock.Mock(return_value=share_net_subnets)) 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') self.mock_object(self.share_manager, '_form_server_setup_info', - mock.Mock(return_value=network_info)) + mock.Mock(return_value=[network_info])) self.mock_object(self.share_manager, '_validate_segmentation_id') self.mock_object(self.share_manager.driver, 'setup_server', mock.Mock(return_value=server_info)) @@ -3549,18 +3567,19 @@ class ShareManagerTestCase(test.TestCase): # verify results self.assertEqual(share_server, result) 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.context, share_net_subnets[0]['share_network_id']) + (self.share_manager.db.share_network_subnet_get_all_by_share_server_id. + assert_called_once_with( + self.context, share_server['id'])) self.share_manager.driver.allocate_network.assert_called_once_with( self.context, share_server, share_network, - share_server['share_network_subnet']) + share_server['share_network_subnets'][0]) self.share_manager._form_server_setup_info.assert_called_once_with( - self.context, share_server, share_network, share_net_subnet) + self.context, share_server, share_network, share_net_subnets) self.share_manager._validate_segmentation_id.assert_called_once_with( network_info) self.share_manager.driver.setup_server.assert_called_once_with( - network_info, metadata=metadata) + [network_info], metadata=metadata) (self.share_manager.db.share_server_backend_details_set. assert_has_calls([ mock.call(self.context, share_server['id'], @@ -3583,11 +3602,11 @@ class ShareManagerTestCase(test.TestCase): # Setup required test data 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_net_subnets = [{'id': 'fake_sns_id', + 'share_network_id': share_network['id']}] share_server = { 'id': 'fake_id', - 'share_network_subnet': share_net_subnet, + 'share_network_subnets': share_net_subnets, } network_info = { 'fake_network_info_key': 'fake_network_info_value', @@ -3597,12 +3616,13 @@ 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_subnet_get_all_by_share_server_id', + mock.Mock(return_value=share_net_subnets)) 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', - mock.Mock(return_value=network_info)) + mock.Mock(return_value=[network_info])) self.mock_object(self.share_manager.driver, 'setup_server', mock.Mock(return_value=server_info)) self.mock_object(self.share_manager.db, 'share_server_update', @@ -3616,30 +3636,40 @@ class ShareManagerTestCase(test.TestCase): # verify results self.assertEqual(share_server, result) 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.context, share_net_subnets[0]['share_network_id']) + (self.share_manager.db.share_network_subnet_get_all_by_share_server_id. + assert_called_once_with( + self.context, share_server['id'])) self.share_manager._form_server_setup_info.assert_called_once_with( - self.context, share_server, share_network, share_net_subnet) + self.context, share_server, share_network, share_net_subnets) self.share_manager.driver.setup_server.assert_called_once_with( - network_info, metadata=metadata) + [network_info], metadata=metadata) self.share_manager.db.share_server_update.assert_called_once_with( self.context, share_server['id'], {'status': constants.STATUS_ACTIVE, 'identifier': share_server['id']}) self.share_manager.driver.allocate_network.assert_called_once_with( - self.context, share_server, share_network, share_net_subnet) + self.context, share_server, share_network, share_net_subnets[0]) + + def setup_server_raise_no_subnets(self): + + self.assertRaises( + exception.NetworkBadConfigurationException, + self.share_manager._setup_server, + self.context, + {'share_network_subnets': []}, + {}) def setup_server_raise_exception(self, detail_data_proper): # Setup required test data metadata = {'fake_metadata_key': 'fake_metadata_value'} 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_net_subnets = [{'id': 'fake_sns_id', + 'share_network_id': share_network['id']}] share_server = { 'id': 'fake_id', - 'share_network_subnet': share_net_subnet + 'share_network_subnets': share_net_subnets } network_info = { 'fake_network_info_key': 'fake_network_info_value', @@ -3656,13 +3686,14 @@ 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_network_subnet_get_all_by_share_server_id', + mock.Mock(return_value=share_net_subnets)) 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) self.mock_object(self.share_manager, '_form_server_setup_info', - mock.Mock(return_value=network_info)) + mock.Mock(return_value=[network_info])) self.mock_object(self.share_manager.db, 'share_server_backend_details_set') self.mock_object(self.share_manager.driver, 'setup_server', @@ -3684,17 +3715,18 @@ 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, share_net_subnet) + self.context, share_server, share_network, share_net_subnets) 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_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.context, share_net_subnets[0]['share_network_id']) + (self.share_manager.db.share_network_subnet_get_all_by_share_server_id. + assert_called_once_with( + self.context, share_server['id'])) self.share_manager.driver.allocate_network.assert_has_calls([ mock.call(self.context, share_server, share_network, - share_net_subnet)]) + share_net_subnets[0])]) self.share_manager.driver.deallocate_network.assert_has_calls([ mock.call(self.context, share_server['id'])]) @@ -3720,11 +3752,12 @@ class ShareManagerTestCase(test.TestCase): if not isinstance(d, dict): return {} return d - share_net_subnet = db_utils.create_share_network_subnet( + + share_net_subnets = [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']) + id='fake', share_network_subnets=share_net_subnets) details = get_server_details_from_data(data) metadata = {'fake_metadata_key': 'fake_metadata_value'} @@ -3820,12 +3853,13 @@ class ShareManagerTestCase(test.TestCase): security_services='fake_security_services' ) fake_share_network_subnet = dict( + id='fake_sns_id', segmentation_id='fake_segmentation_id', cidr='fake_cidr', neutron_net_id='fake_neutron_net_id', neutron_subnet_id='fake_neutron_subnet_id', network_type='fake_network_type') - expected = dict( + expected = [dict( server_id=fake_share_server['id'], segmentation_id=fake_share_network_subnet['segmentation_id'], cidr=fake_share_network_subnet['cidr'], @@ -3837,17 +3871,20 @@ class ShareManagerTestCase(test.TestCase): admin_network_allocations=( fake_network_allocations_get_for_share_server(label='admin')), backend_details=fake_share_server['backend_details'], - network_type=fake_share_network_subnet['network_type']) + network_type=fake_share_network_subnet['network_type'])] network_info = self.share_manager._form_server_setup_info( self.context, fake_share_server, fake_share_network, - fake_share_network_subnet) + [fake_share_network_subnet]) self.assertEqual(expected, network_info) (self.share_manager.db.network_allocations_get_for_share_server. assert_has_calls([ - mock.call(self.context, fake_share_server['id'], label='user'), - mock.call(self.context, fake_share_server['id'], label='admin') + mock.call(self.context, fake_share_server['id'], + label='admin'), + mock.call(self.context, fake_share_server['id'], + label='user', + subnet_id=fake_share_network_subnet['id']) ])) @ddt.data( @@ -4360,8 +4397,8 @@ class ShareManagerTestCase(test.TestCase): 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) + 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=[fake_subnet]) ) self.mock_object( self.share_manager, '_provide_share_server_for_share_group', @@ -4442,7 +4479,8 @@ class ShareManagerTestCase(test.TestCase): } 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_ss = {'id': 'fake_ss_id', 'share_network_subnets': [fake_sns], + 'share_network_id': 'fake_sn_id'} 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', @@ -4503,6 +4541,38 @@ class ShareManagerTestCase(test.TestCase): utils.IsAMatcher(context.RequestContext), sg_id, {'status': constants.STATUS_ERROR}) + def test_create_sg_snapshot_share_network_without_subnets(self): + manager.CONF.set_default('driver_handles_share_servers', True) + self.mock_object( + self.share_manager.driver.configuration, 'safe_get', + mock.Mock(return_value=True)) + self.mock_object(self.share_manager.db, + 'share_instances_get_all_by_share_group_id', + mock.Mock(return_value=[])) + fake_group = { + 'id': 'fake_id', + 'source_share_group_snapshot_id': 'fake_snap_id', + 'shares': [], + 'share_network_id': 'fake_sn', + 'host': "fake_host", + } + self.mock_object( + self.share_manager.db, 'share_group_get', + mock.Mock(return_value=fake_group)) + fake_snap = {'id': 'fake_snap_id', 'share_group_snapshot_members': []} + self.mock_object(self.share_manager.db, 'share_group_snapshot_get', + mock.Mock(return_value=fake_snap)) + self.mock_object(self.share_manager, '_get_az_for_share_group', + mock.Mock(return_value='az')) + self.mock_object( + self.share_manager.db, + 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=[])) + + self.assertRaises(exception.ShareNetworkSubnetNotFound, + self.share_manager.create_share_group, + self.context, 'fake_share_group_id') + def test_create_share_group_from_sg_snapshot_share_network_dhss(self): manager.CONF.set_default('driver_handles_share_servers', True) self.mock_object(self.share_manager.driver.configuration, 'safe_get', @@ -4525,9 +4595,10 @@ 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.db, + 'share_network_subnets_get_all_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))) @@ -6358,11 +6429,12 @@ class ShareManagerTestCase(test.TestCase): 'password': 'fake_pass', } mock_manage_admin_network_allocations = mock.Mock() - 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']) + fake_share_server['share_network_subnets'] = [share_net_subnet] + share_server = db_utils.create_share_server(**fake_share_server) db.share_network_add_security_service(context.get_admin_context(), share_network['id'], security_service['id']) @@ -6434,7 +6506,7 @@ class ShareManagerTestCase(test.TestCase): ) mock_share_net_subnet_get.assert_called_once_with( utils.IsAMatcher(context.RequestContext), - fake_share_server['share_network_subnet_id'] + share_server['share_network_subnet_ids'][0] ) mock_network_allocations_get.assert_called_once_with() mock_share_server_net_info.assert_called_once_with( @@ -6453,7 +6525,8 @@ class ShareManagerTestCase(test.TestCase): mock_share_server_update.assert_called_once_with( utils.IsAMatcher(context.RequestContext), fake_share_server['id'], {'status': constants.STATUS_ACTIVE, - 'identifier': driver_return[0] or share_server['id']} + 'identifier': driver_return[0] or share_server['id'], + 'network_allocation_update_support': False} ) mock_set_backend_details.assert_called_once_with( utils.IsAMatcher(context.RequestContext), share_server['id'], @@ -6479,12 +6552,12 @@ class ShareManagerTestCase(test.TestCase): fake_share_server = fakes.fake_share_server_get() fake_list_empty_network_info = [] 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'] + fake_share_server['share_network_subnets'] = [share_network_subnet] + share_server = db_utils.create_share_server(**fake_share_server) self.share_manager.driver._admin_network_api = mock.Mock() mock_share_server_get = self.mock_object( @@ -6516,7 +6589,7 @@ class ShareManagerTestCase(test.TestCase): ) mock_share_net_subnet_get.assert_called_once_with( utils.IsAMatcher(context.RequestContext), - share_server['share_network_subnet_id'] + share_server['share_network_subnet_ids'][0] ) mock_network_allocations_get.assert_called_once_with() mock_get_share_network_info.assert_called_once_with( @@ -6529,12 +6602,12 @@ class ShareManagerTestCase(test.TestCase): fake_share_server = fakes.fake_share_server_get() fake_list_network_info = [{}, {}] 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'] + fake_share_server['share_network_subnets'] = [share_network_subnet] + share_server = db_utils.create_share_server(**fake_share_server) self.share_manager.driver._admin_network_api = mock.Mock() mock_share_server_get = self.mock_object( @@ -6574,7 +6647,7 @@ class ShareManagerTestCase(test.TestCase): ) mock_share_net_subnet_get.assert_called_once_with( utils.IsAMatcher(context.RequestContext), - share_server['share_network_subnet_id'] + share_server['share_network_subnet_ids'][0] ) mock_network_allocations_get.assert_called_once_with() mock_get_share_network_info.assert_called_once_with( @@ -8194,7 +8267,7 @@ class ShareManagerTestCase(test.TestCase): self, fake_share_instances, fake_snap_instances, fake_old_network, fake_new_network, fake_service, fake_request_spec, fake_driver_result, fake_new_share_server, server_info, - network_subnet): + network_subnet, new_network_subnet=None, az_compatible=True): self.mock_object(db, 'share_instances_get_all_by_share_server', mock.Mock(return_value=fake_share_instances)) self.mock_object(db, 'share_snapshot_instance_get_all_with_filters', @@ -8220,6 +8293,8 @@ class ShareManagerTestCase(test.TestCase): '_cast_access_rules_to_readonly_for_server') self.mock_object(db, 'share_network_subnet_get', mock.Mock(return_value=network_subnet)) + self.mock_object(db, 'share_network_subnet_get_all_by_share_server_id', + mock.Mock(return_value=[new_network_subnet])) self.mock_object(db, 'share_server_get', mock.Mock(return_value=fake_new_share_server)) self.mock_object(self.share_manager.driver, 'allocate_network') @@ -8233,6 +8308,8 @@ class ShareManagerTestCase(test.TestCase): mock.Mock(return_value=server_info)) self.mock_object(db, 'share_server_backend_details_set') self.mock_object(self.share_manager, 'delete_share_server') + self.mock_object(share_utils, 'is_az_subnets_compatible', + mock.Mock(return_value=az_compatible)) @ddt.data((True, True), (False, True)) @ddt.unpack @@ -8240,25 +8317,23 @@ class ShareManagerTestCase(test.TestCase): nondisruptive): old_subnet_id = 'fake_id' new_subnet_kwargs = {} - create_new_allocations = False if not nondisruptive: new_subnet_kwargs.update({ 'neutron_net_id': 'fake_nn_id', 'neutron_subnet_id': 'fake_sn_id' }) - create_new_allocations = True network_subnet = db_utils.create_share_network_subnet(id=old_subnet_id) new_network_subnet = db_utils.create_share_network_subnet( **new_subnet_kwargs) fake_old_share_server = { 'id': 'fake_server_id', - 'share_network_subnet': network_subnet, + 'share_network_subnets': [network_subnet], 'host': 'host@backend' } fake_new_share_server = { 'id': 'fake_server_id_2', - 'share_network_subnet': new_network_subnet, + 'share_network_subnets': [new_network_subnet], 'host': 'host@backend' } @@ -8290,12 +8365,12 @@ class ShareManagerTestCase(test.TestCase): 'backend_details': {'fake': 'fake'} } create_on_backend = not nondisruptive - self._setup_server_migration_start_mocks( fake_share_instances, fake_snap_instances, fake_old_network, fake_new_network, fake_service, fake_request_spec, fake_driver_result, fake_new_share_server, server_info, - network_subnet) + network_subnet, new_network_subnet=new_network_subnet, + az_compatible=create_on_backend) result = self.share_manager._share_server_migration_start_driver( self.context, fake_old_share_server, fake_dest_host, writable, @@ -8340,16 +8415,17 @@ class ShareManagerTestCase(test.TestCase): self.context, fake_old_share_server, fake_new_share_server, fake_share_instances, fake_snap_instances)) if not create_on_backend: - db.share_server_get.assert_called_once_with( - self.context, fake_new_share_server['id']) - if create_new_allocations: - db.share_network_subnet_get.assert_called_once_with( - self.context, fake_new_share_server['id']) - self.driver.allocate_network.assert_called_once_with( - self.context, fake_new_share_server, fake_new_network, - network_subnet) - self.driver.allocate_admin_network_assert_called_once_with( - self.context, fake_new_share_server) + share_utils.is_az_subnets_compatible.assert_called_once_with( + [new_network_subnet], [network_subnet]) + (db.share_network_subnet_get_all_by_share_server_id. + assert_called_once_with( + self.context, fake_new_share_server['id'])) + self.share_manager.driver.allocate_network.assert_called_once_with( + self.context, fake_new_share_server, fake_new_network, + new_network_subnet) + (self.share_manager.driver.allocate_admin_network. + assert_called_once_with(self.context, fake_new_share_server)) + if not writable: (self.share_manager._cast_access_rules_to_readonly_for_server. assert_called_once_with( @@ -9004,7 +9080,7 @@ class ShareManagerTestCase(test.TestCase): share_network_id=fake_share_network['id']) fake_source_share_server = db_utils.create_share_server() fake_dest_share_server = db_utils.create_share_server( - share_network_subnet_id=fake_share_network_subnet['id']) + share_network_subnets=[fake_share_network_subnet]) fake_share = db_utils.create_share() fake_snapshot = db_utils.create_snapshot(share_id=fake_share['id']) fake_service = {'availability_zone_id': 'fake_az_id', @@ -9012,10 +9088,10 @@ class ShareManagerTestCase(test.TestCase): fake_share_instances = [fake_share['instance']] fake_snapshot_instances = [fake_snapshot['instance']] fake_share_instance_id = fake_share['instance']['id'] - fake_allocation_data = { + fake_alloc_data = [{ 'network_allocations': [{'id': 'fake_id'}], 'admin_network_allocations': [{'id': 'fake_admin_id'}], - } + }] model_update['share_updates'][fake_share['instance']['id']] = { 'export_locations': { "path": "10.10.10.31:/fake_mount_point", @@ -9062,11 +9138,11 @@ class ShareManagerTestCase(test.TestCase): mock.Mock(return_value=dest_network_allocations) ) mock_subnet_get = self.mock_object( - db, 'share_network_subnet_get', + db, 'share_network_subnet_get_all_by_share_server_id', mock.Mock(return_value=fake_share_network_subnet)) mock_form_server_setup_info = self.mock_object( self.share_manager, '_form_server_setup_info', - mock.Mock(return_value=fake_allocation_data)) + mock.Mock(return_value=fake_alloc_data)) mock_server_migration_complete = self.mock_object( self.share_manager.driver, 'share_server_migration_complete', mock.Mock(return_value=model_update)) @@ -9106,7 +9182,7 @@ class ShareManagerTestCase(test.TestCase): mock_network_get.assert_called_once_with( self.context, fake_share_network['id']) mock_subnet_get.assert_called_once_with( - self.context, fake_share_network_subnet['id']) + self.context, fake_dest_share_server['id']) mock_allocations_get.assert_called_once_with( self.context, fake_dest_share_server['id']) @@ -9123,16 +9199,16 @@ class ShareManagerTestCase(test.TestCase): mock_network_allocation_update.assert_has_calls( [mock.call( self.context, - fake_allocation_data['network_allocations'][0]['id'], + fake_alloc_data[0]['network_allocations'][0]['id'], {'share_server_id': fake_dest_share_server['id']}), mock.call( self.context, - fake_allocation_data['admin_network_allocations'][0]['id'], + fake_alloc_data[0]['admin_network_allocations'][0]['id'], {'share_server_id': fake_dest_share_server['id']})]) mock_server_migration_complete.assert_called_once_with( self.context, fake_source_share_server, fake_dest_share_server, - fake_share_instances, fake_snapshot_instances, fake_allocation_data + fake_share_instances, fake_snapshot_instances, fake_alloc_data ) mock_service_get_by_args.assert_called_once_with( self.context, fake_dest_share_server['host'], 'manila-share') @@ -9370,7 +9446,8 @@ class ShareManagerTestCase(test.TestCase): db, 'share_server_get_all_by_host', mock.Mock(return_value=share_servers)) self.mock_object( - db, 'share_network_subnet_get', mock.Mock(return_value=subnet)) + db, 'share_network_subnet_get_all_by_share_server_id', + mock.Mock(return_value=[subnet])) self.mock_object( self.share_manager, '_form_server_setup_info', mock.Mock(return_value=network_info)) @@ -9408,12 +9485,11 @@ class ShareManagerTestCase(test.TestCase): share_network_subnet = db_utils.create_share_network_subnet() share_servers = [ db_utils.create_share_server( - share_network_subnet_id=share_network_subnet['id'])] + share_network_subnets=[share_network_subnet])] security_services_effect = mock.Mock(side_effect=security_services) share_network_id = share_network['id'] current_security_service_id = security_services[0]['id'] new_security_service_id = security_services[1]['id'] - share_network_subnet_id = share_servers[0]['share_network_subnet_id'] share_instances = [db_utils.create_share()['instance']] fake_rules = ['fake_rules'] network_info = {'fake': 'fake'} @@ -9462,10 +9538,12 @@ class ShareManagerTestCase(test.TestCase): db.share_server_get_all_by_host.assert_called_once_with( self.context, self.share_manager.host, filters={'share_network_id': share_network_id}) - db.share_network_subnet_get.assert_called_once_with( - self.context, share_network_subnet_id) + (db.share_network_subnet_get_all_by_share_server_id. + assert_called_once_with( + self.context, share_servers[0]['id'])) self.share_manager._form_server_setup_info.assert_called_once_with( - self.context, share_servers[0], share_network, share_network_subnet + self.context, share_servers[0], share_network, + [share_network_subnet] ) db.share_instances_get_all_by_share_server.assert_called_once_with( self.context, share_servers[0]['id'], with_share_data=True) @@ -9507,13 +9585,12 @@ class ShareManagerTestCase(test.TestCase): share_network_subnet = db_utils.create_share_network_subnet() share_servers = [ db_utils.create_share_server( - share_network_subnet_id=share_network_subnet['id'])] + share_network_subnets=[share_network_subnet])] security_services_effect = mock.Mock(side_effect=security_services) share_network_id = share_network['id'] current_security_service_id = security_services[0]['id'] new_security_service_id = security_services[1]['id'] - share_network_subnet_id = share_servers[0]['share_network_subnet_id'] - network_info = {'fake': 'fake'} + network_info = [{'fake': 'fake'}] share_instances = [db_utils.create_share()['instance']] fake_rules = ['fake_rules'] expected_instance_rules = [{ @@ -9540,10 +9617,11 @@ class ShareManagerTestCase(test.TestCase): db.share_server_get_all_by_host.assert_called_once_with( self.context, self.share_manager.host, filters={'share_network_id': share_network_id}) - db.share_network_subnet_get.assert_called_once_with( - self.context, share_network_subnet_id) + (db.share_network_subnet_get_all_by_share_server_id. + assert_called_once_with(self.context, share_servers[0]['id'])) self.share_manager._form_server_setup_info.assert_called_once_with( - self.context, share_servers[0], share_network, share_network_subnet + self.context, share_servers[0], share_network, + [share_network_subnet] ) db.share_instances_get_all_by_share_server.assert_called_once_with( self.context, share_servers[0]['id'], with_share_data=True) @@ -9565,15 +9643,14 @@ class ShareManagerTestCase(test.TestCase): share_network_subnet = db_utils.create_share_network_subnet() share_servers = [ db_utils.create_share_server( - share_network_subnet_id=share_network_subnet['id'])] + share_network_subnets=[share_network_subnet])] share_instances = [db_utils.create_share_instance(share_id='fake')] share_instance_ids = [instance['id'] for instance in share_instances] security_services_effect = mock.Mock(side_effect=security_services) share_network_id = share_network['id'] current_security_service_id = security_services[0]['id'] new_security_service_id = security_services[1]['id'] - share_network_subnet_id = share_servers[0]['share_network_subnet_id'] - network_info = {'fake': 'fake'} + network_info = [{'fake': 'fake'}] backend_details_keys = [ 'name', 'ou', 'domain', 'server', 'dns_ip', 'user', 'type', 'password'] @@ -9617,10 +9694,11 @@ class ShareManagerTestCase(test.TestCase): db.share_server_get_all_by_host.assert_called_once_with( self.context, self.share_manager.host, filters={'share_network_id': share_network_id}) - db.share_network_subnet_get.assert_called_once_with( - self.context, share_network_subnet_id) + (db.share_network_subnet_get_all_by_share_server_id. + assert_called_once_with(self.context, share_servers[0]['id'])) self.share_manager._form_server_setup_info.assert_called_once_with( - self.context, share_servers[0], share_network, share_network_subnet + self.context, share_servers[0], share_network, + [share_network_subnet] ) (self.share_manager.driver.update_share_server_security_service. assert_called_once_with( @@ -9682,6 +9760,389 @@ class ShareManagerTestCase(test.TestCase): current_security_service_id=current_security_service_id, check_only=True)) + @ddt.data(None, '{"fake_host": false}') + def test__update_share_server_allocations_check_operation( + self, current_hosts_info): + update_key = 'fake_key' + mock_get_allocations_key = self.mock_object( + self.share_manager.share_api, + 'get_share_server_update_allocations_key', + mock.Mock(return_value=update_key)) + mock_get_data = self.mock_object( + self.share_manager.db, + 'async_operation_data_get', + mock.Mock(return_value=current_hosts_info)) + mock_update_data = self.mock_object( + self.share_manager.db, + 'async_operation_data_update') + + share_network_id = 'fake_net_id' + availability_zone_id = 'fake_az_id' + self.share_manager._update_share_server_allocations_check_operation( + self.context, True, share_network_id=share_network_id, + availability_zone_id=availability_zone_id) + + mock_get_allocations_key.assert_called_once_with( + share_network_id, availability_zone_id) + mock_get_data.assert_called_once_with( + self.context, share_network_id, update_key) + if current_hosts_info: + mock_update_data.assert_called_once_with( + self.context, share_network_id, + {update_key: json.dumps({self.share_manager.host: True})}) + else: + mock_update_data.assert_not_called() + + def test__get_subnet_allocations(self): + + fake_allocations = ['fake_alloc'] + mock_get_allocations = self.mock_object( + self.share_manager.db, 'network_allocations_get_for_share_server', + mock.Mock(return_value=fake_allocations)) + subnet_id = 'fake_id' + neutron_net_id = 'fake_net_id' + neutron_subnet_id = 'fake_subnet_id' + + fake_subnet = { + 'id': subnet_id, + 'neutron_net_id': neutron_net_id, + 'neutron_subnet_id': neutron_subnet_id, + } + result = self.share_manager._get_subnet_allocations( + self.context, 'fake_id', fake_subnet) + + expected_allocations = { + 'share_network_subnet_id': subnet_id, + 'neutron_net_id': neutron_net_id, + 'neutron_subnet_id': neutron_subnet_id, + 'network_allocations': fake_allocations, + } + self.assertEqual(expected_allocations, result) + mock_get_allocations.assert_called_once_with( + self.context, 'fake_id', label='user', subnet_id=fake_subnet['id']) + + def test__form_network_allocations(self): + fake_allocation = 'fake_alloc' + mock_get_allocations = self.mock_object( + self.share_manager, '_get_subnet_allocations', + mock.Mock(return_value=fake_allocation)) + mock_admin_allocations = self.mock_object( + self.share_manager.db, 'network_allocations_get_for_share_server', + mock.Mock(return_value=[fake_allocation])) + + result = self.share_manager._form_network_allocations( + self.context, 'fake_id', ['fake_subnet']) + + expected_allocations = { + 'admin_network_allocations': [fake_allocation], + 'subnets': [fake_allocation], + } + self.assertEqual(expected_allocations, result) + mock_get_allocations.assert_called_once_with( + self.context, 'fake_id', 'fake_subnet') + mock_admin_allocations.assert_called_once_with( + self.context, 'fake_id', label='admin') + + @ddt.data(True, False) + def test_check_update_share_server_network_allocations(self, support): + security_services = 'fake_service' + mock_net_get = self.mock_object( + self.share_manager.db, 'share_network_get', + mock.Mock(return_value={'security_services': security_services})) + server = {'id': 'fake_id'} + subnets = [{'share_servers': [server]}] + mock_subnet_get = self.mock_object( + self.share_manager.db, + 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=subnets)) + mock_include_net_info = self.mock_object( + self.share_manager.driver.network_api, 'include_network_info') + current_network_allocations = 'fake_net_allocations' + mock_form_net_allocations = self.mock_object( + self.share_manager, + '_form_network_allocations', + mock.Mock(return_value=current_network_allocations)) + share_id = 'fake_id' + shares = [fakes.fake_share(id=share_id)] + mock_shares_get = self.mock_object( + self.share_manager.db, + 'share_instances_get_all_by_share_server', + mock.Mock(return_value=shares)) + access = 'fake_access' + mock_access_get = self.mock_object( + self.share_manager.db, + 'share_access_get_all_for_instance', + mock.Mock(return_value=access)) + mock_check_update = self.mock_object( + self.share_manager.driver, + 'check_update_share_server_network_allocations', + mock.Mock(return_value=support)) + mock_update_check_operation = self.mock_object( + self.share_manager, + '_update_share_server_allocations_check_operation') + + new_subnet = {'availability_zone_id': 'fake_az'} + net_id = 'fake_net_id' + self.share_manager.check_update_share_server_network_allocations( + self.context, net_id, new_subnet) + + mock_net_get.assert_called_once_with(self.context, net_id) + mock_subnet_get.assert_called_once_with( + self.context, net_id, new_subnet['availability_zone_id'], + fallback_to_default=False) + mock_include_net_info.assert_called_once_with(new_subnet) + mock_form_net_allocations.assert_called_once_with( + self.context, server['id'], subnets) + mock_shares_get.assert_called_once_with( + self.context, server['id'], with_share_data=True) + mock_access_get.assert_called_once_with(self.context, share_id) + access_list = [{'share_instance_id': share_id, 'access_rules': access}] + mock_check_update.assert_called_once_with( + self.context, server, current_network_allocations, + new_subnet, security_services, shares, access_list) + mock_update_check_operation.assert_called_once_with( + self.context, support, share_network_id=net_id, + availability_zone_id=new_subnet['availability_zone_id']) + + def test__do_update_share_server_network_allocations(self): + mock_allocate = self.mock_object( + self.share_manager.driver, 'allocate_network') + net_allocations = {'network_allocations': ['fake_allocation']} + mock_get_allocations = self.mock_object( + self.share_manager, '_get_subnet_allocations', + mock.Mock(return_value=net_allocations)) + mock_validate_segmentation = self.mock_object( + self.share_manager, '_validate_segmentation_id') + server_details = 'fake_details' + snap_export = [{'path': 'fake_path', 'is_admin_only': 'fake_is_admin'}] + update_model = { + 'server_details': server_details, + 'share_updates': {'fake_id': 'fake_export'}, + 'snapshot_updates': { + 'fake_id': { + 'export_locations': snap_export, + 'status': 'fake_status', + }, + }, + } + mock_update_server_allocations = self.mock_object( + self.share_manager.driver, + 'update_share_server_network_allocations', + mock.Mock(return_value=update_model)) + mock_update_net_allocation = self.mock_object( + self.share_manager.driver, 'update_network_allocation') + mock_db_backend_details_set = self.mock_object( + self.share_manager.db, 'share_server_backend_details_set') + mock_db_export_share_update = self.mock_object( + self.share_manager.db, 'share_export_locations_update') + mock_db_snapshot_update = self.mock_object( + self.share_manager.db, 'share_snapshot_instance_update') + mock_db_export_snap_update = self.mock_object( + self.share_manager.db, + 'share_snapshot_instance_export_locations_update') + + server = {'id': 'fake_id'} + share_net = {'security_services': 'fake_services'} + new_subnet = 'fake_subnet' + current_network_allocations = 'fake_allocations' + share_instances = 'fake_instances' + snapshot_instance_ids = 'fake_snaps' + self.share_manager._do_update_share_server_network_allocations( + self.context, server, share_net, new_subnet, + current_network_allocations, share_instances, snapshot_instance_ids + ) + + mock_update_server_allocations.assert_called_once_with( + self.context, server, current_network_allocations, + net_allocations, share_net['security_services'], share_instances, + snapshot_instance_ids) + mock_allocate.assert_called_once_with( + self.context, server, share_net, new_subnet) + mock_get_allocations.assert_called_once_with( + self.context, server['id'], new_subnet) + mock_validate_segmentation.assert_called_once_with( + net_allocations['network_allocations'][0]) + mock_update_net_allocation.assert_called_once_with( + self.context, server) + mock_db_backend_details_set.assert_called_once_with( + self.context, server['id'], server_details) + mock_db_export_share_update.assert_called_once_with( + self.context, 'fake_id', 'fake_export') + mock_db_snapshot_update.assert_called_once_with( + self.context, 'fake_id', {'status': 'fake_status'}) + mock_db_export_snap_update.assert_called_once_with( + self.context, 'fake_id', snap_export) + + def test__do_update_share_server_network_allocations_exception(self): + self.mock_object(self.share_manager.driver, 'allocate_network') + net_allocations = {'network_allocations': []} + self.mock_object( + self.share_manager, '_get_subnet_allocations', + mock.Mock(return_value=net_allocations)) + server = {'id': 'fake_id'} + share_net = {'security_services': 'fake_services'} + new_subnet = 'fake_subnet' + current_network_allocations = 'fake_allocations' + share_instances = 'fake_instances' + snapshot_instance_ids = 'fake_snaps' + + self.assertRaises( + exception.AllocationsNotFoundForShareServer, + self.share_manager._do_update_share_server_network_allocations, + self.context, server, share_net, new_subnet, + current_network_allocations, share_instances, snapshot_instance_ids + ) + + def test_update_share_server_network_allocations(self): + net_id = 'fake_net_id' + mock_net_get = self.mock_object( + self.share_manager.db, 'share_network_get', + mock.Mock(return_value={'id': net_id})) + new_subnet = {'availability_zone_id': 'fake_id'} + mock_subnet_get = self.mock_object( + self.share_manager.db, 'share_network_subnet_get', + mock.Mock(return_value=new_subnet)) + subnets = [{'id': 'fake_id'}] + mock_subnets_get = self.mock_object( + self.share_manager.db, + 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=subnets)) + server_id = 'fake_server_id' + server = {'id': server_id} + mock_servers_get = self.mock_object( + self.share_manager.db, + 'share_server_get_all_by_host_and_share_subnet_valid', + mock.Mock(return_value=[server])) + current_network_allocations = 'fake_current_net_allocations' + mock_form_net_allocations = self.mock_object( + self.share_manager, '_form_network_allocations', + mock.Mock(return_value=current_network_allocations)) + share_instances = [{'id': 'fake_id'}] + mock_instances_get = self.mock_object( + self.share_manager.db, + 'share_instances_get_all_by_share_server', + mock.Mock(return_value=share_instances)) + snap_instances = [{'id': 'fake_id'}] + mock_snap_instances_get = self.mock_object( + self.share_manager.db, + 'share_snapshot_instance_get_all_with_filters', + mock.Mock(return_value=snap_instances)) + mock_do_update = self.mock_object( + self.share_manager, '_do_update_share_server_network_allocations') + mock_server_update = self.mock_object( + self.share_manager.db, + 'share_server_update', + mock.Mock(return_value=snap_instances)) + mock_check_update_finished = self.mock_object( + self.share_manager, '_check_share_network_update_finished') + + new_share_network_subnet_id = 'fake_new_subnet_id' + self.share_manager.update_share_server_network_allocations( + self.context, net_id, new_share_network_subnet_id) + + mock_net_get.assert_called_once_with(self.context, net_id) + mock_subnet_get.assert_called_once_with(self.context, + new_share_network_subnet_id) + mock_subnets_get.assert_called_once_with( + self.context, net_id, new_subnet['availability_zone_id'], + fallback_to_default=False) + mock_servers_get.assert_called_once_with( + self.context, self.share_manager.host, new_share_network_subnet_id, + server_status=constants.STATUS_SERVER_NETWORK_CHANGE) + mock_form_net_allocations.assert_called_once_with( + self.context, server['id'], subnets) + mock_instances_get.assert_called_once_with( + self.context, server['id'], with_share_data=True) + mock_snap_instances_get.assert_called_once_with( + self.context, {'share_instance_ids': ['fake_id']}) + mock_do_update.assert_called_once_with( + self.context, server, {'id': net_id}, new_subnet, + current_network_allocations, share_instances, + snap_instances) + mock_server_update.assert_called_once_with( + self.context, server['id'], {'status': constants.STATUS_ACTIVE}) + mock_check_update_finished.assert_called_once_with( + self.context, share_network_id=net_id) + + def test_update_share_server_network_allocations_failed(self): + net_id = 'fake_net_id' + mock_net_get = self.mock_object( + self.share_manager.db, 'share_network_get', + mock.Mock(return_value={'id': net_id})) + new_subnet = {'availability_zone_id': 'fake_id'} + mock_subnet_get = self.mock_object( + self.share_manager.db, 'share_network_subnet_get', + mock.Mock(return_value=new_subnet)) + subnets = [{'id': 'fake_id'}] + mock_subnets_get = self.mock_object( + self.share_manager.db, + 'share_network_subnets_get_all_by_availability_zone_id', + mock.Mock(return_value=subnets)) + server_id = 'fake_server_id' + server = {'id': server_id} + mock_servers_get = self.mock_object( + self.share_manager.db, + 'share_server_get_all_by_host_and_share_subnet_valid', + mock.Mock(return_value=[server])) + current_network_allocations = 'fake_current_net_allocations' + mock_form_net_allocations = self.mock_object( + self.share_manager, '_form_network_allocations', + mock.Mock(return_value=current_network_allocations)) + share_instances = [{'id': 'fake_id'}] + mock_instances_get = self.mock_object( + self.share_manager.db, + 'share_instances_get_all_by_share_server', + mock.Mock(return_value=share_instances)) + snap_instances = [{'id': 'fake_id'}] + mock_snap_instances_get = self.mock_object( + self.share_manager.db, + 'share_snapshot_instance_get_all_with_filters', + mock.Mock(return_value=snap_instances)) + mock_do_update = self.mock_object( + self.share_manager, '_do_update_share_server_network_allocations', + mock.Mock(side_effect=exception.AllocationsNotFoundForShareServer)) + mock_handle_error = self.mock_object( + self.share_manager, '_handle_setup_server_error') + mock_update_status = self.mock_object( + self.share_manager, '_update_resource_status') + mock_server_update = self.mock_object( + self.share_manager.db, + 'share_server_update', + mock.Mock(return_value=snap_instances)) + mock_check_update_finished = self.mock_object( + self.share_manager, '_check_share_network_update_finished') + + new_share_network_subnet_id = 'fake_new_subnet_id' + self.share_manager.update_share_server_network_allocations( + self.context, net_id, new_share_network_subnet_id) + + mock_net_get.assert_called_once_with(self.context, net_id) + mock_subnet_get.assert_called_once_with(self.context, + new_share_network_subnet_id) + mock_subnets_get.assert_called_once_with( + self.context, net_id, new_subnet['availability_zone_id'], + fallback_to_default=False) + mock_servers_get.assert_called_once_with( + self.context, self.share_manager.host, new_share_network_subnet_id, + server_status=constants.STATUS_SERVER_NETWORK_CHANGE) + mock_form_net_allocations.assert_called_once_with( + self.context, server['id'], subnets) + mock_instances_get.assert_called_once_with( + self.context, server['id'], with_share_data=True) + mock_snap_instances_get.assert_called_once_with( + self.context, {'share_instance_ids': ['fake_id']}) + mock_do_update.assert_called_once_with( + self.context, server, {'id': net_id}, new_subnet, + current_network_allocations, share_instances, + snap_instances) + mock_server_update.assert_not_called() + mock_handle_error.assert_called() + mock_update_status.assert_called_once_with( + self.context, constants.STATUS_ERROR, + share_instance_ids=['fake_id'], snapshot_instance_ids=['fake_id']) + mock_check_update_finished.assert_called_once_with( + self.context, share_network_id=net_id) + @ddt.ddt class HookWrapperTestCase(test.TestCase): diff --git a/manila/tests/share/test_rpcapi.py b/manila/tests/share/test_rpcapi.py index 41f6f5af7d..78dbac2fa5 100644 --- a/manila/tests/share/test_rpcapi.py +++ b/manila/tests/share/test_rpcapi.py @@ -48,6 +48,17 @@ class ShareRpcAPITestCase(test.TestCase): share_group_snapshot = {'id': 'fake_share_group_id'} host = 'fake_host' share_server = db_utils.create_share_server(host=host) + share_network_subnet = { + 'availability_zone_id': 'fake_az_id', + 'neutron_net_id': 'fake_neutron_net_id', + 'neutron_subnet_id': 'fake_neutron_subnet_id', + 'ip_version': 4, + 'cidr': '127.0.0.0/28', + 'gateway': '127.0.0.1', + 'mtu': 1500, + 'network_type': 'vlan', + 'segmentation_id': 3000, + } self.fake_share = jsonutils.to_primitive(share) # mock out the getattr on the share db model object since jsonutils # doesn't know about those extra attributes to pull in @@ -61,6 +72,8 @@ class ShareRpcAPITestCase(test.TestCase): self.fake_share_group_snapshot = jsonutils.to_primitive( share_group_snapshot) self.fake_host = jsonutils.to_primitive(host) + self.fake_share_network_subnet = jsonutils.to_primitive( + share_network_subnet) self.ctxt = context.RequestContext('fake_user', 'fake_project') self.rpcapi = share_rpcapi.ShareAPI() @@ -476,3 +489,21 @@ class ShareRpcAPITestCase(test.TestCase): share_network_id='fake_net_id', new_security_service_id='fake_sec_service_id', current_security_service_id='fake_sec_service_id') + + def test_check_update_share_server_network_allocations(self): + self._test_share_api( + 'check_update_share_server_network_allocations', + rpc_method='cast', + version='1.23', + dest_host=self.fake_host, + share_network_id='fake_net_id', + new_share_network_subnet=self.fake_share_network_subnet) + + def test_update_share_server_network_allocations(self): + self._test_share_api( + 'update_share_server_network_allocations', + rpc_method='cast', + version='1.23', + dest_host=self.fake_host, + share_network_id='fake_net_id', + new_share_network_subnet_id='new_share_network_subnet_id') diff --git a/manila/tests/share/test_share_utils.py b/manila/tests/share/test_share_utils.py index 59bc161362..c8b6f3e6af 100644 --- a/manila/tests/share/test_share_utils.py +++ b/manila/tests/share/test_share_utils.py @@ -165,6 +165,33 @@ class ShareUtilsTestCase(test.TestCase): replica = share_utils.get_active_replica(replica_list) self.assertIsNone(replica) + @ddt.data( + {'fake_subnet': [{'neutron_net_id': 'fake_nn_id', + 'neutron_subnet_id': 'fake_nsb_id'}], + 'fake_new_subnet': [{'neutron_net_id': 'fake_nn_id', + 'neutron_subnet_id': 'fake_nsb_id'}], + 'is_compatible': True}, + {'fake_subnet': [{'neutron_net_id': 'fake_nn_id', + 'neutron_subnet_id': 'fake_nsb_id'}], + 'fake_new_subnet': [{'neutron_net_id': 'fake_nn_id', + 'neutron_subnet_id': 'fake_nsb_id2'}], + 'is_compatible': False}, + {'fake_subnet': [{'neutron_net_id': 'fake_nn_id', + 'neutron_subnet_id': 'fake_nsb_id'}, + {'neutron_net_id': 'fake_nn_id2', + 'neutron_subnet_id': 'fake_nsb_id2'}], + 'fake_new_subnet': [{'neutron_net_id': 'fake_nn_id', + 'neutron_subnet_id': 'fake_nsb_id'}], + 'is_compatible': False} + ) + @ddt.unpack + def test_is_az_subnets_compatible(self, fake_subnet, fake_new_subnet, + is_compatible): + expected_result = is_compatible + result = share_utils.is_az_subnets_compatible(fake_subnet, + fake_new_subnet) + self.assertEqual(expected_result, result) + class NotifyUsageTestCase(test.TestCase): @mock.patch('manila.share.utils._usage_from_share') diff --git a/manila/tests/test_network.py b/manila/tests/test_network.py index 3a40746886..26e1141095 100644 --- a/manila/tests/test_network.py +++ b/manila/tests/test_network.py @@ -88,6 +88,9 @@ class NetworkBaseAPITestCase(test.TestCase): def unmanage_network_allocations(self, context, share_server_id): pass + def include_network_info(self, share_network_subnet): + pass + self.assertRaises(TypeError, FakeNetworkAPI) def test_inherit_network_base_api_allocate_not_redefined(self): @@ -103,6 +106,9 @@ class NetworkBaseAPITestCase(test.TestCase): def unmanage_network_allocations(self, context, share_server_id): pass + def include_network_info(self, share_network_subnet): + pass + self.assertRaises(TypeError, FakeNetworkAPI) def test_inherit_network_base_api(self): @@ -121,6 +127,9 @@ class NetworkBaseAPITestCase(test.TestCase): def unmanage_network_allocations(self, context, share_server_id): pass + def include_network_info(self, share_network_subnet): + pass + result = FakeNetworkAPI() self.assertTrue(hasattr(result, '_verify_share_network')) @@ -143,6 +152,9 @@ class NetworkBaseAPITestCase(test.TestCase): def unmanage_network_allocations(self, context, share_server_id): pass + def include_network_info(self, share_network_subnet): + pass + result = FakeNetworkAPI() result._verify_share_network('foo_id', {'id': 'bar_id'}) @@ -163,6 +175,9 @@ class NetworkBaseAPITestCase(test.TestCase): def unmanage_network_allocations(self, context, share_server_id): pass + def include_network_info(self, share_network_subnet): + pass + result = FakeNetworkAPI() self.assertRaises( @@ -190,10 +205,13 @@ class NetworkBaseAPITestCase(test.TestCase): def unmanage_network_allocations(self, context, share_server_id): pass - network.CONF.set_default('network_plugin_ipv6_enabled', - network_plugin_ipv6_enabled) - network.CONF.set_default('network_plugin_ipv4_enabled', - network_plugin_ipv4_enabled) + def include_network_info(self, share_network_subnet): + pass + + network.CONF.set_default( + 'network_plugin_ipv6_enabled', network_plugin_ipv6_enabled) + network.CONF.set_default( + 'network_plugin_ipv4_enabled', network_plugin_ipv4_enabled) result = FakeNetworkAPI() diff --git a/manila/tests/test_service.py b/manila/tests/test_service.py index a542926883..e8edd4369c 100644 --- a/manila/tests/test_service.py +++ b/manila/tests/test_service.py @@ -89,6 +89,7 @@ class ServiceFlagsTestCase(test.TestCase): app = service.Service.create(host=host, binary=binary) app.start() app.stop() + app.wait() ref = db.service_get(context.get_admin_context(), app.service_id) db.service_destroy(context.get_admin_context(), app.service_id) self.assertFalse(ref['disabled']) @@ -123,6 +124,13 @@ service_create = { 'report_count': 0, 'availability_zone': 'nova', } +service_create_other_az = { + 'host': host, + 'binary': binary, + 'topic': topic, + 'report_count': 0, + 'availability_zone': 'other-zone', +} service_ref = { 'host': host, 'binary': binary, @@ -200,6 +208,58 @@ class ServiceTestCase(test.TestCase): service.db.service_update.assert_called_once_with( mock.ANY, service_ref['id'], mock.ANY) + @mock.patch.object(service.db, 'service_get_by_args', + mock.Mock(side_effect=fake_service_get_by_args)) + @mock.patch.object(service.db, 'service_create', + mock.Mock(return_value=service_ref)) + @mock.patch.object(service.db, 'service_get', + mock.Mock(return_value=service_ref)) + @mock.patch.object(service.db, 'service_update', + mock.Mock(return_value=service_ref. + update({'report_count': 1}))) + def test_report_state_newly_connected_different_az(self): + serv = service.Service(host, binary, topic, CONF.fake_manager) + serv.availability_zone = 'other-zone' + serv.start() + serv.model_disconnected = True + serv.report_state() + self.assertFalse(serv.model_disconnected) + service.db.service_get_by_args.assert_called_once_with( + mock.ANY, host, binary) + service.db.service_create.assert_called_once_with( + mock.ANY, service_create_other_az) + service.db.service_get.assert_called_once_with( + mock.ANY, service_ref['id']) + service.db.service_update.assert_called_once_with( + mock.ANY, service_ref['id'], mock.ANY) + + @mock.patch.object(service.db, 'service_get_by_args', + mock.Mock(side_effect=fake_service_get_by_args)) + @mock.patch.object(service.db, 'service_create', + mock.Mock(return_value=service_ref)) + @mock.patch.object(service.db, 'service_get', + mock.Mock(side_effect=[exception.NotFound, + service_ref])) + @mock.patch.object(service.db, 'service_update', + mock.Mock(return_value=service_ref. + update({'report_count': 1}))) + def test_report_state_newly_connected_not_found(self): + serv = service.Service(host, binary, topic, CONF.fake_manager) + serv.start() + serv.model_disconnected = True + serv.report_state() + self.assertFalse(serv.model_disconnected) + service.db.service_get_by_args.assert_called_once_with( + mock.ANY, host, binary) + service.db.service_create.assert_has_calls([ + mock.call(mock.ANY, service_create), + mock.call(mock.ANY, service_create)]) + service.db.service_get.assert_has_calls([ + mock.call(mock.ANY, service_ref['id']), + mock.call(mock.ANY, service_ref['id'])]) + service.db.service_update.assert_called_once_with( + mock.ANY, service_ref['id'], mock.ANY) + def test_report_state_service_not_ready(self): with mock.patch.object(service, 'db') as mock_db: mock_db.service_get.return_value = service_ref diff --git a/releasenotes/notes/add_support_multiple_subnet_per_az-e7b0359f4e8eca48.yaml b/releasenotes/notes/add_support_multiple_subnet_per_az-e7b0359f4e8eca48.yaml new file mode 100644 index 0000000000..b129030259 --- /dev/null +++ b/releasenotes/notes/add_support_multiple_subnet_per_az-e7b0359f4e8eca48.yaml @@ -0,0 +1,25 @@ +--- +features: + - | + Add support for multiple subnet per availability zone. The multiple + configuration can be done either on share server deployment or updating + a pre-existent share server. + + The new field ``network_allocation_update_support`` was added to share + server's model This field defaults to ``False``, and all of the + already deployed share servers are going to get the default value even if + their backend support it. Administrators will be able to update the field + value using ``manila-manage`` commands. + + The driver will report its support for adding a subnet on a pre-existent + share server through ``network_allocation_update_support``. Also, it will + report the support for creating the server with multiple subnets with the + ``share_server_multiple_subnet_support``. The scheduler will filter out + backend that does not handle this request during some operations. Example, + creating a share with a share network containing multiple subnets, only + hosts that support this deployment will be selected. +deprecations: + - | + Remove 'share_network_subnet_id' attribute from share server view and + add 'share_network_subnet_ids' starting with microversion '2.70'. The share + server has a list of subnets.