Merge "Support Snapshot policy in volumes"

This commit is contained in:
Jenkins 2013-05-07 07:11:19 +00:00 committed by Gerrit Code Review
commit f333d77084
4 changed files with 177 additions and 28 deletions

View File

@ -403,18 +403,16 @@ class Resource(object):
self.validate_deletion_policy(self.t)
return self.properties.validate()
@staticmethod
def validate_deletion_policy(template):
@classmethod
def validate_deletion_policy(cls, template):
deletion_policy = template.get('DeletionPolicy', 'Delete')
if deletion_policy not in ('Delete', 'Retain', 'Snapshot'):
msg = 'Invalid DeletionPolicy %s' % deletion_policy
raise exception.StackValidationFailed(message=msg)
elif deletion_policy == 'Snapshot':
# Some resources will support it in the future, in which case we
# should check for the presence of a handle_snapshot method for
# example.
msg = 'Snapshot DeletionPolicy not supported'
raise exception.StackValidationFailed(message=msg)
if not callable(getattr(cls, 'handle_snapshot', None)):
msg = 'Snapshot DeletionPolicy not supported'
raise exception.StackValidationFailed(message=msg)
def delete(self):
'''
@ -434,9 +432,13 @@ class Resource(object):
try:
self.state_set(self.DELETE_IN_PROGRESS)
if self.t.get('DeletionPolicy', 'Delete') == 'Delete':
deletion_policy = self.t.get('DeletionPolicy', 'Delete')
if deletion_policy == 'Delete':
if callable(getattr(self, 'handle_delete', None)):
self.handle_delete()
elif deletion_policy == 'Snapshot':
if callable(getattr(self, 'handle_snapshot', None)):
self.handle_snapshot()
except Exception as ex:
logger.exception('Delete %s', str(self))
failure = exception.ResourceFailure(ex)

View File

@ -15,15 +15,19 @@
import eventlet
from heat.openstack.common import log as logging
from heat.openstack.common.importutils import try_import
from heat.common import exception
from heat.engine import clients
from heat.engine import resource
volume_backups = try_import('cinderclient.v1.volume_backups')
logger = logging.getLogger(__name__)
class Volume(resource.Resource):
properties_schema = {'AvailabilityZone': {'Type': 'String',
'Required': True},
'Size': {'Type': 'Number'},
@ -50,6 +54,18 @@ class Volume(resource.Resource):
def handle_update(self, json_snippet):
return self.UPDATE_REPLACE
if volume_backups is not None:
def handle_snapshot(self):
if self.resource_id is not None:
# We use backups as snapshots are not independent of volumes
backup = self.cinder().backups.create(self.resource_id)
while backup.status == 'creating':
eventlet.sleep(1)
backup.get()
if backup.status != 'available':
raise exception.Error(backup.status)
self.handle_delete()
def handle_delete(self):
if self.resource_id is not None:
try:

View File

@ -323,6 +323,23 @@ test_template_snapshot_deletion_policy = '''
}
'''
test_template_volume_snapshot = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "test.",
"Resources" : {
"DataVolume" : {
"Type" : "AWS::EC2::Volume",
"DeletionPolicy": "Snapshot",
"Properties" : {
"Size" : "6",
"AvailabilityZone" : "nova"
}
}
}
}
'''
class validateTest(HeatTestCase):
def setUp(self):
@ -457,3 +474,13 @@ class validateTest(HeatTestCase):
res = dict(engine.validate_template(None, t))
self.assertEqual(
res, {'Error': 'Snapshot DeletionPolicy not supported'})
def test_volume_snapshot_deletion_policy(self):
t = template_format.parse(test_template_volume_snapshot)
self.m.StubOutWithMock(instances.Instance, 'nova')
instances.Instance.nova().AndReturn(self.fc)
self.m.ReplayAll()
engine = service.EngineService('a', 't')
res = dict(engine.validate_template(None, t))
self.assertEqual(res, {'Description': u'test.', 'Parameters': {}})

View File

