Skip to content

Commit

Permalink
Implement share group with affinity
Browse files Browse the repository at this point in the history
Change-Id: I8a229c6593b4feb4e82e3c88514c41ec4d91a39f
  • Loading branch information
chuan137 committed Nov 11, 2024
1 parent 4af50a0 commit c2c7f6a
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 36 deletions.
5 changes: 3 additions & 2 deletions manila/api/v1/shares.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,9 @@ def _create(self, req, body,
"share group's one (%(sg_az)s).") % {
's_az': availability_zone_id, 'sg_az': sg_az_id}
raise exception.InvalidInput(msg)
availability_zone = db.availability_zone_get(
context, sg_az_id).name
if sg_az_id:
availability_zone = db.availability_zone_get(
context, sg_az_id).name

kwargs = {
'availability_zone': availability_zone,
Expand Down
75 changes: 66 additions & 9 deletions manila/api/v2/share_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,30 @@ def _create(self, req, body):
'source_share_group_snapshot_id',
'share_network_id',
'availability_zone',
'share_affinity',
}
invalid_fields = set(share_group.keys()) - valid_fields
if invalid_fields:
msg = _("The fields %s are invalid.") % invalid_fields
raise exc.HTTPBadRequest(explanation=msg)

if 'share_affinity' in share_group:
kwargs = self._create_options_with_affinity(context, share_group)
else:
kwargs = self._create_options(context, share_group)

try:
new_share_group = self.share_group_api.create(context, **kwargs)
except exception.InvalidShareGroupSnapshot as e:
raise exc.HTTPConflict(explanation=six.text_type(e))
except (exception.ShareGroupSnapshotNotFound,
exception.InvalidInput) as e:
raise exc.HTTPBadRequest(explanation=six.text_type(e))

return self._view_builder.detail(
req, {k: v for k, v in new_share_group.items()})

