Merge "Add handle_update to VolumeAttachment"
This commit is contained in:
@@ -232,43 +232,50 @@ class VolumeAttachTask(object):
|
|||||||
class VolumeDetachTask(object):
|
class VolumeDetachTask(object):
|
||||||
"""A task for detaching a volume from a Nova server."""
|
"""A task for detaching a volume from a Nova server."""
|
||||||
|
|
||||||
def __init__(self, stack, server_id, volume_id):
|
def __init__(self, stack, server_id, attachment_id):
|
||||||
"""
|
"""
|
||||||
Initialise with the stack (for obtaining the clients), and the IDs of
|
Initialise with the stack (for obtaining the clients), and the IDs of
|
||||||
the server and volume.
|
the server and volume.
|
||||||
"""
|
"""
|
||||||
self.clients = stack.clients
|
self.clients = stack.clients
|
||||||
self.server_id = server_id
|
self.server_id = server_id
|
||||||
self.volume_id = volume_id
|
self.attachment_id = attachment_id
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return a human-readable string description of the task."""
|
"""Return a human-readable string description of the task."""
|
||||||
return 'Detaching Volume %s from Instance %s' % (self.volume_id,
|
return _('Removing attachment %(att)s from Instance %(srv)s') % {
|
||||||
self.server_id)
|
'att': self.attachment_id, 'srv': self.server_id}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""Return a brief string description of the task."""
|
"""Return a brief string description of the task."""
|
||||||
return '%s(%s -/> %s)' % (type(self).__name__,
|
return '%s(%s -/> %s)' % (type(self).__name__,
|
||||||
self.volume_id,
|
self.attachment_id,
|
||||||
self.server_id)
|
self.server_id)
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
"""Return a co-routine which runs the task."""
|
"""Return a co-routine which runs the task."""
|
||||||
logger.debug(str(self))
|
logger.debug(str(self))
|
||||||
|
|
||||||
|
server_api = self.clients.nova().volumes
|
||||||
|
|
||||||
|
# get reference to the volume while it is attached
|
||||||
try:
|
try:
|
||||||
vol = self.clients.cinder().volumes.get(self.volume_id)
|
nova_vol = server_api.get_server_volume(self.server_id,
|
||||||
except clients.cinderclient.exceptions.NotFound:
|
self.attachment_id)
|
||||||
|
vol = self.clients.cinder().volumes.get(nova_vol.id)
|
||||||
|
except (clients.cinderclient.exceptions.NotFound,
|
||||||
|
clients.novaclient.exceptions.BadRequest,
|
||||||
|
clients.novaclient.exceptions.NotFound):
|
||||||
logger.warning(_('%s - volume not found') % str(self))
|
logger.warning(_('%s - volume not found') % str(self))
|
||||||
return
|
return
|
||||||
|
|
||||||
server_api = self.clients.nova().volumes
|
# detach the volume using volume_attachment
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server_api.delete_server_volume(self.server_id, self.volume_id)
|
server_api.delete_server_volume(self.server_id, self.attachment_id)
|
||||||
except (clients.novaclient.exceptions.BadRequest,
|
except (clients.novaclient.exceptions.BadRequest,
|
||||||
clients.novaclient.exceptions.NotFound) as e:
|
clients.novaclient.exceptions.NotFound) as e:
|
||||||
logger.warning('%s - %s' % (str(self), str(e)))
|
logger.warning('%(res)s - %(err)s' % {'res': str(self),
|
||||||
|
'err': str(e)})
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@@ -280,7 +287,7 @@ class VolumeDetachTask(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
server_api.delete_server_volume(self.server_id,
|
server_api.delete_server_volume(self.server_id,
|
||||||
self.volume_id)
|
self.attachment_id)
|
||||||
except (clients.novaclient.exceptions.BadRequest,
|
except (clients.novaclient.exceptions.BadRequest,
|
||||||
clients.novaclient.exceptions.NotFound):
|
clients.novaclient.exceptions.NotFound):
|
||||||
pass
|
pass
|
||||||
@@ -294,6 +301,27 @@ class VolumeDetachTask(object):
|
|||||||
except clients.cinderclient.exceptions.NotFound:
|
except clients.cinderclient.exceptions.NotFound:
|
||||||
logger.warning(_('%s - volume not found') % str(self))
|
logger.warning(_('%s - volume not found') % str(self))
|
||||||
|
|
||||||
|
# The next check is needed for immediate reattachment when updating:
|
||||||
|
# as the volume info is taken from cinder, but the detach
|
||||||
|
# request is sent to nova, there might be some time
|
||||||
|
# between cinder marking volume as 'available' and
|
||||||
|
# nova removing attachment from it's own objects, so we
|
||||||
|
# check that nova already knows that the volume is detached
|
||||||
|
def server_has_attachment(server_id, attachment_id):
|
||||||
|
try:
|
||||||
|
server_api.get_server_volume(server_id, attachment_id)
|
||||||
|
except clients.novaclient.exceptions.NotFound:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
while server_has_attachment(self.server_id, self.attachment_id):
|
||||||
|
logger.info(_("Server %(srv)s still has attachment %(att)s.") %
|
||||||
|
{'att': self.attachment_id, 'srv': self.server_id})
|
||||||
|
yield
|
||||||
|
|
||||||
|
logger.info(_("Volume %(vol)s is detached from server %(srv)s") %
|
||||||
|
{'vol': vol.id, 'srv': self.server_id})
|
||||||
|
|
||||||
|
|
||||||
class VolumeAttachment(resource.Resource):
|
class VolumeAttachment(resource.Resource):
|
||||||
PROPERTIES = (
|
PROPERTIES = (
|
||||||
@@ -306,12 +334,14 @@ class VolumeAttachment(resource.Resource):
|
|||||||
INSTANCE_ID: properties.Schema(
|
INSTANCE_ID: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('The ID of the instance to which the volume attaches.'),
|
_('The ID of the instance to which the volume attaches.'),
|
||||||
required=True
|
required=True,
|
||||||
|
update_allowed=True
|
||||||
),
|
),
|
||||||
VOLUME_ID: properties.Schema(
|
VOLUME_ID: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('The ID of the volume to be attached.'),
|
_('The ID of the volume to be attached.'),
|
||||||
required=True
|
required=True,
|
||||||
|
update_allowed=True
|
||||||
),
|
),
|
||||||
DEVICE: properties.Schema(
|
DEVICE: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
@@ -319,12 +349,15 @@ class VolumeAttachment(resource.Resource):
|
|||||||
'assignment may not be honored and it is advised that the path '
|
'assignment may not be honored and it is advised that the path '
|
||||||
'/dev/disk/by-id/virtio-<VolumeId> be used instead.'),
|
'/dev/disk/by-id/virtio-<VolumeId> be used instead.'),
|
||||||
required=True,
|
required=True,
|
||||||
|
update_allowed=True,
|
||||||
constraints=[
|
constraints=[
|
||||||
constraints.AllowedPattern('/dev/vd[b-z]'),
|
constraints.AllowedPattern('/dev/vd[b-z]'),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_allowed_keys = ('Properties',)
|
||||||
|
|
||||||
def handle_create(self):
|
def handle_create(self):
|
||||||
server_id = self.properties[self.INSTANCE_ID]
|
server_id = self.properties[self.INSTANCE_ID]
|
||||||
volume_id = self.properties[self.VOLUME_ID]
|
volume_id = self.properties[self.VOLUME_ID]
|
||||||
@@ -344,10 +377,49 @@ class VolumeAttachment(resource.Resource):
|
|||||||
|
|
||||||
def handle_delete(self):
|
def handle_delete(self):
|
||||||
server_id = self.properties[self.INSTANCE_ID]
|
server_id = self.properties[self.INSTANCE_ID]
|
||||||
volume_id = self.properties[self.VOLUME_ID]
|
detach_task = VolumeDetachTask(self.stack, server_id, self.resource_id)
|
||||||
detach_task = VolumeDetachTask(self.stack, server_id, volume_id)
|
|
||||||
scheduler.TaskRunner(detach_task)()
|
scheduler.TaskRunner(detach_task)()
|
||||||
|
|
||||||
|
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||||
|
checkers = []
|
||||||
|
if prop_diff:
|
||||||
|
# Even though some combinations of changed properties
|
||||||
|
# could be updated in UpdateReplace manner,
|
||||||
|
# we still first detach the old resource so that
|
||||||
|
# self.resource_id is not replaced prematurely
|
||||||
|
volume_id = self.properties.get(self.VOLUME_ID)
|
||||||
|
if self.VOLUME_ID in prop_diff:
|
||||||
|
volume_id = prop_diff.get(self.VOLUME_ID)
|
||||||
|
|
||||||
|
device = self.properties.get(self.DEVICE)
|
||||||
|
if self.DEVICE in prop_diff:
|
||||||
|
device = prop_diff.get(self.DEVICE)
|
||||||
|
|
||||||
|
server_id = self.properties.get(self.INSTANCE_ID)
|
||||||
|
detach_task = VolumeDetachTask(self.stack, server_id,
|
||||||
|
self.resource_id)
|
||||||
|
checkers.append(scheduler.TaskRunner(detach_task))
|
||||||
|
|
||||||
|
if self.INSTANCE_ID in prop_diff:
|
||||||
|
server_id = prop_diff.get(self.INSTANCE_ID)
|
||||||
|
attach_task = VolumeAttachTask(self.stack, server_id,
|
||||||
|
volume_id, device)
|
||||||
|
|
||||||
|
checkers.append(scheduler.TaskRunner(attach_task))
|
||||||
|
|
||||||
|
if checkers:
|
||||||
|
checkers[0].start()
|
||||||
|
return checkers
|
||||||
|
|
||||||
|
def check_update_complete(self, checkers):
|
||||||
|
for checker in checkers:
|
||||||
|
if not checker.started():
|
||||||
|
checker.start()
|
||||||
|
if not checker.step():
|
||||||
|
return False
|
||||||
|
self.resource_id_set(checkers[-1]._task.attachment_id)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class CinderVolume(Volume):
|
class CinderVolume(Volume):
|
||||||
|
|
||||||
@@ -483,18 +555,21 @@ class CinderVolumeAttachment(VolumeAttachment):
|
|||||||
INSTANCE_ID: properties.Schema(
|
INSTANCE_ID: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('The ID of the server to which the volume attaches.'),
|
_('The ID of the server to which the volume attaches.'),
|
||||||
required=True
|
required=True,
|
||||||
|
update_allowed=True
|
||||||
),
|
),
|
||||||
VOLUME_ID: properties.Schema(
|
VOLUME_ID: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('The ID of the volume to be attached.'),
|
_('The ID of the volume to be attached.'),
|
||||||
required=True
|
required=True,
|
||||||
|
update_allowed=True
|
||||||
),
|
),
|
||||||
DEVICE: properties.Schema(
|
DEVICE: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('The location where the volume is exposed on the instance. This '
|
_('The location where the volume is exposed on the instance. This '
|
||||||
'assignment may not be honored and it is advised that the path '
|
'assignment may not be honored and it is advised that the path '
|
||||||
'/dev/disk/by-id/virtio-<VolumeId> be used instead.')
|
'/dev/disk/by-id/virtio-<VolumeId> be used instead.'),
|
||||||
|
update_allowed=True
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from cinderclient.v1 import client as cinderclient
|
from cinderclient.v1 import client as cinderclient
|
||||||
@@ -59,6 +59,15 @@ volume_template = '''
|
|||||||
"Tags" : [{ "Key" : "Usage", "Value" : "Wiki Data Volume" }]
|
"Tags" : [{ "Key" : "Usage", "Value" : "Wiki Data Volume" }]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"DataVolume2" : {
|
||||||
|
"Type" : "AWS::EC2::Volume",
|
||||||
|
"Properties" : {
|
||||||
|
"Size" : "2",
|
||||||
|
"AvailabilityZone" : {"Fn::GetAtt": ["WikiDatabase",
|
||||||
|
"AvailabilityZone"]},
|
||||||
|
"Tags" : [{ "Key" : "Usage", "Value" : "Wiki Data Volume2" }]
|
||||||
|
}
|
||||||
|
},
|
||||||
"MountPoint" : {
|
"MountPoint" : {
|
||||||
"Type" : "AWS::EC2::VolumeAttachment",
|
"Type" : "AWS::EC2::VolumeAttachment",
|
||||||
"Properties" : {
|
"Properties" : {
|
||||||
@@ -84,6 +93,7 @@ class VolumeTest(HeatTestCase):
|
|||||||
self.m.StubOutWithMock(self.cinder_fc.volumes, 'delete')
|
self.m.StubOutWithMock(self.cinder_fc.volumes, 'delete')
|
||||||
self.m.StubOutWithMock(self.fc.volumes, 'create_server_volume')
|
self.m.StubOutWithMock(self.fc.volumes, 'create_server_volume')
|
||||||
self.m.StubOutWithMock(self.fc.volumes, 'delete_server_volume')
|
self.m.StubOutWithMock(self.fc.volumes, 'delete_server_volume')
|
||||||
|
self.m.StubOutWithMock(self.fc.volumes, 'get_server_volume')
|
||||||
self.m.StubOutWithMock(nova_utils, 'get_image_id')
|
self.m.StubOutWithMock(nova_utils, 'get_image_id')
|
||||||
utils.setup_dummy_db()
|
utils.setup_dummy_db()
|
||||||
|
|
||||||
@@ -124,12 +134,16 @@ class VolumeTest(HeatTestCase):
|
|||||||
clients.cinderclient.exceptions.NotFound('Not found'))
|
clients.cinderclient.exceptions.NotFound('Not found'))
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
def _mock_create_server_volume_script(self, fva):
|
def _mock_create_server_volume_script(self, fva,
|
||||||
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
server=u'WikiDatabase',
|
||||||
|
volume='vol-123',
|
||||||
|
device=u'/dev/vdc',
|
||||||
|
update=False):
|
||||||
|
if not update:
|
||||||
|
clients.OpenStackClients.nova().MultipleTimes().AndReturn(self.fc)
|
||||||
self.fc.volumes.create_server_volume(
|
self.fc.volumes.create_server_volume(
|
||||||
device=u'/dev/vdc', server_id=u'WikiDatabase',
|
device=device, server_id=server, volume_id=volume).AndReturn(fva)
|
||||||
volume_id=u'vol-123').AndReturn(fva)
|
self.cinder_fc.volumes.get(volume).AndReturn(fva)
|
||||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fva)
|
|
||||||
|
|
||||||
def test_volume(self):
|
def test_volume(self):
|
||||||
fv = FakeVolume('creating', 'available')
|
fv = FakeVolume('creating', 'available')
|
||||||
@@ -203,6 +217,7 @@ class VolumeTest(HeatTestCase):
|
|||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
t = template_format.parse(volume_template)
|
t = template_format.parse(volume_template)
|
||||||
|
t['Resources'].pop('DataVolume2')
|
||||||
stack = utils.parse_stack(t, stack_name=stack_name)
|
stack = utils.parse_stack(t, stack_name=stack_name)
|
||||||
|
|
||||||
rsrc = stack['DataVolume']
|
rsrc = stack['DataVolume']
|
||||||
@@ -282,9 +297,16 @@ class VolumeTest(HeatTestCase):
|
|||||||
|
|
||||||
# delete script
|
# delete script
|
||||||
fva = FakeVolume('in-use', 'available')
|
fva = FakeVolume('in-use', 'available')
|
||||||
self.fc.volumes.delete_server_volume('WikiDatabase',
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
'vol-123').AndReturn(None)
|
'vol-123').AndReturn(fva)
|
||||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fva)
|
self.cinder_fc.volumes.get(fva.id).AndReturn(fva)
|
||||||
|
self.fc.volumes.delete_server_volume(
|
||||||
|
'WikiDatabase', 'vol-123').MultipleTimes().AndReturn(None)
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.fc.volumes.get_server_volume(
|
||||||
|
u'WikiDatabase', 'vol-123').AndRaise(
|
||||||
|
clients.novaclient.exceptions.NotFound('NotFound'))
|
||||||
|
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
@@ -296,9 +318,6 @@ class VolumeTest(HeatTestCase):
|
|||||||
self.assertEqual('available', fv.status)
|
self.assertEqual('available', fv.status)
|
||||||
rsrc = self.create_attachment(t, stack, 'MountPoint')
|
rsrc = self.create_attachment(t, stack, 'MountPoint')
|
||||||
|
|
||||||
self.assertRaises(resource.UpdateReplace,
|
|
||||||
rsrc.handle_update, {}, {}, {})
|
|
||||||
|
|
||||||
scheduler.TaskRunner(rsrc.delete)()
|
scheduler.TaskRunner(rsrc.delete)()
|
||||||
|
|
||||||
self.m.VerifyAll()
|
self.m.VerifyAll()
|
||||||
@@ -318,7 +337,9 @@ class VolumeTest(HeatTestCase):
|
|||||||
fva.get().MultipleTimes()
|
fva.get().MultipleTimes()
|
||||||
fva.status = "in-use"
|
fva.status = "in-use"
|
||||||
|
|
||||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fva)
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.cinder_fc.volumes.get(fva.id).AndReturn(fva)
|
||||||
|
|
||||||
self.fc.volumes.delete_server_volume(
|
self.fc.volumes.delete_server_volume(
|
||||||
'WikiDatabase', 'vol-123').AndRaise(
|
'WikiDatabase', 'vol-123').AndRaise(
|
||||||
@@ -336,6 +357,11 @@ class VolumeTest(HeatTestCase):
|
|||||||
'WikiDatabase', 'vol-123').AndRaise(
|
'WikiDatabase', 'vol-123').AndRaise(
|
||||||
clients.cinderclient.exceptions.NotFound('Not found'))
|
clients.cinderclient.exceptions.NotFound('Not found'))
|
||||||
|
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.fc.volumes.get_server_volume(
|
||||||
|
u'WikiDatabase', 'vol-123').AndRaise(
|
||||||
|
clients.novaclient.exceptions.NotFound('NotFound'))
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
t = template_format.parse(volume_template)
|
t = template_format.parse(volume_template)
|
||||||
@@ -346,9 +372,6 @@ class VolumeTest(HeatTestCase):
|
|||||||
self.assertEqual('available', fv.status)
|
self.assertEqual('available', fv.status)
|
||||||
rsrc = self.create_attachment(t, stack, 'MountPoint')
|
rsrc = self.create_attachment(t, stack, 'MountPoint')
|
||||||
|
|
||||||
self.assertRaises(resource.UpdateReplace,
|
|
||||||
rsrc.handle_update, {}, {}, {})
|
|
||||||
|
|
||||||
scheduler.TaskRunner(rsrc.delete)()
|
scheduler.TaskRunner(rsrc.delete)()
|
||||||
|
|
||||||
self.m.VerifyAll()
|
self.m.VerifyAll()
|
||||||
@@ -363,7 +386,9 @@ class VolumeTest(HeatTestCase):
|
|||||||
self._mock_create_server_volume_script(fva)
|
self._mock_create_server_volume_script(fva)
|
||||||
|
|
||||||
# delete script
|
# delete script
|
||||||
self.cinder_fc.volumes.get('vol-123').AndRaise(
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.cinder_fc.volumes.get(fva.id).AndRaise(
|
||||||
clients.cinderclient.exceptions.NotFound('Not found'))
|
clients.cinderclient.exceptions.NotFound('Not found'))
|
||||||
|
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
@@ -391,9 +416,16 @@ class VolumeTest(HeatTestCase):
|
|||||||
# delete script
|
# delete script
|
||||||
volume_detach_cycle = 'in-use', 'detaching', 'available'
|
volume_detach_cycle = 'in-use', 'detaching', 'available'
|
||||||
fva = FakeLatencyVolume(life_cycle=volume_detach_cycle)
|
fva = FakeLatencyVolume(life_cycle=volume_detach_cycle)
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.cinder_fc.volumes.get(fva.id).AndReturn(fva)
|
||||||
self.fc.volumes.delete_server_volume(
|
self.fc.volumes.delete_server_volume(
|
||||||
'WikiDatabase', 'vol-123').MultipleTimes().AndReturn(None)
|
'WikiDatabase', 'vol-123').MultipleTimes().AndReturn(None)
|
||||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fva)
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.fc.volumes.get_server_volume(
|
||||||
|
u'WikiDatabase', 'vol-123').AndRaise(
|
||||||
|
clients.novaclient.exceptions.NotFound('NotFound'))
|
||||||
|
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
@@ -420,10 +452,11 @@ class VolumeTest(HeatTestCase):
|
|||||||
|
|
||||||
# delete script
|
# delete script
|
||||||
fva = FakeVolume('in-use', 'error')
|
fva = FakeVolume('in-use', 'error')
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.cinder_fc.volumes.get(fva.id).AndReturn(fva)
|
||||||
self.fc.volumes.delete_server_volume('WikiDatabase',
|
self.fc.volumes.delete_server_volume('WikiDatabase',
|
||||||
'vol-123').AndReturn(None)
|
'vol-123').AndReturn(None)
|
||||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fva)
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
t = template_format.parse(volume_template)
|
t = template_format.parse(volume_template)
|
||||||
@@ -461,6 +494,168 @@ class VolumeTest(HeatTestCase):
|
|||||||
|
|
||||||
self.m.VerifyAll()
|
self.m.VerifyAll()
|
||||||
|
|
||||||
|
def test_volume_attachment_update_device(self):
|
||||||
|
fv = FakeVolume('creating', 'available')
|
||||||
|
fva = FakeVolume('attaching', 'in-use')
|
||||||
|
fva2 = FakeVolume('attaching', 'in-use')
|
||||||
|
stack_name = 'test_volume_attach_stack'
|
||||||
|
|
||||||
|
self._mock_create_volume(fv, stack_name)
|
||||||
|
|
||||||
|
self._mock_create_server_volume_script(fva)
|
||||||
|
|
||||||
|
# delete script
|
||||||
|
fva = FakeVolume('in-use', 'available')
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.cinder_fc.volumes.get(fva.id).AndReturn(fva)
|
||||||
|
self.fc.volumes.delete_server_volume(
|
||||||
|
'WikiDatabase', 'vol-123').MultipleTimes().AndReturn(None)
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.fc.volumes.get_server_volume(
|
||||||
|
u'WikiDatabase', 'vol-123').AndRaise(
|
||||||
|
clients.novaclient.exceptions.NotFound('NotFound'))
|
||||||
|
|
||||||
|
# attach script
|
||||||
|
self._mock_create_server_volume_script(fva2, device=u'/dev/vdd',
|
||||||
|
update=True)
|
||||||
|
|
||||||
|
self.m.ReplayAll()
|
||||||
|
|
||||||
|
t = template_format.parse(volume_template)
|
||||||
|
t['Resources']['DataVolume']['Properties']['AvailabilityZone'] = 'nova'
|
||||||
|
stack = utils.parse_stack(t, stack_name=stack_name)
|
||||||
|
|
||||||
|
scheduler.TaskRunner(stack['DataVolume'].create)()
|
||||||
|
self.assertEqual('available', fv.status)
|
||||||
|
|
||||||
|
rsrc = self.create_attachment(t, stack, 'MountPoint')
|
||||||
|
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||||
|
|
||||||
|
after = copy.deepcopy(t)['Resources']['MountPoint']
|
||||||
|
after['Properties']['VolumeId'] = 'vol-123'
|
||||||
|
after['Properties']['InstanceId'] = 'WikiDatabase'
|
||||||
|
after['Properties']['Device'] = '/dev/vdd'
|
||||||
|
scheduler.TaskRunner(rsrc.update, after)()
|
||||||
|
|
||||||
|
self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state)
|
||||||
|
self.m.VerifyAll()
|
||||||
|
|
||||||
|
def test_volume_attachment_update_volume(self):
|
||||||
|
fv = FakeVolume('creating', 'available')
|
||||||
|
fva = FakeVolume('attaching', 'in-use')
|
||||||
|
fv2 = FakeVolume('creating', 'available')
|
||||||
|
fv2.id = 'vol-456'
|
||||||
|
fv2a = FakeVolume('attaching', 'in-use')
|
||||||
|
fv2a.id = 'vol-456'
|
||||||
|
stack_name = 'test_volume_attach_stack'
|
||||||
|
|
||||||
|
self._mock_create_volume(fv, stack_name)
|
||||||
|
|
||||||
|
vol2_name = utils.PhysName(stack_name, 'DataVolume2')
|
||||||
|
self.cinder_fc.volumes.create(
|
||||||
|
size=2, availability_zone='nova',
|
||||||
|
display_description=vol2_name,
|
||||||
|
display_name=vol2_name,
|
||||||
|
metadata={u'Usage': u'Wiki Data Volume2'}).AndReturn(fv2)
|
||||||
|
|
||||||
|
self._mock_create_server_volume_script(fva)
|
||||||
|
|
||||||
|
# delete script
|
||||||
|
fva = FakeVolume('in-use', 'available')
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.cinder_fc.volumes.get(fva.id).AndReturn(fva)
|
||||||
|
self.fc.volumes.delete_server_volume(
|
||||||
|
'WikiDatabase', 'vol-123').MultipleTimes().AndReturn(None)
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.fc.volumes.get_server_volume(
|
||||||
|
u'WikiDatabase', 'vol-123').AndRaise(
|
||||||
|
clients.novaclient.exceptions.NotFound('NotFound'))
|
||||||
|
|
||||||
|
# attach script
|
||||||
|
self._mock_create_server_volume_script(fv2a, volume='vol-456',
|
||||||
|
update=True)
|
||||||
|
#self.fc.volumes.create_server_volume(
|
||||||
|
#device=u'/dev/vdc', server_id=u'WikiDatabase',
|
||||||
|
#volume_id='vol-456').AndReturn(fv2a)
|
||||||
|
#self.cinder_fc.volumes.get('vol-456').AndReturn(fv2a)
|
||||||
|
|
||||||
|
self.m.ReplayAll()
|
||||||
|
|
||||||
|
t = template_format.parse(volume_template)
|
||||||
|
zone = 'nova'
|
||||||
|
t['Resources']['DataVolume']['Properties']['AvailabilityZone'] = zone
|
||||||
|
t['Resources']['DataVolume2']['Properties']['AvailabilityZone'] = zone
|
||||||
|
stack = utils.parse_stack(t, stack_name=stack_name)
|
||||||
|
|
||||||
|
scheduler.TaskRunner(stack['DataVolume'].create)()
|
||||||
|
self.assertEqual('available', fv.status)
|
||||||
|
scheduler.TaskRunner(stack['DataVolume2'].create)()
|
||||||
|
self.assertEqual('available', fv2.status)
|
||||||
|
|
||||||
|
rsrc = self.create_attachment(t, stack, 'MountPoint')
|
||||||
|
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||||
|
|
||||||
|
after = copy.deepcopy(t)['Resources']['MountPoint']
|
||||||
|
after['Properties']['VolumeId'] = 'vol-456'
|
||||||
|
after['Properties']['InstanceId'] = 'WikiDatabase'
|
||||||
|
scheduler.TaskRunner(rsrc.update, after)()
|
||||||
|
|
||||||
|
self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state)
|
||||||
|
self.assertEqual(fv2a.id, rsrc.resource_id)
|
||||||
|
self.m.VerifyAll()
|
||||||
|
|
||||||
|
def test_volume_attachment_update_server(self):
|
||||||
|
fv = FakeVolume('creating', 'available')
|
||||||
|
fva = FakeVolume('attaching', 'in-use')
|
||||||
|
fva2 = FakeVolume('attaching', 'in-use')
|
||||||
|
stack_name = 'test_volume_attach_stack'
|
||||||
|
|
||||||
|
self._mock_create_volume(fv, stack_name)
|
||||||
|
|
||||||
|
self._mock_create_server_volume_script(fva)
|
||||||
|
|
||||||
|
# delete script
|
||||||
|
fva = FakeVolume('in-use', 'available')
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.cinder_fc.volumes.get(fva.id).AndReturn(fva)
|
||||||
|
self.fc.volumes.delete_server_volume(
|
||||||
|
'WikiDatabase', 'vol-123').MultipleTimes().AndReturn(None)
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.fc.volumes.get_server_volume(
|
||||||
|
u'WikiDatabase', 'vol-123').AndRaise(
|
||||||
|
clients.novaclient.exceptions.NotFound('NotFound'))
|
||||||
|
|
||||||
|
# attach script
|
||||||
|
self._mock_create_server_volume_script(fva2, server=u'WikiDatabase2',
|
||||||
|
update=True)
|
||||||
|
|
||||||
|
self.m.ReplayAll()
|
||||||
|
|
||||||
|
t = template_format.parse(volume_template)
|
||||||
|
t['Resources']['DataVolume']['Properties']['AvailabilityZone'] = 'nova'
|
||||||
|
stack = utils.parse_stack(t, stack_name=stack_name)
|
||||||
|
|
||||||
|
scheduler.TaskRunner(stack['DataVolume'].create)()
|
||||||
|
self.assertEqual('available', fv.status)
|
||||||
|
|
||||||
|
rsrc = self.create_attachment(t, stack, 'MountPoint')
|
||||||
|
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||||
|
|
||||||
|
after = copy.deepcopy(t)['Resources']['MountPoint']
|
||||||
|
after['Properties']['VolumeId'] = 'vol-123'
|
||||||
|
after['Properties']['InstanceId'] = 'WikiDatabase2'
|
||||||
|
#after['Properties']['Device'] = '/dev/vdd'
|
||||||
|
scheduler.TaskRunner(rsrc.update, after)()
|
||||||
|
|
||||||
|
self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state)
|
||||||
|
self.m.VerifyAll()
|
||||||
|
|
||||||
@skipIf(volume_backups is None, 'unable to import volume_backups')
|
@skipIf(volume_backups is None, 'unable to import volume_backups')
|
||||||
def test_snapshot(self):
|
def test_snapshot(self):
|
||||||
stack_name = 'test_volume_stack'
|
stack_name = 'test_volume_stack'
|
||||||
@@ -793,9 +988,16 @@ class VolumeTest(HeatTestCase):
|
|||||||
|
|
||||||
# delete script
|
# delete script
|
||||||
fva = FakeVolume('in-use', 'available')
|
fva = FakeVolume('in-use', 'available')
|
||||||
self.fc.volumes.delete_server_volume('WikiDatabase',
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
'vol-123').AndReturn(None)
|
'vol-123').AndReturn(fva)
|
||||||
self.cinder_fc.volumes.get('vol-123').AndReturn(fva)
|
self.cinder_fc.volumes.get(fva.id).AndReturn(fva)
|
||||||
|
self.fc.volumes.delete_server_volume(
|
||||||
|
'WikiDatabase', 'vol-123').MultipleTimes().AndReturn(None)
|
||||||
|
self.fc.volumes.get_server_volume(u'WikiDatabase',
|
||||||
|
'vol-123').AndReturn(fva)
|
||||||
|
self.fc.volumes.get_server_volume(
|
||||||
|
u'WikiDatabase', 'vol-123').AndRaise(
|
||||||
|
clients.novaclient.exceptions.NotFound('NotFound'))
|
||||||
|
|
||||||
self.m.ReplayAll()
|
self.m.ReplayAll()
|
||||||
|
|
||||||
@@ -817,9 +1019,6 @@ class VolumeTest(HeatTestCase):
|
|||||||
scheduler.TaskRunner(rsrc.create)()
|
scheduler.TaskRunner(rsrc.create)()
|
||||||
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||||
|
|
||||||
self.assertRaises(resource.UpdateReplace, rsrc.handle_update,
|
|
||||||
{}, {}, {})
|
|
||||||
|
|
||||||
scheduler.TaskRunner(rsrc.delete)()
|
scheduler.TaskRunner(rsrc.delete)()
|
||||||
|
|
||||||
self.m.VerifyAll()
|
self.m.VerifyAll()
|
||||||
|
|||||||
Reference in New Issue
Block a user