@ -24,20 +24,27 @@ from heat.engine import parser
from heat.engine import scheduler
from heat.engine.resources import volume as vol
from heat.engine import clients
from heat.openstack.common.importutils import try_import
from heat.tests.common import HeatTestCase
from heat.tests.v1_1 import fakes
from heat.tests.utils import setup_dummy_db
from heat.tests.utils import setup_dummy_db, skip_if
from cinderclient.v1 import client as cinderclient
volume_backups = try_import('cinderclient.v1.volume_backups')
class VolumeTest(HeatTestCase):
def setUp(self):
super(VolumeTest, self).setUp()
self.fc = fakes.FakeClient()
self.cinder_fc = cinderclient.Client('username', 'password')
self.m.StubOutWithMock(clients.OpenStackClients, 'cinder')
self.m.StubOutWithMock(clients.OpenStackClients, 'nova')
self.m.StubOutWithMock(self.fc.volumes, 'create')
self.m.StubOutWithMock(self.fc.volumes, 'get')
self.m.StubOutWithMock(self.fc.volumes, 'delete')
self.m.StubOutWithMock(self.cinder_fc.volumes, 'create')
self.m.StubOutWithMock(self.cinder_fc.volumes, 'get')
self.m.StubOutWithMock(self.cinder_fc.volumes, 'delete')
self.m.StubOutWithMock(self.fc.volumes, 'create_server_volume')
self.m.StubOutWithMock(self.fc.volumes, 'delete_server_volume')
self.m.StubOutWithMock(eventlet, 'sleep')
@ -86,19 +93,20 @@ class VolumeTest(HeatTestCase):
stack_name = 'test_volume_stack'
# create script
clients.OpenStackClients.cinder().MultipleTimes().AndReturn(self.fc)
self.fc.volumes.create(
clients.OpenStackClients.cinder().MultipleTimes().AndReturn(
self.cinder_fc)
self.cinder_fc.volumes.create(
u'1', display_description='%s.DataVolume' % stack_name,
display_name='%s.DataVolume' % stack_name).AndReturn(fv)
# delete script
self.fc.volumes.get('vol-123').AndReturn(fv)
self.cinder_fc.volumes.get('vol-123').AndReturn(fv)
eventlet.sleep(1).AndReturn(None)
self.fc.volumes.get('vol-123').AndReturn(fv)
self.fc.volumes.delete('vol-123').AndReturn(None)
self.cinder_fc.volumes.get('vol-123').AndReturn(fv)
self.cinder_fc.volumes.delete('vol-123').AndReturn(None)
self.fc.volumes.get('vol-123').AndRaise(
self.cinder_fc.volumes.get('vol-123').AndRaise(
clients.cinder_exceptions.NotFound('Not found'))
self.m.ReplayAll()
@ -126,8 +134,8 @@ class VolumeTest(HeatTestCase):
stack_name = 'test_volume_create_error_stack'
# create script
clients.OpenStackClients.cinder().AndReturn(self.fc)
self.fc.volumes.create(
clients.OpenStackClients.cinder().AndReturn(self.cinder_fc)
self.cinder_fc.volumes.create(
u'1', display_description='%s.DataVolume' % stack_name,
display_name='%s.DataVolume' % stack_name).AndReturn(fv)
@ -152,14 +160,14 @@ class VolumeTest(HeatTestCase):
stack_name = 'test_volume_attach_error_stack'
# volume create
clients.OpenStackClients.cinder().MultipleTimes().AndReturn(self.fc)
self.fc.volumes.create(
clients.OpenStackClients.cinder().MultipleTimes().AndReturn(
self.cinder_fc)
self.cinder_fc.volumes.create(
u'1', display_description='%s.DataVolume' % stack_name,
display_name='%s.DataVolume' % stack_name).AndReturn(fv)
# create script
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
# clients.OpenStackClients.cinder().MultipleTimes().AndReturn(self.fc)
eventlet.sleep(1).MultipleTimes().AndReturn(None)
self.fc.volumes.create_server_volume(
@ -167,7 +175,7 @@ class VolumeTest(HeatTestCase):
server_id=u'WikiDatabase',
volume_id=u'vol-123').AndReturn(fva)
self.fc.volumes.get('vol-123').AndReturn(fva)
self.cinder_fc.volumes.get('vol-123').AndReturn(fva)
self.m.ReplayAll()
@ -190,8 +198,9 @@ class VolumeTest(HeatTestCase):
stack_name = 'test_volume_attach_stack'
# volume create
clients.OpenStackClients.cinder().MultipleTimes().AndReturn(self.fc)
self.fc.volumes.create(
clients.OpenStackClients.cinder().MultipleTimes().AndReturn(
self.cinder_fc)
self.cinder_fc.volumes.create(
u'1', display_description='%s.DataVolume' % stack_name,
display_name='%s.DataVolume' % stack_name).AndReturn(fv)
@ -204,13 +213,13 @@ class VolumeTest(HeatTestCase):
server_id=u'WikiDatabase',
volume_id=u'vol-123').AndReturn(fva)
self.fc.volumes.get('vol-123').AndReturn(fva)
self.cinder_fc.volumes.get('vol-123').AndReturn(fva)
# delete script
fva = FakeVolume('in-use', 'available')
self.fc.volumes.delete_server_volume('WikiDatabase',
'vol-123').AndReturn(None)
self.fc.volumes.get('vol-123').AndReturn(fva)
self.cinder_fc.volumes.get('vol-123').AndReturn(fva)
self.m.ReplayAll()
@ -227,6 +236,96 @@ class VolumeTest(HeatTestCase):
self.m.VerifyAll()
@skip_if(volume_backups is None, 'unable to import volume_backups')
def test_snapshot(self):
stack_name = 'test_volume_stack'
fv = FakeVolume('creating', 'available')
fb = FakeBackup('creating', 'available')
# create script
clients.OpenStackClients.cinder().MultipleTimes().AndReturn(
self.cinder_fc)
self.cinder_fc.volumes.create(
u'1', display_description='%s.DataVolume' % stack_name,
display_name='%s.DataVolume' % stack_name).AndReturn(fv)
eventlet.sleep(1).AndReturn(None)
# snapshot script
self.m.StubOutWithMock(self.cinder_fc.backups, 'create')
self.cinder_fc.backups.create('vol-123').AndReturn(fb)
eventlet.sleep(1).AndReturn(None)
self.cinder_fc.volumes.get('vol-123').AndReturn(fv)
self.cinder_fc.volumes.delete('vol-123').AndReturn(None)
self.m.ReplayAll()
t = self.load_template()
t['Resources']['DataVolume']['DeletionPolicy'] = 'Snapshot'
stack = self.parse_stack(t, stack_name)
resource = self.create_volume(t, stack, 'DataVolume')
self.assertEqual(resource.destroy(), None)
self.m.VerifyAll()
@skip_if(volume_backups is None, 'unable to import volume_backups')
def test_snapshot_error(self):
stack_name = 'test_volume_stack'
fv = FakeVolume('creating', 'available')
fb = FakeBackup('creating', 'error')
# create script
clients.OpenStackClients.cinder().MultipleTimes().AndReturn(
self.cinder_fc)
self.cinder_fc.volumes.create(
u'1', display_description='%s.DataVolume' % stack_name,
display_name='%s.DataVolume' % stack_name).AndReturn(fv)
eventlet.sleep(1).AndReturn(None)
# snapshot script
self.m.StubOutWithMock(self.cinder_fc.backups, 'create')
self.cinder_fc.backups.create('vol-123').AndReturn(fb)
eventlet.sleep(1).AndReturn(None)
self.m.ReplayAll()
t = self.load_template()
t['Resources']['DataVolume']['DeletionPolicy'] = 'Snapshot'
stack = self.parse_stack(t, stack_name)
resource = self.create_volume(t, stack, 'DataVolume')
self.assertRaises(exception.ResourceFailure, resource.destroy)
self.m.VerifyAll()
def test_snapshot_no_volume(self):
stack_name = 'test_volume_stack'
fv = FakeVolume('creating', 'error')
# create script
clients.OpenStackClients.cinder().MultipleTimes().AndReturn(
self.cinder_fc)
self.cinder_fc.volumes.create(
u'1', display_description='%s.DataVolume' % stack_name,
display_name='%s.DataVolume' % stack_name).AndReturn(fv)
eventlet.sleep(1).AndReturn(None)
self.m.ReplayAll()
t = self.load_template()
t['Resources']['DataVolume']['DeletionPolicy'] = 'Snapshot'
stack = self.parse_stack(t, stack_name)
resource = vol.Volume('DataVolume',
t['Resources']['DataVolume'],
stack)
create = scheduler.TaskRunner(resource.create)
self.assertRaises(exception.ResourceFailure, create)
self.assertEqual(resource.destroy(), None)
self.m.VerifyAll()
class FakeVolume:
status = 'attaching'
@ -238,3 +337,8 @@ class FakeVolume:
def get(self):
self.status = self.final_status
class FakeBackup(FakeVolume):
status = 'creating'
id = 'backup-123'