Decouple volumes of aws and os

Add 'volume_base' module which include base volume
manager and base volume attachment manager.

Change-Id: I5c154f6f9fa78c15b39e5327b7e419db4f3d9da4
This commit is contained in:
huangtianhua 2015-05-06 11:03:23 +08:00
parent e294154f17
commit 70014ffe26
3 changed files with 174 additions and 149 deletions

View File

@ -11,16 +11,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.engine import constraints from heat.engine import constraints
from heat.engine import properties from heat.engine import properties
from heat.engine import resource from heat.engine.resources import volume_base as vb
from heat.engine import scheduler
from heat.engine import volume_tasks as vol_task
class Volume(resource.Resource): class Volume(vb.BaseVolume):
PROPERTIES = ( PROPERTIES = (
AVAILABILITY_ZONE, SIZE, BACKUP_ID, TAGS, AVAILABILITY_ZONE, SIZE, BACKUP_ID, TAGS,
@ -77,14 +74,6 @@ class Volume(resource.Resource):
_volume_creating_status = ['creating', 'restoring-backup'] _volume_creating_status = ['creating', 'restoring-backup']
default_client_name = 'cinder'
def _name(self):
return self.physical_resource_name()
def _description(self):
return self.physical_resource_name()
def _create_arguments(self): def _create_arguments(self):
if self.properties[self.TAGS]: if self.properties[self.TAGS]:
tags = dict((tm[self.TAG_KEY], tm[self.TAG_VALUE]) tags = dict((tm[self.TAG_KEY], tm[self.TAG_VALUE])
@ -99,109 +88,8 @@ class Volume(resource.Resource):
'metadata': tags 'metadata': tags
} }
def _fetch_name_and_description(self, api_version, name=None,
description=None):
if api_version == 1:
return {'display_name': name or self._name(),
'display_description': description or self._description()}
else:
return {'name': name or self._name(),
'description': description or self._description()}
def handle_create(self): class VolumeAttachment(vb.BaseVolumeAttachment):
backup_id = self.properties.get(self.BACKUP_ID)
cinder = self.client()
if backup_id is not None:
vol_id = cinder.restores.restore(backup_id).volume_id
vol = cinder.volumes.get(vol_id)
kwargs = self._fetch_name_and_description(
cinder.volume_api_version)
cinder.volumes.update(vol_id, **kwargs)
else:
kwargs = self._create_arguments()
kwargs.update(self._fetch_name_and_description(
cinder.volume_api_version))
vol = cinder.volumes.create(**kwargs)
self.resource_id_set(vol.id)
return vol.id
def check_create_complete(self, vol_id):
vol = self.client().volumes.get(vol_id)
if vol.status == 'available':
return True
if vol.status in self._volume_creating_status:
return False
if vol.status == 'error':
raise resource.ResourceInError(
resource_status=vol.status)
else:
raise resource.ResourceUnknownStatus(
resource_status=vol.status,
result=_('Volume create failed'))
def handle_check(self):
vol = self.client().volumes.get(self.resource_id)
statuses = ['available', 'in-use']
checks = [
{'attr': 'status', 'expected': statuses, 'current': vol.status},
]
self._verify_check_conditions(checks)
def _backup(self):
cinder = self.client()
backup = cinder.backups.create(self.resource_id)
while backup.status == 'creating':
yield
backup = cinder.backups.get(backup.id)
if backup.status != 'available':
raise resource.ResourceUnknownStatus(
resource_status=backup.status,
result=_('Volume backup failed'))
@scheduler.wrappertask
def _delete(self, backup=False):
if self.resource_id is not None:
cinder = self.client()
try:
vol = cinder.volumes.get(self.resource_id)
if backup:
yield self._backup()
vol = cinder.volumes.get(self.resource_id)
if vol.status == 'in-use':
raise exception.Error(_('Volume in use'))
# if the volume is already in deleting status,
# just wait for the deletion to complete
if vol.status != 'deleting':
cinder.volumes.delete(self.resource_id)
while True:
yield
vol = cinder.volumes.get(self.resource_id)
except Exception as ex:
self.client_plugin().ignore_not_found(ex)
def handle_snapshot_delete(self, state):
backup = state not in ((self.CREATE, self.FAILED),
(self.UPDATE, self.FAILED))
delete_task = scheduler.TaskRunner(self._delete, backup=backup)
delete_task.start()
return delete_task
def handle_delete(self):
delete_task = scheduler.TaskRunner(self._delete)
delete_task.start()
return delete_task
def check_delete_complete(self, delete_task):
return delete_task.step()
class VolumeAttachment(resource.Resource):
PROPERTIES = ( PROPERTIES = (
INSTANCE_ID, VOLUME_ID, DEVICE, INSTANCE_ID, VOLUME_ID, DEVICE,
) = ( ) = (
@ -240,35 +128,6 @@ class VolumeAttachment(resource.Resource):
), ),
} }
def handle_create(self):
server_id = self.properties[self.INSTANCE_ID]
volume_id = self.properties[self.VOLUME_ID]
dev = self.properties[self.DEVICE]
attach_task = vol_task.VolumeAttachTask(
self.stack, server_id, volume_id, dev)
attach_runner = scheduler.TaskRunner(attach_task)
attach_runner.start()
self.resource_id_set(attach_task.attachment_id)
return attach_runner
def check_create_complete(self, attach_runner):
return attach_runner.step()
def handle_delete(self):
server_id = self.properties[self.INSTANCE_ID]
detach_task = vol_task.VolumeDetachTask(
self.stack, server_id, self.resource_id)
detach_runner = scheduler.TaskRunner(detach_task)
detach_runner.start()
return detach_runner
def check_delete_complete(self, detach_runner):
return detach_runner.step()
def resource_mapping(): def resource_mapping():
return { return {

View File

@ -21,7 +21,7 @@ from heat.common.i18n import _LI
from heat.engine import attributes from heat.engine import attributes
from heat.engine import constraints from heat.engine import constraints
from heat.engine import properties from heat.engine import properties
from heat.engine.resources.aws.ec2 import volume as aws_vol from heat.engine.resources import volume_base as vb
from heat.engine import scheduler from heat.engine import scheduler
from heat.engine import support from heat.engine import support
from heat.engine import volume_tasks as vol_task from heat.engine import volume_tasks as vol_task
@ -29,7 +29,7 @@ from heat.engine import volume_tasks as vol_task
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class CinderVolume(aws_vol.Volume): class CinderVolume(vb.BaseVolume):
PROPERTIES = ( PROPERTIES = (
AVAILABILITY_ZONE, SIZE, SNAPSHOT_ID, BACKUP_ID, NAME, AVAILABILITY_ZONE, SIZE, SNAPSHOT_ID, BACKUP_ID, NAME,
@ -180,8 +180,6 @@ class CinderVolume(aws_vol.Volume):
_volume_creating_status = ['creating', 'restoring-backup', 'downloading'] _volume_creating_status = ['creating', 'restoring-backup', 'downloading']
default_client_name = 'cinder'
def _name(self): def _name(self):
name = self.properties[self.NAME] name = self.properties[self.NAME]
if name: if name:
@ -361,7 +359,7 @@ class CinderVolume(aws_vol.Volume):
return defn.freeze(properties=props) return defn.freeze(properties=props)
class CinderVolumeAttachment(aws_vol.VolumeAttachment): class CinderVolumeAttachment(vb.BaseVolumeAttachment):
PROPERTIES = ( PROPERTIES = (
INSTANCE_ID, VOLUME_ID, DEVICE, INSTANCE_ID, VOLUME_ID, DEVICE,

View File

@ -0,0 +1,168 @@
#
# 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 heat.common import exception
from heat.common.i18n import _
from heat.engine import resource
from heat.engine import scheduler
from heat.engine import volume_tasks as vol_task
class BaseVolume(resource.Resource):
'''
Base Volume Manager.
'''
default_client_name = 'cinder'
def handle_create(self):
backup_id = self.properties.get(self.BACKUP_ID)
cinder = self.client()
if backup_id is not None:
vol_id = cinder.restores.restore(backup_id).volume_id
vol = cinder.volumes.get(vol_id)
kwargs = self._fetch_name_and_description(
cinder.volume_api_version)
cinder.volumes.update(vol_id, **kwargs)
else:
kwargs = self._create_arguments()
kwargs.update(self._fetch_name_and_description(
cinder.volume_api_version))
vol = cinder.volumes.create(**kwargs)
self.resource_id_set(vol.id)
return vol.id
def check_create_complete(self, vol_id):
vol = self.client().volumes.get(vol_id)
if vol.status == 'available':
return True
if vol.status in self._volume_creating_status:
return False
if vol.status == 'error':
raise resource.ResourceInError(
resource_status=vol.status)
else:
raise resource.ResourceUnknownStatus(
resource_status=vol.status,
result=_('Volume create failed'))
def _name(self):
return self.physical_resource_name()
def _description(self):
return self.physical_resource_name()
def _fetch_name_and_description(self, api_version, name=None,
description=None):
if api_version == 1:
return {'display_name': name or self._name(),
'display_description': description or self._description()}
else:
return {'name': name or self._name(),
'description': description or self._description()}
def handle_check(self):
vol = self.client().volumes.get(self.resource_id)
statuses = ['available', 'in-use']
checks = [
{'attr': 'status', 'expected': statuses, 'current': vol.status},
]
self._verify_check_conditions(checks)
def _backup(self):
cinder = self.client()
backup = cinder.backups.create(self.resource_id)
while backup.status == 'creating':
yield
backup = cinder.backups.get(backup.id)
if backup.status != 'available':
raise resource.ResourceUnknownStatus(
resource_status=backup.status,
result=_('Volume backup failed'))
@scheduler.wrappertask
def _delete(self, backup=False):
if self.resource_id is not None:
cinder = self.client()
try:
vol = cinder.volumes.get(self.resource_id)
if backup:
yield self._backup()
vol = cinder.volumes.get(self.resource_id)
if vol.status == 'in-use':
raise exception.Error(_('Volume in use'))
# if the volume is already in deleting status,
# just wait for the deletion to complete
if vol.status != 'deleting':
cinder.volumes.delete(self.resource_id)
while True:
yield
vol = cinder.volumes.get(self.resource_id)
except Exception as ex:
self.client_plugin().ignore_not_found(ex)
def handle_snapshot_delete(self, state):
backup = state not in ((self.CREATE, self.FAILED),
(self.UPDATE, self.FAILED))
delete_task = scheduler.TaskRunner(self._delete, backup=backup)
delete_task.start()
return delete_task
def handle_delete(self):
delete_task = scheduler.TaskRunner(self._delete)
delete_task.start()
return delete_task
def check_delete_complete(self, delete_task):
return delete_task.step()
class BaseVolumeAttachment(resource.Resource):
'''
Base Volume Attachment Manager.
'''
def handle_create(self):
server_id = self.properties[self.INSTANCE_ID]
volume_id = self.properties[self.VOLUME_ID]
dev = self.properties[self.DEVICE]
attach_task = vol_task.VolumeAttachTask(
self.stack, server_id, volume_id, dev)
attach_runner = scheduler.TaskRunner(attach_task)
attach_runner.start()
self.resource_id_set(attach_task.attachment_id)
return attach_runner
def check_create_complete(self, attach_runner):
return attach_runner.step()
def handle_delete(self):
server_id = self.properties[self.INSTANCE_ID]
detach_task = vol_task.VolumeDetachTask(
self.stack, server_id, self.resource_id)
detach_runner = scheduler.TaskRunner(detach_task)
detach_runner.start()
return detach_runner
def check_delete_complete(self, detach_runner):
return detach_runner.step()