
Enable migration for attached volumes by calling Nova to have the hypervisor copy the data (Nova's swap_volume feature). A new API function is added for Nova to call once it is done copying to finish the Cinder side of the migration. The overall 'generic' migration flow (i.e., not performed by a driver) is as follows: 1. Creates a new volume 2a. If the source volume's state is 'available', attach both volumes and run 'dd' to copy, then detach both. When finished, call the migrate_volume_completion function. 2b. If the source volume's state is 'in-use', call Nova to perform the copy. Nova will attach the new volume, copy the data from the original volume to the new one, and detach the original volume. When the copy completes, Nova will call Cinder's new migrate_volume_completion function. 3. The migrate_volume_completion function deletes the original volume, and calls the database API's finish_volume_migration function. This copies all of the new volume's information to the original, and deletes the new volume's row, and thus we can keep using the original volume_id (the user sees no change). We also don't change the original volume's status, and instead add a migration_status column which only selected users can see (e.g., admin). The migration status is None when no migration is in progress, whether it succeeded or failed. The admin should check the volume's current host to determine success or failure. This is meant to simplify operations. The user will only be aware of a migration if they try to change the volume's state during the course of a migration. As mentioned, we change the volume while keeping the original volume ID. Because a volume's name depends on its ID, the new volume will have a different name than the original. This is the purpose of the name_id field in the database - the name is now based on name_id. So although we keep the original volume's ID, we use the new volume's ID as the name_id. Thus we can remove the rename_volume function - it is no longer necessary because the name_id field in the database already allows for the volume's name on the backend to not rely on its ID. The user than can see the migration_status can also see the name_id, in case they need to find it on the backend. There were a few other places throughout the code that relied on constructing a volume's name from its ID, and those were fixed. DocImpact Implements: bp online-volume-migration Change-Id: I8daaee174e426fbd450fa75f04f9c8e6fa09f01a
98 lines
3.8 KiB
Python
98 lines
3.8 KiB
Python
# Copyright 2013 IBM Corp.
|
|
#
|
|
# 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 cinder.api import extensions
|
|
from cinder.api.openstack import wsgi
|
|
from cinder.api import xmlutil
|
|
from cinder import volume
|
|
|
|
|
|
authorize = extensions.soft_extension_authorizer('volume',
|
|
'volume_mig_status_attribute')
|
|
|
|
|
|
class VolumeMigStatusAttributeController(wsgi.Controller):
|
|
def __init__(self, *args, **kwargs):
|
|
super(VolumeMigStatusAttributeController, self).__init__(*args,
|
|
**kwargs)
|
|
self.volume_api = volume.API()
|
|
|
|
def _add_volume_mig_status_attribute(self, context, resp_volume):
|
|
try:
|
|
db_volume = self.volume_api.get(context, resp_volume['id'])
|
|
except Exception:
|
|
return
|
|
else:
|
|
key = "%s:migstat" % Volume_mig_status_attribute.alias
|
|
resp_volume[key] = db_volume['migration_status']
|
|
key = "%s:name_id" % Volume_mig_status_attribute.alias
|
|
resp_volume[key] = db_volume['_name_id']
|
|
|
|
@wsgi.extends
|
|
def show(self, req, resp_obj, id):
|
|
context = req.environ['cinder.context']
|
|
if authorize(context):
|
|
resp_obj.attach(xml=VolumeMigStatusAttributeTemplate())
|
|
self._add_volume_mig_status_attribute(context,
|
|
resp_obj.obj['volume'])
|
|
|
|
@wsgi.extends
|
|
def detail(self, req, resp_obj):
|
|
context = req.environ['cinder.context']
|
|
if authorize(context):
|
|
resp_obj.attach(xml=VolumeListMigStatusAttributeTemplate())
|
|
for volume in list(resp_obj.obj['volumes']):
|
|
self._add_volume_mig_status_attribute(context, volume)
|
|
|
|
|
|
class Volume_mig_status_attribute(extensions.ExtensionDescriptor):
|
|
"""Expose migration_status as an attribute of a volume."""
|
|
|
|
name = "VolumeMigStatusAttribute"
|
|
alias = "os-vol-mig-status-attr"
|
|
namespace = ("http://docs.openstack.org/volume/ext/"
|
|
"volume_mig_status_attribute/api/v1")
|
|
updated = "2013-08-08T00:00:00+00:00"
|
|
|
|
def get_controller_extensions(self):
|
|
controller = VolumeMigStatusAttributeController()
|
|
extension = extensions.ControllerExtension(self, 'volumes', controller)
|
|
return [extension]
|
|
|
|
|
|
def make_volume(elem):
|
|
elem.set('{%s}migstat' % Volume_mig_status_attribute.namespace,
|
|
'%s:migstat' % Volume_mig_status_attribute.alias)
|
|
elem.set('{%s}name_id' % Volume_mig_status_attribute.namespace,
|
|
'%s:name_id' % Volume_mig_status_attribute.alias)
|
|
|
|
|
|
class VolumeMigStatusAttributeTemplate(xmlutil.TemplateBuilder):
|
|
def construct(self):
|
|
root = xmlutil.TemplateElement('volume', selector='volume')
|
|
make_volume(root)
|
|
alias = Volume_mig_status_attribute.alias
|
|
namespace = Volume_mig_status_attribute.namespace
|
|
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|
|
|
|
|
|
class VolumeListMigStatusAttributeTemplate(xmlutil.TemplateBuilder):
|
|
def construct(self):
|
|
root = xmlutil.TemplateElement('volumes')
|
|
elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes')
|
|
make_volume(elem)
|
|
alias = Volume_mig_status_attribute.alias
|
|
namespace = Volume_mig_status_attribute.namespace
|
|
return xmlutil.SlaveTemplate(root, 1, nsmap={alias: namespace})
|