def _create_options(self, context, share_group):
if ('share_types' in share_group and
'source_share_group_snapshot_id' in share_group):
msg = _("Cannot supply both 'share_types' and "
Expand Down Expand Up @@ -273,17 +291,56 @@ def _create(self, req, body):
msg = _("Must specify a share group type as a default "
"share group type has not been configured.")
raise exc.HTTPBadRequest(explanation=msg)
return kwargs

def _create_options_with_affinity(self, context, share_group):
msg = ""
if "share_group_type_id" in share_group:
msg = _(
"Cannot supply both 'share_affinity' and "
"'share_group_type_id' attributes."
)
if "source_share_group_snapshot_id" in share_group:
msg = _(
"Cannot supply both 'share_affinity' and "
"'source_share_group_snapshot_id' attributes."
)
if "share_network_id" in share_group:
msg = _(
"Cannot supply both 'share_affinity' and "
"'share_network_id' attributes."
)
if "share_types" in share_group:
for st in share_group["share_types"]:
if not uuidutils.is_uuid_like(st):
msg = _(
"The 'share_types' attribute must be a list of uuids"
)
if msg != "":
raise exc.HTTPBadRequest(explanation=msg)

try:
new_share_group = self.share_group_api.create(context, **kwargs)
except exception.InvalidShareGroupSnapshot as e:
raise exc.HTTPConflict(explanation=six.text_type(e))
except (exception.ShareGroupSnapshotNotFound,
exception.InvalidInput) as e:
raise exc.HTTPBadRequest(explanation=six.text_type(e))
kwargs = {'share_affinity': share_group['share_affinity']}

return self._view_builder.detail(
req, {k: v for k, v in new_share_group.items()})
if 'name' in share_group:
kwargs['name'] = share_group['name']
if 'description' in share_group:
kwargs['description'] = share_group['description']
if 'share_types' in share_group:
kwargs['share_type_ids'] = share_group['share_types']
else:
default_share_type = share_types.get_default_share_type()
if default_share_type:
kwargs['share_type_ids'] = [default_share_type['id']]
if 'availability_zone' in share_group:
try:
az = db.availability_zone_get(
context, share_group["availability_zone"]
)
kwargs["availability_zone_id"] = az.id
kwargs["availability_zone"] = az.name
except exception.AvailabilityZoneNotFound as e:
raise exc.HTTPNotFound(explanation=six.text_type(e))
return kwargs

@wsgi.Controller.api_version('2.31', '2.54', experimental=True)
@wsgi.response(202)
Expand Down
1 change: 1 addition & 0 deletions manila/api/views/share_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def detail(self, request, share_group):
'share_network_id': share_group.get('share_network_id'),
'share_types': [st['share_type_id'] for st in share_group.get(
'share_types')],
'share_affinity': share_group.get('share_affinity'),
'links': self._get_links(request, share_group['id']),
}
self.update_versioned_resource_dict(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""add_share_affinity_col_to_share_group
Revision ID: dd172f1de61c
Revises: 636ecb8f3939
Create Date: 2024-11-07 15:56:42.899348
"""

# revision identifiers, used by Alembic.
revision = 'dd172f1de61c'
down_revision = '636ecb8f3939'

from alembic import op
import sqlalchemy as sa

TABLE_NAME = 'share_groups'
ATTR_NAME = 'share_affinity'

def upgrade():
op.add_column(
TABLE_NAME,
sa.Column(
ATTR_NAME,
sa.Enum('affinity', 'anti-affinity', name=ATTR_NAME),
nullable=True,
),
)


def downgrade():
op.drop_column(TABLE_NAME, ATTR_NAME)
1 change: 1 addition & 0 deletions manila/db/sqlalchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,7 @@ class ShareGroup(BASE, ManilaBase):
availability_zone_id = Column(
String(36), ForeignKey('availability_zones.id'), nullable=True)
consistent_snapshot_support = Column(Enum('pool', 'host'), default=None)
share_affinity = Column(Enum('affinity', 'anti-affinity'), default=None)

share_group_type = orm.relationship(
ShareGroupTypes,
Expand Down
24 changes: 23 additions & 1 deletion manila/share/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,8 @@ def create(self, context, share_proto, size, name, description,
"(%(group)s).") % params
raise exception.InvalidParameterValue(msg)

if not share_group.get('share_network_id') == share_network_id:
sg_share_network_id = share_group.get('share_network_id')
if sg_share_network_id and sg_share_network_id != share_network_id:
params = {
'net': share_network_id,
'group': share_group_id
Expand Down Expand Up @@ -509,6 +510,27 @@ def create_instance(self, context, share, share_network_id=None,
# snapshot
return

if share_group and share_group["share_affinity"]:
scheduler_hints = scheduler_hints or {}
if ((AFFINITY_HINT in scheduler_hints)
or (ANTI_AFFINITY_HINT in scheduler_hints)):
msg = _("Share group and scheduler hint %s/%s are mutually "
"exclusive. Please remove the scheduler hint or share "
"group affinity setting." %
(AFFINITY_HINT, ANTI_AFFINITY_HINT))
raise exception.InvalidInput(message=msg)
share_instances_in_group = (
self.db.share_instances_get_all_by_share_group_id(
context, share_group["id"]))
shares_in_group = set([
share_instance['share_id']
for share_instance in share_instances_in_group
])
if share_group['share_affinity'] == 'affinity':
scheduler_hints[AFFINITY_HINT] = shares_in_group
if share_group['share_affinity'] == 'anti-affinity':
scheduler_hints[ANTI_AFFINITY_HINT] = shares_in_group

if host:
self.share_rpcapi.create_share_instance(
context,
Expand Down
66 changes: 42 additions & 24 deletions manila/share_group/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ def __init__(self, db_driver=None):
def create(self, context, name=None, description=None,
share_type_ids=None, source_share_group_snapshot_id=None,
share_network_id=None, share_group_type_id=None,
availability_zone_id=None, availability_zone=None):
share_affinity = None, availability_zone_id=None,
availability_zone=None):
"""Create new share group."""

share_group_snapshot = None
Expand Down Expand Up @@ -123,35 +124,44 @@ def create(self, context, name=None, description=None,
# Check if share network is active, otherwise raise a BadRequest
api_common.check_share_network_is_active(share_network)

if (driver_handles_share_servers and
not (source_share_group_snapshot_id or share_network_id)):
if (not share_affinity) and (
driver_handles_share_servers
and not (source_share_group_snapshot_id or share_network_id)
):
msg = _("When using a share type with the "
"driver_handles_share_servers extra spec as "
"True, a share_network_id must be provided.")
raise exception.InvalidInput(reason=msg)

try:
share_group_type = self.db.share_group_type_get(
context, share_group_type_id)
except exception.ShareGroupTypeNotFound:
msg = _("The specified share group type %s does not exist.")
raise exception.InvalidInput(reason=msg % share_group_type_id)

supported_share_types = set(
[x['share_type_id'] for x in share_group_type['share_types']])
supported_share_type_objects = [
share_types.get_share_type(context, share_type_id) for
share_type_id in supported_share_types
]

if not set(share_type_ids or []) <= supported_share_types:
msg = _("The specified share types must be a subset of the share "
"types supported by the share group type.")
raise exception.InvalidInput(reason=msg)
share_group_type = {}
supported_share_type_objects = []
if share_group_type_id:
try:
share_group_type = self.db.share_group_type_get(
context, share_group_type_id)
except exception.ShareGroupTypeNotFound:
msg = _("The specified share group type %s does not exist.")
raise exception.InvalidInput(reason=msg % share_group_type_id)

supported_share_types = set([
x['share_type_id']
for x in share_group_type.get('share_types', [])
])
supported_share_type_objects = [
share_types.get_share_type(context, share_type_id)
for share_type_id in supported_share_types
]

if not set(share_type_ids or []) <= supported_share_types:
msg = _(
"The specified share types must be a subset of the share "
"types supported by the share group type."
)
raise exception.InvalidInput(reason=msg)

# Grab share type AZs for scheduling
share_types_of_new_group = (
share_type_objects or supported_share_type_objects
share_type_objects or supported_share_type_objects or []
)
stype_azs_of_new_group = []
stypes_unsupported_in_az = []
Expand Down Expand Up @@ -212,7 +222,8 @@ def _consumed(name):
'user_id': context.user_id,
'project_id': context.project_id,
'status': constants.STATUS_CREATING,
'share_types': share_type_ids or supported_share_types
'share_types': share_type_ids or supported_share_types,
'share_affinity': share_affinity,
}
if original_share_group:
options['host'] = original_share_group['host']
Expand Down Expand Up @@ -258,7 +269,14 @@ def _consumed(name):
request_spec['share_types'] = share_type_objects
request_spec['resource_type'] = share_group_type

if share_group_snapshot and original_share_group:
if share_affinity:
self.db.share_group_update(
context,
share_group["id"],
{"status": constants.STATUS_AVAILABLE},
)
share_group['status'] = constants.STATUS_AVAILABLE
elif share_group_snapshot and original_share_group:
self.share_rpcapi.create_share_group(
context, share_group, original_share_group['host'])
else:
Expand Down

0 comments on commit c2c7f6a

Please sign in to comment.