Skip to content

Commit

Permalink
Merge "Tiramisu: replication group support"
Browse files Browse the repository at this point in the history
  • Loading branch information
Jenkins authored and openstack-gerrit committed Jun 15, 2017
2 parents df1ef0e + 18744ba commit 9769c6c
Show file tree
Hide file tree
Showing 26 changed files with 1,424 additions and 15 deletions.
3 changes: 2 additions & 1 deletion cinder/api/openstack/api_version_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,15 @@
* 3.35 - Add ``volume-type`` filter to Get-Pools API.
* 3.36 - Add metadata to volumes/summary response body.
* 3.37 - Support sort backup by "name".
* 3.38 - Add replication group API (Tiramisu).
"""

# 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.
# Explicitly using /v1 or /v2 endpoints will still work
_MIN_API_VERSION = "3.0"
_MAX_API_VERSION = "3.37"
_MAX_API_VERSION = "3.38"
_LEGACY_API_VERSION1 = "1.0"
_LEGACY_API_VERSION2 = "2.0"

Expand Down
5 changes: 5 additions & 0 deletions cinder/api/openstack/rest_api_version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,8 @@ user documentation.
3.37
----
Support sort backup by "name".

3.38
----
Added enable_replication/disable_replication/failover_replication/
list_replication_targets for replication groups (Tiramisu).
106 changes: 106 additions & 0 deletions cinder/api/v3/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

GROUP_API_VERSION = '3.13'
GROUP_CREATE_FROM_SRC_API_VERSION = '3.14'
GROUP_REPLICATION_API_VERSION = '3.38'


class GroupsController(wsgi.Controller):
Expand Down Expand Up @@ -372,6 +373,111 @@ def update(self, req, id, body):

return webob.Response(status_int=http_client.ACCEPTED)

@wsgi.Controller.api_version(GROUP_REPLICATION_API_VERSION)
@wsgi.action("enable_replication")
def enable_replication(self, req, id, body):
"""Enables replications for a group."""
context = req.environ['cinder.context']
if body:
if not self.is_valid_body(body, 'enable_replication'):
msg = _("Missing required element 'enable_replication' in "
"request body.")
raise exc.HTTPBadRequest(explanation=msg)

LOG.info('Enable replication group with id: %s.', id,
context=context)

try:
group = self.group_api.get(context, id)
self.group_api.enable_replication(context, group)
# Not found exception will be handled at the wsgi level
except (exception.InvalidGroup, exception.InvalidGroupType,
exception.InvalidVolume, exception.InvalidVolumeType) as error:
raise exc.HTTPBadRequest(explanation=error.msg)

return webob.Response(status_int=202)

@wsgi.Controller.api_version(GROUP_REPLICATION_API_VERSION)
@wsgi.action("disable_replication")
def disable_replication(self, req, id, body):
"""Disables replications for a group."""
context = req.environ['cinder.context']
if body:
if not self.is_valid_body(body, 'disable_replication'):
msg = _("Missing required element 'disable_replication' in "
"request body.")
raise exc.HTTPBadRequest(explanation=msg)

LOG.info('Disable replication group with id: %s.', id,
context=context)

try:
group = self.group_api.get(context, id)
self.group_api.disable_replication(context, group)
# Not found exception will be handled at the wsgi level
except (exception.InvalidGroup, exception.InvalidGroupType,
exception.InvalidVolume, exception.InvalidVolumeType) as error:
raise exc.HTTPBadRequest(explanation=error.msg)

return webob.Response(status_int=202)

@wsgi.Controller.api_version(GROUP_REPLICATION_API_VERSION)
@wsgi.action("failover_replication")
def failover_replication(self, req, id, body):
"""Fails over replications for a group."""
context = req.environ['cinder.context']
if body:
if not self.is_valid_body(body, 'failover_replication'):
msg = _("Missing required element 'failover_replication' in "
"request body.")
raise exc.HTTPBadRequest(explanation=msg)

grp_body = body['failover_replication']
try:
allow_attached = strutils.bool_from_string(
grp_body.get('allow_attached_volume', False),
strict=True)
except ValueError:
msg = (_("Invalid value '%s' for allow_attached_volume flag.")
% grp_body)
raise exc.HTTPBadRequest(explanation=msg)
secondary_backend_id = grp_body.get('secondary_backend_id')

LOG.info('Failover replication group with id: %s.', id,
context=context)

try:
group = self.group_api.get(context, id)
self.group_api.failover_replication(context, group, allow_attached,
secondary_backend_id)
# Not found exception will be handled at the wsgi level
except (exception.InvalidGroup, exception.InvalidGroupType,
exception.InvalidVolume, exception.InvalidVolumeType) as error:
raise exc.HTTPBadRequest(explanation=error.msg)

return webob.Response(status_int=202)

@wsgi.Controller.api_version(GROUP_REPLICATION_API_VERSION)
@wsgi.action("list_replication_targets")
def list_replication_targets(self, req, id, body):
"""List replication targets for a group."""
context = req.environ['cinder.context']
if body:
if not self.is_valid_body(body, 'list_replication_targets'):
msg = _("Missing required element 'list_replication_targets' "
"in request body.")
raise exc.HTTPBadRequest(explanation=msg)

LOG.info('List replication targets for group with id: %s.', id,
context=context)

# Not found exception will be handled at the wsgi level
group = self.group_api.get(context, id)
replication_targets = self.group_api.list_replication_targets(
context, group)

return replication_targets


def create_resource():
return wsgi.Resource(GroupsController())
5 changes: 5 additions & 0 deletions cinder/api/v3/views/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ def detail(self, request, group):
group_ref['group']['volumes'] = [volume.id
for volume in group.volumes]

# Add replication_status if min version is greater than or equal
# to 3.38.
if req_version.matches("3.38", None):
group_ref['group']['replication_status'] = group.replication_status

return group_ref

def _list_view(self, func, request, groups):
Expand Down
17 changes: 13 additions & 4 deletions cinder/db/sqlalchemy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5563,6 +5563,16 @@ def _group_snapshot_get_query(context, session=None, project_only=False):
@apply_like_filters(model=models.Group)
def _process_groups_filters(query, filters):
if filters:
# NOTE(xyang): backend_match_level needs to be handled before
# is_valid_model_filters is called as it is not a column name
# in the db.
backend_match_level = filters.pop('backend_match_level', 'backend')
# host is a valid filter. Filter the query by host and
# backend_match_level first.
host = filters.pop('host', None)
if host:
query = query.filter(_filter_host(models.Group.host, host,
match_level=backend_match_level))
# Ensure that filters' keys exist on the model
if not is_valid_model_filters(models.Group, filters):
return
Expand All @@ -5582,10 +5592,9 @@ def _process_group_snapshot_filters(query, filters):

def _group_get_all(context, filters=None, marker=None, limit=None,
offset=None, sort_keys=None, sort_dirs=None):
if filters and not is_valid_model_filters(models.Group,
filters):
return []

# No need to call is_valid_model_filters here. It is called
# in _process_group_filters when _generate_paginate_query
# is called below.
session = get_session()
with session.begin():
# Generate the paginate query
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright (C) 2017 Dell Inc. or its subsidiaries.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from sqlalchemy import Column
from sqlalchemy import MetaData, String, Table


def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine

# Add replication_status column to groups table
table = Table('groups', meta, autoload=True)
if not hasattr(table.c, 'replication_status'):
new_column = Column('replication_status', String(255), nullable=True)
table.create_column(new_column)
2 changes: 2 additions & 0 deletions cinder/db/sqlalchemy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ class Group(BASE, CinderBase):
group_snapshot_id = Column(String(36))
source_group_id = Column(String(36))

replication_status = Column(String(255))


class Cgsnapshot(BASE, CinderBase):
"""Represents a cgsnapshot."""
Expand Down
5 changes: 5 additions & 0 deletions cinder/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,11 @@ class ReplicationError(CinderException):
"error: %(reason)s")


class ReplicationGroupError(CinderException):
message = _("Group %(group_id)s replication "
"error: %(reason)s.")


class ReplicationNotFound(NotFound):
message = _("Volume replication for %(volume_id)s "
"could not be found.")
Expand Down

0 comments on commit 9769c6c

Please sign in to comment.