Support multiple stores of Glance
Glance now has ability to configure multiple stores at a time. To use this feature in cinder for uploading volume to image, operator need to define a new field named 'image_service:store_id' in the volume-type extra-specs. At a time of volume upload to image request, if 'image_service:store_id' is present in the associated volume type, then image will be uploaded to specified 'store_id'. The value 'store_id' is nothing but store identifier defined in glance-api.conf. If the value of 'image_service:store_id' is null or not set in volume-type then the image will be uploaded to default store in glance. Co-authored-by: Sagar Waghmare <sawaghma@redhat.com> Co-authored-by: Abhishek Kekane <akekane@redhat.com> DocImpact Implements: bp support-glance-multiple-backend Change-Id: Ica56833a1d9fb9f47b922dbbc6558901bb3a2800
This commit is contained in:
parent
9e3ce1427f
commit
350973f3dd
|
@ -26,6 +26,7 @@ from cinder import context as ctxt
|
||||||
from cinder import db
|
from cinder import db
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
|
from cinder.image import image_utils
|
||||||
from cinder.policies import type_extra_specs as policy
|
from cinder.policies import type_extra_specs as policy
|
||||||
from cinder import rpc
|
from cinder import rpc
|
||||||
from cinder.volume import volume_types
|
from cinder.volume import volume_types
|
||||||
|
@ -71,6 +72,10 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
||||||
self._check_type(context, type_id)
|
self._check_type(context, type_id)
|
||||||
specs = body['extra_specs']
|
specs = body['extra_specs']
|
||||||
|
|
||||||
|
if 'image_service:store_id' in specs:
|
||||||
|
image_service_store_id = specs['image_service:store_id']
|
||||||
|
image_utils.validate_stores_id(context, image_service_store_id)
|
||||||
|
|
||||||
db.volume_type_extra_specs_update_or_create(context,
|
db.volume_type_extra_specs_update_or_create(context,
|
||||||
type_id,
|
type_id,
|
||||||
specs)
|
specs)
|
||||||
|
@ -95,6 +100,10 @@ class VolumeTypeExtraSpecsController(wsgi.Controller):
|
||||||
expl = _('Request body and URI mismatch')
|
expl = _('Request body and URI mismatch')
|
||||||
raise webob.exc.HTTPBadRequest(explanation=expl)
|
raise webob.exc.HTTPBadRequest(explanation=expl)
|
||||||
|
|
||||||
|
if 'image_service:store_id' in body:
|
||||||
|
image_service_store_id = body['image_service:store_id']
|
||||||
|
image_utils.validate_stores_id(context, image_service_store_id)
|
||||||
|
|
||||||
db.volume_type_extra_specs_update_or_create(context,
|
db.volume_type_extra_specs_update_or_create(context,
|
||||||
type_id,
|
type_id,
|
||||||
body)
|
body)
|
||||||
|
|
|
@ -331,6 +331,14 @@ class NotFound(CinderException):
|
||||||
safe = True
|
safe = True
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceStoreNotFound(NotFound):
|
||||||
|
message = _("Store %(store_id)s not enabled in glance.")
|
||||||
|
|
||||||
|
|
||||||
|
class GlanceStoreReadOnly(Invalid):
|
||||||
|
message = _("Store %(store_id)s is read-only in glance.")
|
||||||
|
|
||||||
|
|
||||||
class VolumeNotFound(NotFound):
|
class VolumeNotFound(NotFound):
|
||||||
message = _("Volume %(volume_id)s could not be found.")
|
message = _("Volume %(volume_id)s could not be found.")
|
||||||
|
|
||||||
|
|
|
@ -216,9 +216,15 @@ class GlanceClientWrapper(object):
|
||||||
glanceclient.exc.InvalidEndpoint,
|
glanceclient.exc.InvalidEndpoint,
|
||||||
glanceclient.exc.CommunicationError)
|
glanceclient.exc.CommunicationError)
|
||||||
num_attempts = 1 + CONF.glance_num_retries
|
num_attempts = 1 + CONF.glance_num_retries
|
||||||
|
store_id = kwargs.pop('store_id', None)
|
||||||
|
|
||||||
for attempt in range(1, num_attempts + 1):
|
for attempt in range(1, num_attempts + 1):
|
||||||
client = self.client or self._create_onetime_client(context)
|
client = self.client or self._create_onetime_client(context)
|
||||||
|
if store_id:
|
||||||
|
client.http_client.additional_headers = {
|
||||||
|
'x-image-meta-store': store_id
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
controller = getattr(client,
|
controller = getattr(client,
|
||||||
kwargs.pop('controller', 'images'))
|
kwargs.pop('controller', 'images'))
|
||||||
|
@ -287,6 +293,14 @@ class GlanceImageService(object):
|
||||||
except Exception:
|
except Exception:
|
||||||
_reraise_translated_image_exception(image_id)
|
_reraise_translated_image_exception(image_id)
|
||||||
|
|
||||||
|
def get_stores(self, context):
|
||||||
|
"""Returns a list of dicts with stores information."""
|
||||||
|
try:
|
||||||
|
return self._client.call(context,
|
||||||
|
'get_stores_info')
|
||||||
|
except Exception:
|
||||||
|
_reraise_translated_exception()
|
||||||
|
|
||||||
def show(self, context, image_id):
|
def show(self, context, image_id):
|
||||||
"""Returns a dict with image data for the given opaque image id."""
|
"""Returns a dict with image data for the given opaque image id."""
|
||||||
try:
|
try:
|
||||||
|
@ -380,7 +394,8 @@ class GlanceImageService(object):
|
||||||
return self._translate_from_glance(context, recv_service_image_meta)
|
return self._translate_from_glance(context, recv_service_image_meta)
|
||||||
|
|
||||||
def update(self, context, image_id,
|
def update(self, context, image_id,
|
||||||
image_meta, data=None, purge_props=True):
|
image_meta, data=None, purge_props=True,
|
||||||
|
store_id=None):
|
||||||
"""Modify the given image with the new data."""
|
"""Modify the given image with the new data."""
|
||||||
# For v2, _translate_to_glance stores custom properties in image meta
|
# For v2, _translate_to_glance stores custom properties in image meta
|
||||||
# directly. We need the custom properties to identify properties to
|
# directly. We need the custom properties to identify properties to
|
||||||
|
@ -394,9 +409,13 @@ class GlanceImageService(object):
|
||||||
# NOTE(bcwaldon): id is not an editable field, but it is likely to be
|
# NOTE(bcwaldon): id is not an editable field, but it is likely to be
|
||||||
# passed in by calling code. Let's be nice and ignore it.
|
# passed in by calling code. Let's be nice and ignore it.
|
||||||
image_meta.pop('id', None)
|
image_meta.pop('id', None)
|
||||||
|
kwargs = {}
|
||||||
|
if store_id:
|
||||||
|
kwargs['store_id'] = store_id
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if data:
|
if data:
|
||||||
self._client.call(context, 'upload', image_id, data)
|
self._client.call(context, 'upload', image_id, data, **kwargs)
|
||||||
if image_meta:
|
if image_meta:
|
||||||
if purge_props:
|
if purge_props:
|
||||||
# Properties to remove are those not specified in
|
# Properties to remove are those not specified in
|
||||||
|
|
|
@ -48,6 +48,7 @@ import six
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _
|
from cinder.i18n import _
|
||||||
from cinder.image import accelerator
|
from cinder.image import accelerator
|
||||||
|
from cinder.image import glance
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder.volume import throttling
|
from cinder.volume import throttling
|
||||||
from cinder.volume import volume_utils
|
from cinder.volume import volume_utils
|
||||||
|
@ -88,6 +89,19 @@ QEMU_IMG_MIN_CONVERT_LUKS_VERSION = '2.10'
|
||||||
COMPRESSIBLE_IMAGE_FORMATS = ('qcow2',)
|
COMPRESSIBLE_IMAGE_FORMATS = ('qcow2',)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_stores_id(context, image_service_store_id):
|
||||||
|
image_service = glance.get_default_image_service()
|
||||||
|
stores_info = image_service.get_stores(context)['stores']
|
||||||
|
for info in stores_info:
|
||||||
|
if image_service_store_id == info['id']:
|
||||||
|
if info.get('read-only') == "true":
|
||||||
|
raise exception.GlanceStoreReadOnly(
|
||||||
|
store_id=image_service_store_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
raise exception.GlanceStoreNotFound(store_id=image_service_store_id)
|
||||||
|
|
||||||
|
|
||||||
def fixup_disk_format(disk_format):
|
def fixup_disk_format(disk_format):
|
||||||
"""Return the format to be provided to qemu-img convert."""
|
"""Return the format to be provided to qemu-img convert."""
|
||||||
|
|
||||||
|
@ -660,7 +674,8 @@ def _validate_file_format(image_data, expected_format):
|
||||||
|
|
||||||
|
|
||||||
def upload_volume(context, image_service, image_meta, volume_path,
|
def upload_volume(context, image_service, image_meta, volume_path,
|
||||||
volume_format='raw', run_as_root=True, compress=True):
|
volume_format='raw', run_as_root=True, compress=True,
|
||||||
|
store_id=None):
|
||||||
image_id = image_meta['id']
|
image_id = image_meta['id']
|
||||||
if image_meta.get('container_format') != 'compressed':
|
if image_meta.get('container_format') != 'compressed':
|
||||||
if (image_meta['disk_format'] == volume_format):
|
if (image_meta['disk_format'] == volume_format):
|
||||||
|
@ -669,12 +684,14 @@ def upload_volume(context, image_service, image_meta, volume_path,
|
||||||
if os.name == 'nt' or os.access(volume_path, os.R_OK):
|
if os.name == 'nt' or os.access(volume_path, os.R_OK):
|
||||||
with open(volume_path, 'rb') as image_file:
|
with open(volume_path, 'rb') as image_file:
|
||||||
image_service.update(context, image_id, {},
|
image_service.update(context, image_id, {},
|
||||||
tpool.Proxy(image_file))
|
tpool.Proxy(image_file),
|
||||||
|
store_id=store_id)
|
||||||
else:
|
else:
|
||||||
with utils.temporary_chown(volume_path):
|
with utils.temporary_chown(volume_path):
|
||||||
with open(volume_path, 'rb') as image_file:
|
with open(volume_path, 'rb') as image_file:
|
||||||
image_service.update(context, image_id, {},
|
image_service.update(context, image_id, {},
|
||||||
tpool.Proxy(image_file))
|
tpool.Proxy(image_file),
|
||||||
|
store_id=store_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
with temporary_file() as tmp:
|
with temporary_file() as tmp:
|
||||||
|
@ -716,7 +733,8 @@ def upload_volume(context, image_service, image_meta, volume_path,
|
||||||
accel.compress_img(run_as_root=run_as_root)
|
accel.compress_img(run_as_root=run_as_root)
|
||||||
with open(tmp, 'rb') as image_file:
|
with open(tmp, 'rb') as image_file:
|
||||||
image_service.update(context, image_id, {},
|
image_service.update(context, image_id, {},
|
||||||
tpool.Proxy(image_file))
|
tpool.Proxy(image_file),
|
||||||
|
store_id=store_id)
|
||||||
|
|
||||||
|
|
||||||
def check_virtual_size(virtual_size, volume_size, image_id):
|
def check_virtual_size(virtual_size, volume_size, image_id):
|
||||||
|
|
|
@ -24,6 +24,7 @@ import webob
|
||||||
|
|
||||||
from cinder.api.contrib import types_extra_specs
|
from cinder.api.contrib import types_extra_specs
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
|
from cinder.image import glance as image_store
|
||||||
from cinder import test
|
from cinder import test
|
||||||
from cinder.tests.unit.api import fakes
|
from cinder.tests.unit.api import fakes
|
||||||
from cinder.tests.unit import fake_constants as fake
|
from cinder.tests.unit import fake_constants as fake
|
||||||
|
@ -147,6 +148,65 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
||||||
self.assertIn('updated_at', self.notifier.notifications[0]['payload'])
|
self.assertIn('updated_at', self.notifier.notifications[0]['payload'])
|
||||||
self.assertEqual('value1', res_dict['extra_specs']['key1'])
|
self.assertEqual('value1', res_dict['extra_specs']['key1'])
|
||||||
|
|
||||||
|
@mock.patch.object(image_store.GlanceImageService, 'get_stores')
|
||||||
|
def test_create_valid_image_store(self, mock_get_stores):
|
||||||
|
mock_get_stores.return_value = {
|
||||||
|
'stores': [{
|
||||||
|
'default': 'true',
|
||||||
|
'id': 'cheap'
|
||||||
|
}, {
|
||||||
|
'id': 'read_only_store',
|
||||||
|
'read-only': 'true'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
self.mock_object(cinder.db,
|
||||||
|
'volume_type_extra_specs_update_or_create',
|
||||||
|
return_create_volume_type_extra_specs)
|
||||||
|
body = {"extra_specs": {"image_service:store_id": "cheap"}}
|
||||||
|
|
||||||
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
|
req = fakes.HTTPRequest.blank(self.api_path)
|
||||||
|
res_dict = self.controller.create(req, fake.VOLUME_ID, body=body)
|
||||||
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
|
self.assertIn('created_at', self.notifier.notifications[0]['payload'])
|
||||||
|
self.assertIn('updated_at', self.notifier.notifications[0]['payload'])
|
||||||
|
self.assertEqual(
|
||||||
|
'cheap', res_dict['extra_specs']['image_service:store_id'])
|
||||||
|
|
||||||
|
@mock.patch.object(image_store.GlanceImageService, 'get_stores')
|
||||||
|
def test_create_invalid_image_store(self, mock_get_stores):
|
||||||
|
mock_get_stores.return_value = {
|
||||||
|
'stores': [{
|
||||||
|
'default': 'true',
|
||||||
|
'id': 'cheap'
|
||||||
|
}, {
|
||||||
|
'id': 'read_only_store',
|
||||||
|
'read-only': 'true'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
body = {"extra_specs": {"image_service:store_id": "fast"}}
|
||||||
|
req = fakes.HTTPRequest.blank(self.api_path)
|
||||||
|
self.assertRaises(cinder.exception.GlanceStoreNotFound,
|
||||||
|
self.controller.create,
|
||||||
|
req, fake.VOLUME_ID, body=body)
|
||||||
|
|
||||||
|
@mock.patch.object(image_store.GlanceImageService, 'get_stores')
|
||||||
|
def test_create_read_only_image_store(self, mock_get_stores):
|
||||||
|
mock_get_stores.return_value = {
|
||||||
|
'stores': [{
|
||||||
|
'default': 'true',
|
||||||
|
'id': 'cheap'
|
||||||
|
}, {
|
||||||
|
'id': 'read_only_store',
|
||||||
|
'read-only': 'true'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
body = {"extra_specs": {"image_service:store_id": "read_only_store"}}
|
||||||
|
req = fakes.HTTPRequest.blank(self.api_path)
|
||||||
|
self.assertRaises(cinder.exception.GlanceStoreReadOnly,
|
||||||
|
self.controller.create,
|
||||||
|
req, fake.VOLUME_ID, body=body)
|
||||||
|
|
||||||
@mock.patch.object(cinder.db, 'volume_type_extra_specs_update_or_create')
|
@mock.patch.object(cinder.db, 'volume_type_extra_specs_update_or_create')
|
||||||
def test_create_key_allowed_chars(
|
def test_create_key_allowed_chars(
|
||||||
self, volume_type_extra_specs_update_or_create):
|
self, volume_type_extra_specs_update_or_create):
|
||||||
|
@ -195,6 +255,93 @@ class VolumeTypesExtraSpecsTest(test.TestCase):
|
||||||
self.assertEqual('value3',
|
self.assertEqual('value3',
|
||||||
res_dict['extra_specs']['other3_alphanum.-_:'])
|
res_dict['extra_specs']['other3_alphanum.-_:'])
|
||||||
|
|
||||||
|
@mock.patch.object(image_store.GlanceImageService, 'get_stores')
|
||||||
|
def test_update_valid_image_store(self, mock_get_stores):
|
||||||
|
mock_get_stores.return_value = {
|
||||||
|
'stores': [{
|
||||||
|
'default': 'true',
|
||||||
|
'id': 'cheap'
|
||||||
|
}, {
|
||||||
|
'id': 'fast',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'read_only_store',
|
||||||
|
'read-only': 'true'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
self.mock_object(cinder.db,
|
||||||
|
'volume_type_extra_specs_update_or_create',
|
||||||
|
return_create_volume_type_extra_specs)
|
||||||
|
body = {"image_service:store_id": "fast"}
|
||||||
|
|
||||||
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
self.api_path + "/image_service:store_id")
|
||||||
|
res_dict = self.controller.update(req, fake.VOLUME_ID,
|
||||||
|
"image_service:store_id",
|
||||||
|
body=body)
|
||||||
|
self.assertEqual(1, len(self.notifier.notifications))
|
||||||
|
self.assertIn('created_at', self.notifier.notifications[0]['payload'])
|
||||||
|
self.assertIn('updated_at', self.notifier.notifications[0]['payload'])
|
||||||
|
self.assertEqual(
|
||||||
|
'fast', res_dict['image_service:store_id'])
|
||||||
|
|
||||||
|
@mock.patch.object(image_store.GlanceImageService, 'get_stores')
|
||||||
|
def test_update_invalid_image_store(self, mock_get_stores):
|
||||||
|
mock_get_stores.return_value = {
|
||||||
|
'stores': [{
|
||||||
|
'default': 'true',
|
||||||
|
'id': 'cheap'
|
||||||
|
}, {
|
||||||
|
'id': 'fast',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'read_only_store',
|
||||||
|
'read-only': 'true'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
self.mock_object(cinder.db,
|
||||||
|
'volume_type_extra_specs_update_or_create',
|
||||||
|
return_create_volume_type_extra_specs)
|
||||||
|
body = {"image_service:store_id": "very_fast"}
|
||||||
|
|
||||||
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
self.api_path + "/image_service:store_id")
|
||||||
|
self.assertRaises(cinder.exception.GlanceStoreNotFound,
|
||||||
|
self.controller.update,
|
||||||
|
req, fake.VOLUME_ID,
|
||||||
|
"image_service:store_id",
|
||||||
|
body=body)
|
||||||
|
|
||||||
|
@mock.patch.object(image_store.GlanceImageService, 'get_stores')
|
||||||
|
def test_update_read_only_image_store(self, mock_get_stores):
|
||||||
|
mock_get_stores.return_value = {
|
||||||
|
'stores': [{
|
||||||
|
'default': 'true',
|
||||||
|
'id': 'cheap'
|
||||||
|
}, {
|
||||||
|
'id': 'fast',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'read_only_store',
|
||||||
|
'read-only': 'true'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
self.mock_object(cinder.db,
|
||||||
|
'volume_type_extra_specs_update_or_create',
|
||||||
|
return_create_volume_type_extra_specs)
|
||||||
|
body = {"image_service:store_id": "read_only_store"}
|
||||||
|
|
||||||
|
self.assertEqual(0, len(self.notifier.notifications))
|
||||||
|
req = fakes.HTTPRequest.blank(
|
||||||
|
self.api_path + "/image_service:store_id")
|
||||||
|
self.assertRaises(cinder.exception.GlanceStoreReadOnly,
|
||||||
|
self.controller.update,
|
||||||
|
req, fake.VOLUME_ID,
|
||||||
|
"image_service:store_id",
|
||||||
|
body=body)
|
||||||
|
|
||||||
def test_update_item(self):
|
def test_update_item(self):
|
||||||
self.mock_object(cinder.db,
|
self.mock_object(cinder.db,
|
||||||
'volume_type_extra_specs_update_or_create',
|
'volume_type_extra_specs_update_or_create',
|
||||||
|
|
|
@ -212,7 +212,7 @@ class _FakeImageService(object):
|
||||||
return self.images[image_id]
|
return self.images[image_id]
|
||||||
|
|
||||||
def update(self, context, image_id, metadata, data=None,
|
def update(self, context, image_id, metadata, data=None,
|
||||||
purge_props=False):
|
purge_props=False, store_id=None):
|
||||||
"""Replace the contents of the given image with the new data.
|
"""Replace the contents of the given image with the new data.
|
||||||
|
|
||||||
:raises ImageNotFound: if the image does not exist.
|
:raises ImageNotFound: if the image does not exist.
|
||||||
|
|
|
@ -762,7 +762,8 @@ class TestUploadVolume(test.TestCase):
|
||||||
mock_proxy.assert_called_once_with(
|
mock_proxy.assert_called_once_with(
|
||||||
mock_open.return_value.__enter__.return_value)
|
mock_open.return_value.__enter__.return_value)
|
||||||
image_service.update.assert_called_once_with(
|
image_service.update.assert_called_once_with(
|
||||||
ctxt, image_meta['id'], {}, mock_proxy.return_value)
|
ctxt, image_meta['id'], {}, mock_proxy.return_value,
|
||||||
|
store_id=None)
|
||||||
|
|
||||||
@mock.patch('eventlet.tpool.Proxy')
|
@mock.patch('eventlet.tpool.Proxy')
|
||||||
@mock.patch('cinder.image.image_utils.utils.temporary_chown')
|
@mock.patch('cinder.image.image_utils.utils.temporary_chown')
|
||||||
|
@ -794,7 +795,8 @@ class TestUploadVolume(test.TestCase):
|
||||||
mock_proxy.assert_called_once_with(
|
mock_proxy.assert_called_once_with(
|
||||||
mock_open.return_value.__enter__.return_value)
|
mock_open.return_value.__enter__.return_value)
|
||||||
image_service.update.assert_called_once_with(
|
image_service.update.assert_called_once_with(
|
||||||
ctxt, image_meta['id'], {}, mock_proxy.return_value)
|
ctxt, image_meta['id'], {}, mock_proxy.return_value,
|
||||||
|
store_id=None)
|
||||||
|
|
||||||
@mock.patch('cinder.image.accelerator.ImageAccel._get_engine')
|
@mock.patch('cinder.image.accelerator.ImageAccel._get_engine')
|
||||||
@mock.patch('cinder.image.accelerator.ImageAccel.is_engine_ready',
|
@mock.patch('cinder.image.accelerator.ImageAccel.is_engine_ready',
|
||||||
|
@ -849,7 +851,8 @@ class TestUploadVolume(test.TestCase):
|
||||||
mock_proxy.assert_called_once_with(
|
mock_proxy.assert_called_once_with(
|
||||||
mock_open.return_value.__enter__.return_value)
|
mock_open.return_value.__enter__.return_value)
|
||||||
image_service.update.assert_called_once_with(
|
image_service.update.assert_called_once_with(
|
||||||
ctxt, image_meta['id'], {}, mock_proxy.return_value)
|
ctxt, image_meta['id'], {}, mock_proxy.return_value,
|
||||||
|
store_id=None)
|
||||||
mock_engine.compress_img.assert_called()
|
mock_engine.compress_img.assert_called()
|
||||||
|
|
||||||
@mock.patch('eventlet.tpool.Proxy')
|
@mock.patch('eventlet.tpool.Proxy')
|
||||||
|
@ -882,7 +885,8 @@ class TestUploadVolume(test.TestCase):
|
||||||
mock_proxy.assert_called_once_with(
|
mock_proxy.assert_called_once_with(
|
||||||
mock_open.return_value.__enter__.return_value)
|
mock_open.return_value.__enter__.return_value)
|
||||||
image_service.update.assert_called_once_with(
|
image_service.update.assert_called_once_with(
|
||||||
ctxt, image_meta['id'], {}, mock_proxy.return_value)
|
ctxt, image_meta['id'], {}, mock_proxy.return_value,
|
||||||
|
store_id=None)
|
||||||
|
|
||||||
@mock.patch('cinder.image.accelerator.ImageAccel._get_engine')
|
@mock.patch('cinder.image.accelerator.ImageAccel._get_engine')
|
||||||
@mock.patch('cinder.image.accelerator.ImageAccel.is_engine_ready',
|
@mock.patch('cinder.image.accelerator.ImageAccel.is_engine_ready',
|
||||||
|
@ -938,7 +942,8 @@ class TestUploadVolume(test.TestCase):
|
||||||
mock_proxy.assert_called_once_with(
|
mock_proxy.assert_called_once_with(
|
||||||
mock_open.return_value.__enter__.return_value)
|
mock_open.return_value.__enter__.return_value)
|
||||||
image_service.update.assert_called_once_with(
|
image_service.update.assert_called_once_with(
|
||||||
ctxt, image_meta['id'], {}, mock_proxy.return_value)
|
ctxt, image_meta['id'], {}, mock_proxy.return_value,
|
||||||
|
store_id=None)
|
||||||
mock_engine.compress_img.assert_called()
|
mock_engine.compress_img.assert_called()
|
||||||
|
|
||||||
@mock.patch('cinder.image.image_utils.CONF')
|
@mock.patch('cinder.image.image_utils.CONF')
|
||||||
|
|
|
@ -19,6 +19,7 @@ from unittest import mock
|
||||||
|
|
||||||
from oslo_concurrency import processutils
|
from oslo_concurrency import processutils
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
|
@ -27,6 +28,7 @@ from cinder import objects
|
||||||
from cinder.objects import fields
|
from cinder.objects import fields
|
||||||
from cinder import test
|
from cinder import test
|
||||||
from cinder.tests.unit import fake_constants as fake
|
from cinder.tests.unit import fake_constants as fake
|
||||||
|
from cinder.tests.unit import utils as test_utils
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder.volume import configuration as conf
|
from cinder.volume import configuration as conf
|
||||||
from cinder.volume.drivers.ibm import gpfs
|
from cinder.volume.drivers.ibm import gpfs
|
||||||
|
@ -86,6 +88,7 @@ class GPFSDriverTestCase(test.TestCase):
|
||||||
self.context = context.get_admin_context()
|
self.context = context.get_admin_context()
|
||||||
self.context.user_id = 'fake'
|
self.context.user_id = 'fake'
|
||||||
self.context.project_id = 'fake'
|
self.context.project_id = 'fake'
|
||||||
|
self.updated_at = timeutils.utcnow()
|
||||||
CONF.gpfs_images_dir = self.images_dir
|
CONF.gpfs_images_dir = self.images_dir
|
||||||
|
|
||||||
def _cleanup(self, images_dir, volumes_path):
|
def _cleanup(self, images_dir, volumes_path):
|
||||||
|
@ -1384,7 +1387,16 @@ class GPFSDriverTestCase(test.TestCase):
|
||||||
@mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
|
@mock.patch('cinder.volume.drivers.ibm.gpfs.GPFSDriver.local_path')
|
||||||
@mock.patch('cinder.image.image_utils.upload_volume')
|
@mock.patch('cinder.image.image_utils.upload_volume')
|
||||||
def test_copy_volume_to_image(self, mock_upload_volume, mock_local_path):
|
def test_copy_volume_to_image(self, mock_upload_volume, mock_local_path):
|
||||||
volume = self._fake_volume()
|
volume = test_utils.create_volume(
|
||||||
|
self.context, volume_type_id=fake.VOLUME_TYPE_ID,
|
||||||
|
updated_at=self.updated_at)
|
||||||
|
extra_specs = {
|
||||||
|
'image_service:store_id': 'fake-store'
|
||||||
|
}
|
||||||
|
test_utils.create_volume_type(
|
||||||
|
self.context.elevated(), id=fake.VOLUME_TYPE_ID,
|
||||||
|
name="test_type", extra_specs=extra_specs)
|
||||||
|
|
||||||
self.driver.copy_volume_to_image('', volume, '', '')
|
self.driver.copy_volume_to_image('', volume, '', '')
|
||||||
|
|
||||||
@mock.patch('cinder.utils.execute')
|
@mock.patch('cinder.utils.execute')
|
||||||
|
|
|
@ -30,6 +30,7 @@ import psutil
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
|
from cinder import db
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.image import image_utils
|
from cinder.image import image_utils
|
||||||
from cinder import test
|
from cinder import test
|
||||||
|
@ -1273,7 +1274,9 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||||
def test_copy_volume_to_image_raw_image(self):
|
def test_copy_volume_to_image_raw_image(self):
|
||||||
drv = self._driver
|
drv = self._driver
|
||||||
|
|
||||||
volume = self._simple_volume()
|
volume_type_id = db.volume_type_create(
|
||||||
|
self.context, {'name': 'quo_type', 'extra_specs': {}}).get('id')
|
||||||
|
volume = self._simple_volume(volume_type_id=volume_type_id)
|
||||||
volume_path = '%s/%s' % (self.TEST_MNT_POINT, volume['name'])
|
volume_path = '%s/%s' % (self.TEST_MNT_POINT, volume['name'])
|
||||||
image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'}
|
image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'}
|
||||||
|
|
||||||
|
@ -1311,14 +1314,17 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||||
force_share=True,
|
force_share=True,
|
||||||
run_as_root=False)
|
run_as_root=False)
|
||||||
mock_upload_volume.assert_called_once_with(
|
mock_upload_volume.assert_called_once_with(
|
||||||
mock.ANY, mock.ANY, mock.ANY, upload_path, run_as_root=False)
|
mock.ANY, mock.ANY, mock.ANY, upload_path, run_as_root=False,
|
||||||
|
store_id=None)
|
||||||
self.assertTrue(mock_create_temporary_file.called)
|
self.assertTrue(mock_create_temporary_file.called)
|
||||||
|
|
||||||
def test_copy_volume_to_image_qcow2_image(self):
|
def test_copy_volume_to_image_qcow2_image(self):
|
||||||
"""Upload a qcow2 image file which has to be converted to raw first."""
|
"""Upload a qcow2 image file which has to be converted to raw first."""
|
||||||
drv = self._driver
|
drv = self._driver
|
||||||
|
|
||||||
volume = self._simple_volume()
|
volume_type_id = db.volume_type_create(
|
||||||
|
self.context, {'name': 'quo_type', 'extra_specs': {}}).get('id')
|
||||||
|
volume = self._simple_volume(volume_type_id=volume_type_id)
|
||||||
volume_path = '%s/%s' % (self.TEST_MNT_POINT, volume['name'])
|
volume_path = '%s/%s' % (self.TEST_MNT_POINT, volume['name'])
|
||||||
image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'}
|
image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'}
|
||||||
|
|
||||||
|
@ -1360,14 +1366,17 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||||
mock_convert_image.assert_called_once_with(
|
mock_convert_image.assert_called_once_with(
|
||||||
volume_path, upload_path, 'raw', run_as_root=False)
|
volume_path, upload_path, 'raw', run_as_root=False)
|
||||||
mock_upload_volume.assert_called_once_with(
|
mock_upload_volume.assert_called_once_with(
|
||||||
mock.ANY, mock.ANY, mock.ANY, upload_path, run_as_root=False)
|
mock.ANY, mock.ANY, mock.ANY, upload_path, run_as_root=False,
|
||||||
|
store_id=None)
|
||||||
self.assertTrue(mock_create_temporary_file.called)
|
self.assertTrue(mock_create_temporary_file.called)
|
||||||
|
|
||||||
def test_copy_volume_to_image_snapshot_exists(self):
|
def test_copy_volume_to_image_snapshot_exists(self):
|
||||||
"""Upload an active snapshot which has to be converted to raw first."""
|
"""Upload an active snapshot which has to be converted to raw first."""
|
||||||
drv = self._driver
|
drv = self._driver
|
||||||
|
|
||||||
volume = self._simple_volume()
|
volume_type_id = db.volume_type_create(
|
||||||
|
self.context, {'name': 'quo_type', 'extra_specs': {}}).get('id')
|
||||||
|
volume = self._simple_volume(volume_type_id=volume_type_id)
|
||||||
volume_path = '%s/volume-%s' % (self.TEST_MNT_POINT, self.VOLUME_UUID)
|
volume_path = '%s/volume-%s' % (self.TEST_MNT_POINT, self.VOLUME_UUID)
|
||||||
volume_filename = 'volume-%s' % self.VOLUME_UUID
|
volume_filename = 'volume-%s' % self.VOLUME_UUID
|
||||||
image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'}
|
image_meta = {'id': '10958016-e196-42e3-9e7f-5d8927ae3099'}
|
||||||
|
@ -1411,7 +1420,8 @@ class QuobyteDriverTestCase(test.TestCase):
|
||||||
mock_convert_image.assert_called_once_with(
|
mock_convert_image.assert_called_once_with(
|
||||||
volume_path, upload_path, 'raw', run_as_root=False)
|
volume_path, upload_path, 'raw', run_as_root=False)
|
||||||
mock_upload_volume.assert_called_once_with(
|
mock_upload_volume.assert_called_once_with(
|
||||||
mock.ANY, mock.ANY, mock.ANY, upload_path, run_as_root=False)
|
mock.ANY, mock.ANY, mock.ANY, upload_path, run_as_root=False,
|
||||||
|
store_id=None)
|
||||||
self.assertTrue(mock_create_temporary_file.called)
|
self.assertTrue(mock_create_temporary_file.called)
|
||||||
|
|
||||||
def test_set_nas_security_options_default(self):
|
def test_set_nas_security_options_default(self):
|
||||||
|
|
|
@ -16,12 +16,15 @@ from unittest import mock
|
||||||
|
|
||||||
from os_brick import initiator
|
from os_brick import initiator
|
||||||
from os_brick.initiator import connector
|
from os_brick.initiator import connector
|
||||||
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import objects
|
from cinder import objects
|
||||||
from cinder import test
|
from cinder import test
|
||||||
|
from cinder.tests.unit import fake_constants as fake
|
||||||
from cinder.tests.unit import fake_volume
|
from cinder.tests.unit import fake_volume
|
||||||
|
from cinder.tests.unit import utils as test_utils
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder.volume import configuration as conf
|
from cinder.volume import configuration as conf
|
||||||
from cinder.volume.drivers import spdk as spdk_driver
|
from cinder.volume.drivers import spdk as spdk_driver
|
||||||
|
@ -513,6 +516,8 @@ class SpdkDriverTestCase(test.TestCase):
|
||||||
self.jsonrpcclient = JSONRPCClient()
|
self.jsonrpcclient = JSONRPCClient()
|
||||||
self.driver = spdk_driver.SPDKDriver(configuration=
|
self.driver = spdk_driver.SPDKDriver(configuration=
|
||||||
self.configuration)
|
self.configuration)
|
||||||
|
self._context = context.get_admin_context()
|
||||||
|
self.updated_at = timeutils.utcnow()
|
||||||
|
|
||||||
def test__update_volume_stats(self):
|
def test__update_volume_stats(self):
|
||||||
with mock.patch.object(self.driver, "_rpc_call",
|
with mock.patch.object(self.driver, "_rpc_call",
|
||||||
|
@ -733,16 +738,24 @@ class SpdkDriverTestCase(test.TestCase):
|
||||||
def test_copy_volume_to_image(self, volume_get):
|
def test_copy_volume_to_image(self, volume_get):
|
||||||
with mock.patch.object(self.driver, "_rpc_call",
|
with mock.patch.object(self.driver, "_rpc_call",
|
||||||
self.jsonrpcclient.call):
|
self.jsonrpcclient.call):
|
||||||
db_volume = fake_volume.fake_db_volume()
|
provider_location = "127.0.0.1:3262 RDMA 2016-06.io.spdk:cnode2"
|
||||||
db_volume['provider_location'] = "127.0.0.1:3262 RDMA " \
|
volume = test_utils.create_volume(
|
||||||
"2016-06.io.spdk:cnode2"
|
self._context, volume_type_id=fake.VOLUME_TYPE_ID,
|
||||||
|
updated_at=self.updated_at,
|
||||||
|
provider_location=provider_location)
|
||||||
|
extra_specs = {
|
||||||
|
'image_service:store_id': 'fake-store'
|
||||||
|
}
|
||||||
|
test_utils.create_volume_type(self._context.elevated(),
|
||||||
|
id=fake.VOLUME_TYPE_ID,
|
||||||
|
name="test_type",
|
||||||
|
extra_specs=extra_specs)
|
||||||
|
|
||||||
ctxt = context.get_admin_context()
|
ctxt = context.get_admin_context()
|
||||||
db_volume = objects.Volume._from_db_object(ctxt, objects.Volume(),
|
volume_get.return_value = volume
|
||||||
db_volume)
|
|
||||||
volume_get.return_value = db_volume
|
|
||||||
with mock.patch.object(self.driver.target_driver, "_rpc_call",
|
with mock.patch.object(self.driver.target_driver, "_rpc_call",
|
||||||
self.jsonrpcclient.call):
|
self.jsonrpcclient.call):
|
||||||
self.driver.copy_volume_to_image(ctxt, db_volume, None, None)
|
self.driver.copy_volume_to_image(ctxt, volume, None, None)
|
||||||
|
|
||||||
def test_extend_volume(self):
|
def test_extend_volume(self):
|
||||||
with mock.patch.object(self.driver, "_rpc_call",
|
with mock.patch.object(self.driver, "_rpc_call",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
from oslo_vmware import image_transfer
|
from oslo_vmware import image_transfer
|
||||||
from oslo_vmware.objects import datastore
|
from oslo_vmware.objects import datastore
|
||||||
|
@ -26,8 +27,10 @@ from oslo_vmware import vim_util
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import exception as cinder_exceptions
|
from cinder import exception as cinder_exceptions
|
||||||
from cinder import test
|
from cinder import test
|
||||||
|
from cinder.tests.unit import fake_constants as fake
|
||||||
from cinder.tests.unit import fake_snapshot
|
from cinder.tests.unit import fake_snapshot
|
||||||
from cinder.tests.unit import fake_volume
|
from cinder.tests.unit import fake_volume
|
||||||
|
from cinder.tests.unit import utils as test_utils
|
||||||
from cinder.volume import configuration
|
from cinder.volume import configuration
|
||||||
from cinder.volume.drivers.vmware import datastore as hub
|
from cinder.volume.drivers.vmware import datastore as hub
|
||||||
from cinder.volume.drivers.vmware import fcd
|
from cinder.volume.drivers.vmware import fcd
|
||||||
|
@ -66,6 +69,7 @@ class VMwareVStorageObjectDriverTestCase(test.TestCase):
|
||||||
self._driver._vc_version = self.VC_VERSION
|
self._driver._vc_version = self.VC_VERSION
|
||||||
self._driver._storage_policy_enabled = True
|
self._driver._storage_policy_enabled = True
|
||||||
self._context = context.get_admin_context()
|
self._context = context.get_admin_context()
|
||||||
|
self.updated_at = timeutils.utcnow()
|
||||||
|
|
||||||
@mock.patch.object(VMDK_DRIVER, 'do_setup')
|
@mock.patch.object(VMDK_DRIVER, 'do_setup')
|
||||||
@mock.patch.object(FCD_DRIVER, 'volumeops')
|
@mock.patch.object(FCD_DRIVER, 'volumeops')
|
||||||
|
@ -386,7 +390,16 @@ class VMwareVStorageObjectDriverTestCase(test.TestCase):
|
||||||
vops.get_vmdk_path.return_value = vmdk_file_path
|
vops.get_vmdk_path.return_value = vmdk_file_path
|
||||||
vops.get_backing_by_uuid.return_value = backing
|
vops.get_backing_by_uuid.return_value = backing
|
||||||
|
|
||||||
volume = self._create_volume_obj()
|
volume = test_utils.create_volume(
|
||||||
|
self._context, volume_type_id=fake.VOLUME_TYPE_ID,
|
||||||
|
updated_at=self.updated_at)
|
||||||
|
extra_specs = {
|
||||||
|
'image_service:store_id': 'fake-store'
|
||||||
|
}
|
||||||
|
test_utils.create_volume_type(
|
||||||
|
self._context.elevated(), id=fake.VOLUME_TYPE_ID,
|
||||||
|
name="test_type", extra_specs=extra_specs)
|
||||||
|
|
||||||
image_service = mock.sentinel.image_service
|
image_service = mock.sentinel.image_service
|
||||||
image_meta = self._create_image_meta()
|
image_meta = self._create_image_meta()
|
||||||
self._driver.copy_volume_to_image(
|
self._driver.copy_volume_to_image(
|
||||||
|
@ -411,7 +424,8 @@ class VMwareVStorageObjectDriverTestCase(test.TestCase):
|
||||||
vm=backing,
|
vm=backing,
|
||||||
vmdk_file_path=vmdk_file_path,
|
vmdk_file_path=vmdk_file_path,
|
||||||
vmdk_size=volume.size * units.Gi,
|
vmdk_size=volume.size * units.Gi,
|
||||||
image_name=image_meta['name'])
|
image_name=image_meta['name'],
|
||||||
|
store_id='fake-store')
|
||||||
vops.detach_fcd.assert_called_once_with(backing, fcd_loc)
|
vops.detach_fcd.assert_called_once_with(backing, fcd_loc)
|
||||||
delete_temp_backing.assert_called_once_with(backing)
|
delete_temp_backing.assert_called_once_with(backing)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import re
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
from oslo_utils import versionutils
|
from oslo_utils import versionutils
|
||||||
from oslo_vmware import exceptions
|
from oslo_vmware import exceptions
|
||||||
|
@ -32,6 +33,7 @@ from cinder import test
|
||||||
from cinder.tests.unit import fake_constants
|
from cinder.tests.unit import fake_constants
|
||||||
from cinder.tests.unit import fake_snapshot
|
from cinder.tests.unit import fake_snapshot
|
||||||
from cinder.tests.unit import fake_volume
|
from cinder.tests.unit import fake_volume
|
||||||
|
from cinder.tests.unit import utils as test_utils
|
||||||
from cinder.volume import configuration
|
from cinder.volume import configuration
|
||||||
from cinder.volume.drivers.vmware import datastore as hub
|
from cinder.volume.drivers.vmware import datastore as hub
|
||||||
from cinder.volume.drivers.vmware import exceptions as vmdk_exceptions
|
from cinder.volume.drivers.vmware import exceptions as vmdk_exceptions
|
||||||
|
@ -105,6 +107,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
db=self._db)
|
db=self._db)
|
||||||
|
|
||||||
self._context = context.get_admin_context()
|
self._context = context.get_admin_context()
|
||||||
|
self.updated_at = timeutils.utcnow()
|
||||||
|
|
||||||
def test_get_volume_stats(self):
|
def test_get_volume_stats(self):
|
||||||
stats = self._driver.get_volume_stats()
|
stats = self._driver.get_volume_stats()
|
||||||
|
@ -1182,7 +1185,17 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
vops.get_vmdk_path.return_value = vmdk_file_path
|
vops.get_vmdk_path.return_value = vmdk_file_path
|
||||||
|
|
||||||
context = mock.sentinel.context
|
context = mock.sentinel.context
|
||||||
volume = self._create_volume_dict()
|
volume = test_utils.create_volume(
|
||||||
|
self._context, volume_type_id = fake_constants.VOLUME_TYPE_ID,
|
||||||
|
updated_at = self.updated_at)
|
||||||
|
extra_specs = {
|
||||||
|
'image_service:store_id': 'fake-store'
|
||||||
|
}
|
||||||
|
test_utils.create_volume_type(self._context.elevated(),
|
||||||
|
id=fake_constants.VOLUME_TYPE_ID,
|
||||||
|
name="test_type",
|
||||||
|
extra_specs=extra_specs)
|
||||||
|
|
||||||
image_service = mock.sentinel.image_service
|
image_service = mock.sentinel.image_service
|
||||||
image_meta = self._create_image_meta()
|
image_meta = self._create_image_meta()
|
||||||
self._driver.copy_volume_to_image(
|
self._driver.copy_volume_to_image(
|
||||||
|
@ -1202,6 +1215,7 @@ class VMwareVcVmdkDriverTestCase(test.TestCase):
|
||||||
session=session,
|
session=session,
|
||||||
host=self._config.vmware_host_ip,
|
host=self._config.vmware_host_ip,
|
||||||
port=self._config.vmware_host_port,
|
port=self._config.vmware_host_port,
|
||||||
|
store_id='fake-store',
|
||||||
vm=backing,
|
vm=backing,
|
||||||
vmdk_file_path=vmdk_file_path,
|
vmdk_file_path=vmdk_file_path,
|
||||||
vmdk_size=volume['size'] * units.Gi,
|
vmdk_size=volume['size'] * units.Gi,
|
||||||
|
|
|
@ -89,6 +89,11 @@ class CopyVolumeToImageTestCase(base.BaseVolumeTestCase):
|
||||||
def test_copy_volume_to_image_status_available(self):
|
def test_copy_volume_to_image_status_available(self):
|
||||||
# creating volume testdata
|
# creating volume testdata
|
||||||
self.volume_attrs['instance_uuid'] = None
|
self.volume_attrs['instance_uuid'] = None
|
||||||
|
volume_type_id = db.volume_type_create(
|
||||||
|
self.context, {'name': 'test', 'extra_specs': {
|
||||||
|
'image_service:store_id': 'fake_store'
|
||||||
|
}}).get('id')
|
||||||
|
self.volume_attrs['volume_type_id'] = volume_type_id
|
||||||
db.volume_create(self.context, self.volume_attrs)
|
db.volume_create(self.context, self.volume_attrs)
|
||||||
|
|
||||||
# start test
|
# start test
|
||||||
|
@ -129,6 +134,11 @@ class CopyVolumeToImageTestCase(base.BaseVolumeTestCase):
|
||||||
# Creating volume testdata
|
# Creating volume testdata
|
||||||
self.volume_attrs['instance_uuid'] = 'b21f957d-a72f-4b93-b5a5-' \
|
self.volume_attrs['instance_uuid'] = 'b21f957d-a72f-4b93-b5a5-' \
|
||||||
'45b1161abb02'
|
'45b1161abb02'
|
||||||
|
volume_type_id = db.volume_type_create(
|
||||||
|
self.context, {'name': 'test', 'extra_specs': {
|
||||||
|
'image_service:store_id': 'fake_store'
|
||||||
|
}}).get('id')
|
||||||
|
self.volume_attrs['volume_type_id'] = volume_type_id
|
||||||
db.volume_create(self.context, self.volume_attrs)
|
db.volume_create(self.context, self.volume_attrs)
|
||||||
|
|
||||||
method = 'volume_update_status_based_on_attachment'
|
method = 'volume_update_status_based_on_attachment'
|
||||||
|
@ -150,6 +160,11 @@ class CopyVolumeToImageTestCase(base.BaseVolumeTestCase):
|
||||||
def test_copy_volume_to_image_status_use(self):
|
def test_copy_volume_to_image_status_use(self):
|
||||||
self.image_meta['id'] = 'a440c04b-79fa-479c-bed1-0b816eaec379'
|
self.image_meta['id'] = 'a440c04b-79fa-479c-bed1-0b816eaec379'
|
||||||
# creating volume testdata
|
# creating volume testdata
|
||||||
|
volume_type_id = db.volume_type_create(
|
||||||
|
self.context, {'name': 'test', 'extra_specs': {
|
||||||
|
'image_service:store_id': 'fake_store'
|
||||||
|
}}).get('id')
|
||||||
|
self.volume_attrs['volume_type_id'] = volume_type_id
|
||||||
db.volume_create(self.context, self.volume_attrs)
|
db.volume_create(self.context, self.volume_attrs)
|
||||||
|
|
||||||
# start test
|
# start test
|
||||||
|
@ -163,6 +178,11 @@ class CopyVolumeToImageTestCase(base.BaseVolumeTestCase):
|
||||||
def test_copy_volume_to_image_exception(self):
|
def test_copy_volume_to_image_exception(self):
|
||||||
self.image_meta['id'] = NON_EXISTENT_IMAGE_ID
|
self.image_meta['id'] = NON_EXISTENT_IMAGE_ID
|
||||||
# creating volume testdata
|
# creating volume testdata
|
||||||
|
volume_type_id = db.volume_type_create(
|
||||||
|
self.context, {'name': 'test', 'extra_specs': {
|
||||||
|
'image_service:store_id': 'fake_store'
|
||||||
|
}}).get('id')
|
||||||
|
self.volume_attrs['volume_type_id'] = volume_type_id
|
||||||
self.volume_attrs['status'] = 'in-use'
|
self.volume_attrs['status'] = 'in-use'
|
||||||
db.volume_create(self.context, self.volume_attrs)
|
db.volume_create(self.context, self.volume_attrs)
|
||||||
|
|
||||||
|
@ -296,6 +316,11 @@ class CopyVolumeToImageTestCase(base.BaseVolumeTestCase):
|
||||||
# creating volume testdata
|
# creating volume testdata
|
||||||
self.volume_attrs['instance_uuid'] = None
|
self.volume_attrs['instance_uuid'] = None
|
||||||
self.volume_attrs['snapshot_id'] = fake.SNAPSHOT_ID
|
self.volume_attrs['snapshot_id'] = fake.SNAPSHOT_ID
|
||||||
|
volume_type_id = db.volume_type_create(
|
||||||
|
self.context, {'name': 'test', 'extra_specs': {
|
||||||
|
'image_service:store_id': 'fake_store'
|
||||||
|
}}).get('id')
|
||||||
|
self.volume_attrs['volume_type_id'] = volume_type_id
|
||||||
db.volume_create(self.context, self.volume_attrs)
|
db.volume_create(self.context, self.volume_attrs)
|
||||||
|
|
||||||
def fake_create(context, volume, **kwargs):
|
def fake_create(context, volume, **kwargs):
|
||||||
|
|
|
@ -23,14 +23,17 @@ from unittest import mock
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.image import image_utils
|
from cinder.image import image_utils
|
||||||
from cinder import test
|
from cinder import test
|
||||||
|
from cinder.tests.unit import fake_constants as fake
|
||||||
from cinder.tests.unit import fake_snapshot
|
from cinder.tests.unit import fake_snapshot
|
||||||
from cinder.tests.unit import fake_volume
|
from cinder.tests.unit import fake_volume
|
||||||
|
from cinder.tests.unit import utils as test_utils
|
||||||
from cinder.tests.unit.windows import db_fakes
|
from cinder.tests.unit.windows import db_fakes
|
||||||
from cinder.volume import configuration as conf
|
from cinder.volume import configuration as conf
|
||||||
from cinder.volume.drivers.windows import iscsi as windows_iscsi
|
from cinder.volume.drivers.windows import iscsi as windows_iscsi
|
||||||
|
@ -49,6 +52,9 @@ class TestWindowsISCSIDriver(test.TestCase):
|
||||||
self._driver = windows_iscsi.WindowsISCSIDriver(
|
self._driver = windows_iscsi.WindowsISCSIDriver(
|
||||||
configuration=self.configuration)
|
configuration=self.configuration)
|
||||||
|
|
||||||
|
self._context = context.get_admin_context()
|
||||||
|
self.updated_at = timeutils.utcnow()
|
||||||
|
|
||||||
@mock.patch.object(fileutils, 'ensure_tree')
|
@mock.patch.object(fileutils, 'ensure_tree')
|
||||||
def test_do_setup(self, mock_ensure_tree):
|
def test_do_setup(self, mock_ensure_tree):
|
||||||
self._driver.do_setup(mock.sentinel.context)
|
self._driver.do_setup(mock.sentinel.context)
|
||||||
|
@ -375,7 +381,18 @@ class TestWindowsISCSIDriver(test.TestCase):
|
||||||
|
|
||||||
disk_format = 'vhd'
|
disk_format = 'vhd'
|
||||||
fake_image_meta = db_fakes.get_fake_image_meta()
|
fake_image_meta = db_fakes.get_fake_image_meta()
|
||||||
volume = fake_volume.fake_volume_obj(mock.sentinel.fake_context)
|
|
||||||
|
fake_volume = test_utils.create_volume(
|
||||||
|
self._context, volume_type_id=fake.VOLUME_TYPE_ID,
|
||||||
|
updated_at=self.updated_at)
|
||||||
|
|
||||||
|
extra_specs = {
|
||||||
|
'image_service:store_id': 'fake-store'
|
||||||
|
}
|
||||||
|
test_utils.create_volume_type(self._context.elevated(),
|
||||||
|
id=fake.VOLUME_TYPE_ID, name="test_type",
|
||||||
|
extra_specs=extra_specs)
|
||||||
|
|
||||||
fake_img_conv_dir = 'fake_img_conv_dir'
|
fake_img_conv_dir = 'fake_img_conv_dir'
|
||||||
self.flags(image_conversion_dir=fake_img_conv_dir)
|
self.flags(image_conversion_dir=fake_img_conv_dir)
|
||||||
|
|
||||||
|
@ -388,17 +405,18 @@ class TestWindowsISCSIDriver(test.TestCase):
|
||||||
fake_image_meta['id'] + '.' + disk_format)
|
fake_image_meta['id'] + '.' + disk_format)
|
||||||
|
|
||||||
self._driver.copy_volume_to_image(
|
self._driver.copy_volume_to_image(
|
||||||
mock.sentinel.context, volume,
|
mock.sentinel.context, fake_volume,
|
||||||
mock.sentinel.image_service,
|
mock.sentinel.image_service,
|
||||||
fake_image_meta)
|
fake_image_meta)
|
||||||
|
|
||||||
mock_tmp_snap.assert_called_once_with(volume.name)
|
mock_tmp_snap.assert_called_once_with(fake_volume.name)
|
||||||
tgt_utils.export_snapshot.assert_called_once_with(
|
tgt_utils.export_snapshot.assert_called_once_with(
|
||||||
mock.sentinel.tmp_snap_name,
|
mock.sentinel.tmp_snap_name,
|
||||||
expected_tmp_vhd_path)
|
expected_tmp_vhd_path)
|
||||||
mock_upload_volume.assert_called_once_with(
|
mock_upload_volume.assert_called_once_with(
|
||||||
mock.sentinel.context, mock.sentinel.image_service,
|
mock.sentinel.context, mock.sentinel.image_service,
|
||||||
fake_image_meta, expected_tmp_vhd_path, 'vhd')
|
fake_image_meta, expected_tmp_vhd_path, 'vhd',
|
||||||
|
store_id='fake-store')
|
||||||
mock_delete_if_exists.assert_called_once_with(
|
mock_delete_if_exists.assert_called_once_with(
|
||||||
expected_tmp_vhd_path)
|
expected_tmp_vhd_path)
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import os
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import units
|
from oslo_utils import units
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
|
@ -24,6 +25,7 @@ from cinder import exception
|
||||||
from cinder.image import image_utils
|
from cinder.image import image_utils
|
||||||
from cinder.objects import fields
|
from cinder.objects import fields
|
||||||
from cinder import test
|
from cinder import test
|
||||||
|
from cinder.tests.unit import fake_constants as fake
|
||||||
from cinder.tests.unit import fake_volume
|
from cinder.tests.unit import fake_volume
|
||||||
from cinder.tests.unit import utils as test_utils
|
from cinder.tests.unit import utils as test_utils
|
||||||
from cinder.volume.drivers import remotefs
|
from cinder.volume.drivers import remotefs
|
||||||
|
@ -78,6 +80,9 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||||
self.volume = self._simple_volume()
|
self.volume = self._simple_volume()
|
||||||
self.snapshot = self._simple_snapshot(volume=self.volume)
|
self.snapshot = self._simple_snapshot(volume=self.volume)
|
||||||
|
|
||||||
|
self._context = context.get_admin_context()
|
||||||
|
self.updated_at = timeutils.utcnow()
|
||||||
|
|
||||||
def _simple_volume(self, **kwargs):
|
def _simple_volume(self, **kwargs):
|
||||||
updates = {'id': self._FAKE_VOLUME_ID,
|
updates = {'id': self._FAKE_VOLUME_ID,
|
||||||
'size': self._FAKE_VOLUME_SIZE,
|
'size': self._FAKE_VOLUME_SIZE,
|
||||||
|
@ -722,6 +727,17 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||||
def test_copy_volume_to_image(self, has_parent=False):
|
def test_copy_volume_to_image(self, has_parent=False):
|
||||||
drv = self._smbfs_driver
|
drv = self._smbfs_driver
|
||||||
|
|
||||||
|
volume = test_utils.create_volume(
|
||||||
|
self._context, volume_type_id=fake.VOLUME_TYPE_ID,
|
||||||
|
updated_at=self.updated_at)
|
||||||
|
|
||||||
|
extra_specs = {
|
||||||
|
'image_service:store_id': 'fake-store'
|
||||||
|
}
|
||||||
|
test_utils.create_volume_type(self._context.elevated(),
|
||||||
|
id=fake.VOLUME_TYPE_ID, name="test_type",
|
||||||
|
extra_specs=extra_specs)
|
||||||
|
|
||||||
fake_image_meta = {'id': 'fake-image-id'}
|
fake_image_meta = {'id': 'fake-image-id'}
|
||||||
fake_img_format = self._smbfs_driver._DISK_FORMAT_VHDX
|
fake_img_format = self._smbfs_driver._DISK_FORMAT_VHDX
|
||||||
|
|
||||||
|
@ -746,12 +762,12 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||||
with mock.patch.object(image_utils, 'upload_volume') as (
|
with mock.patch.object(image_utils, 'upload_volume') as (
|
||||||
fake_upload_volume):
|
fake_upload_volume):
|
||||||
drv.copy_volume_to_image(
|
drv.copy_volume_to_image(
|
||||||
mock.sentinel.context, self.volume,
|
mock.sentinel.context, volume,
|
||||||
mock.sentinel.image_service, fake_image_meta)
|
mock.sentinel.image_service, fake_image_meta)
|
||||||
|
|
||||||
if has_parent:
|
if has_parent:
|
||||||
fake_temp_image_name = '%s.temp_image.%s.%s' % (
|
fake_temp_image_name = '%s.temp_image.%s.%s' % (
|
||||||
self.volume.id,
|
volume.id,
|
||||||
fake_image_meta['id'],
|
fake_image_meta['id'],
|
||||||
fake_img_format)
|
fake_img_format)
|
||||||
fake_temp_image_path = os.path.join(
|
fake_temp_image_path = os.path.join(
|
||||||
|
@ -772,7 +788,8 @@ class WindowsSmbFsTestCase(test.TestCase):
|
||||||
|
|
||||||
fake_upload_volume.assert_called_once_with(
|
fake_upload_volume.assert_called_once_with(
|
||||||
mock.sentinel.context, mock.sentinel.image_service,
|
mock.sentinel.context, mock.sentinel.image_service,
|
||||||
fake_image_meta, upload_path, fake_img_format)
|
fake_image_meta, upload_path, fake_img_format,
|
||||||
|
store_id='fake-store')
|
||||||
|
|
||||||
@mock.patch.object(smbfs.WindowsSmbfsDriver, '_get_vhd_type')
|
@mock.patch.object(smbfs.WindowsSmbfsDriver, '_get_vhd_type')
|
||||||
def test_copy_image_to_volume(self, mock_get_vhd_type):
|
def test_copy_image_to_volume(self, mock_get_vhd_type):
|
||||||
|
|
|
@ -899,12 +899,16 @@ class BaseVD(object):
|
||||||
enforce_multipath)
|
enforce_multipath)
|
||||||
attach_info, volume = self._attach_volume(context, volume, properties)
|
attach_info, volume = self._attach_volume(context, volume, properties)
|
||||||
|
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image_utils.upload_volume(context,
|
image_utils.upload_volume(context,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
attach_info['device']['path'],
|
attach_info['device']['path'],
|
||||||
compress=True)
|
compress=True,
|
||||||
|
store_id=store_id)
|
||||||
finally:
|
finally:
|
||||||
# Since attached volume was not used for writing we can force
|
# Since attached volume was not used for writing we can force
|
||||||
# detach it
|
# detach it
|
||||||
|
|
|
@ -1096,11 +1096,14 @@ class VxFlexOSDriver(driver.VolumeDriver):
|
||||||
{'vol': volume,
|
{'vol': volume,
|
||||||
'service': six.text_type(image_service),
|
'service': six.text_type(image_service),
|
||||||
'meta': six.text_type(image_meta)})
|
'meta': six.text_type(image_meta)})
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
try:
|
try:
|
||||||
image_utils.upload_volume(context,
|
image_utils.upload_volume(context,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
self._sio_attach_volume(volume))
|
self._sio_attach_volume(volume),
|
||||||
|
store_id=store_id)
|
||||||
finally:
|
finally:
|
||||||
self._sio_detach_volume(volume)
|
self._sio_detach_volume(volume)
|
||||||
|
|
||||||
|
|
|
@ -994,10 +994,13 @@ class GPFSDriver(driver.CloneableImageVD,
|
||||||
|
|
||||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||||
"""Copy the volume to the specified image."""
|
"""Copy the volume to the specified image."""
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
image_utils.upload_volume(context,
|
image_utils.upload_volume(context,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
self.local_path(volume))
|
self.local_path(volume),
|
||||||
|
store_id=store_id)
|
||||||
|
|
||||||
def _migrate_volume(self, volume, host):
|
def _migrate_volume(self, volume, host):
|
||||||
"""Migrate vol if source and dest are managed by same GPFS cluster."""
|
"""Migrate vol if source and dest are managed by same GPFS cluster."""
|
||||||
|
|
|
@ -661,12 +661,14 @@ class LinstorBaseDriver(driver.VolumeDriver):
|
||||||
# Check if all replies are success
|
# Check if all replies are success
|
||||||
return lin_drv.all_api_responses_success(api_response)
|
return lin_drv.all_api_responses_success(api_response)
|
||||||
|
|
||||||
def _copy_vol_to_image(self, context, image_service, image_meta, rsc_path):
|
def _copy_vol_to_image(self, context, image_service, image_meta, rsc_path,
|
||||||
|
store_id=None):
|
||||||
|
|
||||||
return image_utils.upload_volume(context,
|
return image_utils.upload_volume(context,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
rsc_path)
|
rsc_path,
|
||||||
|
store_id=store_id)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Snapshot
|
# Snapshot
|
||||||
|
@ -978,11 +980,13 @@ class LinstorBaseDriver(driver.VolumeDriver):
|
||||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||||
full_rsc_name = self._drbd_resource_name_from_cinder_volume(volume)
|
full_rsc_name = self._drbd_resource_name_from_cinder_volume(volume)
|
||||||
rsc_path = str(self._get_rsc_path(full_rsc_name))
|
rsc_path = str(self._get_rsc_path(full_rsc_name))
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
self._copy_vol_to_image(context,
|
self._copy_vol_to_image(context,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
rsc_path)
|
rsc_path,
|
||||||
|
store_id=store_id)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Not supported currently
|
# Not supported currently
|
||||||
|
|
|
@ -508,10 +508,14 @@ class LVMVolumeDriver(driver.VolumeDriver):
|
||||||
|
|
||||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||||
"""Copy the volume to the specified image."""
|
"""Copy the volume to the specified image."""
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
|
|
||||||
image_utils.upload_volume(context,
|
image_utils.upload_volume(context,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
self.local_path(volume))
|
self.local_path(volume),
|
||||||
|
store_id=store_id)
|
||||||
|
|
||||||
def create_cloned_volume(self, volume, src_vref):
|
def create_cloned_volume(self, volume, src_vref):
|
||||||
"""Creates a clone of the specified volume."""
|
"""Creates a clone of the specified volume."""
|
||||||
|
|
|
@ -1583,6 +1583,9 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
|
||||||
volume_id=volume.id)
|
volume_id=volume.id)
|
||||||
|
|
||||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
|
|
||||||
tmp_dir = volume_utils.image_conversion_dir()
|
tmp_dir = volume_utils.image_conversion_dir()
|
||||||
tmp_file = os.path.join(tmp_dir,
|
tmp_file = os.path.join(tmp_dir,
|
||||||
volume.name + '-' + image_meta['id'])
|
volume.name + '-' + image_meta['id'])
|
||||||
|
@ -1593,7 +1596,8 @@ class RBDDriver(driver.CloneableImageVD, driver.MigrateVD,
|
||||||
args.extend(self._ceph_args())
|
args.extend(self._ceph_args())
|
||||||
self._try_execute(*args)
|
self._try_execute(*args)
|
||||||
image_utils.upload_volume(context, image_service,
|
image_utils.upload_volume(context, image_service,
|
||||||
image_meta, tmp_file)
|
image_meta, tmp_file,
|
||||||
|
store_id=store_id)
|
||||||
os.unlink(tmp_file)
|
os.unlink(tmp_file)
|
||||||
|
|
||||||
def extend_volume(self, volume, new_size):
|
def extend_volume(self, volume, new_size):
|
||||||
|
|
|
@ -474,11 +474,13 @@ class RemoteFSDriver(driver.BaseVD):
|
||||||
|
|
||||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||||
"""Copy the volume to the specified image."""
|
"""Copy the volume to the specified image."""
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
image_utils.upload_volume(context,
|
image_utils.upload_volume(context,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
self.local_path(volume),
|
self.local_path(volume),
|
||||||
run_as_root=self._execute_as_root)
|
run_as_root=self._execute_as_root,
|
||||||
|
store_id=store_id)
|
||||||
|
|
||||||
def _read_config_file(self, config_file):
|
def _read_config_file(self, config_file):
|
||||||
# Returns list of lines in file
|
# Returns list of lines in file
|
||||||
|
@ -945,7 +947,7 @@ class RemoteFSSnapDriverBase(RemoteFSDriver):
|
||||||
return self.base
|
return self.base
|
||||||
|
|
||||||
def _copy_volume_to_image(self, context, volume, image_service,
|
def _copy_volume_to_image(self, context, volume, image_service,
|
||||||
image_meta):
|
image_meta, store_id=None):
|
||||||
"""Copy the volume to the specified image."""
|
"""Copy the volume to the specified image."""
|
||||||
|
|
||||||
# If snapshots exist, flatten to a temporary image, and upload it
|
# If snapshots exist, flatten to a temporary image, and upload it
|
||||||
|
@ -973,11 +975,15 @@ class RemoteFSSnapDriverBase(RemoteFSDriver):
|
||||||
else:
|
else:
|
||||||
upload_path = active_file_path
|
upload_path = active_file_path
|
||||||
|
|
||||||
|
if not store_id:
|
||||||
|
store_id = volume.volume_type.extra_specs.get(
|
||||||
|
'image_service:store_id')
|
||||||
image_utils.upload_volume(context,
|
image_utils.upload_volume(context,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
upload_path,
|
upload_path,
|
||||||
run_as_root=self._execute_as_root)
|
run_as_root=self._execute_as_root,
|
||||||
|
store_id=store_id)
|
||||||
|
|
||||||
def get_active_image_from_info(self, volume):
|
def get_active_image_from_info(self, volume):
|
||||||
"""Returns filename of the active image from the info file."""
|
"""Returns filename of the active image from the info file."""
|
||||||
|
|
|
@ -358,7 +358,8 @@ class SPDKDriver(driver.VolumeDriver):
|
||||||
|
|
||||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||||
"""Copy the volume to the specified image."""
|
"""Copy the volume to the specified image."""
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
volume['provider_location'] = (
|
volume['provider_location'] = (
|
||||||
self.create_export(context, volume, None)['provider_location'])
|
self.create_export(context, volume, None)['provider_location'])
|
||||||
connection_data = self.initialize_connection(volume, None)['data']
|
connection_data = self.initialize_connection(volume, None)['data']
|
||||||
|
@ -378,7 +379,8 @@ class SPDKDriver(driver.VolumeDriver):
|
||||||
image_utils.upload_volume(context,
|
image_utils.upload_volume(context,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
device_info['path'])
|
device_info['path'],
|
||||||
|
store_id=store_id)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
target_connector.disconnect_volume(connection_data, volume)
|
target_connector.disconnect_volume(connection_data, volume)
|
||||||
|
|
|
@ -257,6 +257,11 @@ class VMwareVStorageObjectDriver(vmdk.VMwareVcVmdkDriver):
|
||||||
|
|
||||||
vmdk_file_path = self.volumeops.get_vmdk_path(backing)
|
vmdk_file_path = self.volumeops.get_vmdk_path(backing)
|
||||||
conf = self.configuration
|
conf = self.configuration
|
||||||
|
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get(
|
||||||
|
'image_service:store_id')
|
||||||
|
|
||||||
image_transfer.upload_image(
|
image_transfer.upload_image(
|
||||||
context,
|
context,
|
||||||
conf.vmware_image_transfer_timeout_secs,
|
conf.vmware_image_transfer_timeout_secs,
|
||||||
|
@ -269,7 +274,8 @@ class VMwareVStorageObjectDriver(vmdk.VMwareVcVmdkDriver):
|
||||||
vm=backing,
|
vm=backing,
|
||||||
vmdk_file_path=vmdk_file_path,
|
vmdk_file_path=vmdk_file_path,
|
||||||
vmdk_size=volume.size * units.Gi,
|
vmdk_size=volume.size * units.Gi,
|
||||||
image_name=image_meta['name'])
|
image_name=image_meta['name'],
|
||||||
|
store_id=store_id)
|
||||||
finally:
|
finally:
|
||||||
if attached:
|
if attached:
|
||||||
self.volumeops.detach_fcd(backing, fcd_loc)
|
self.volumeops.detach_fcd(backing, fcd_loc)
|
||||||
|
|
|
@ -1404,6 +1404,9 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
|
||||||
host_ip = self.configuration.vmware_host_ip
|
host_ip = self.configuration.vmware_host_ip
|
||||||
port = self.configuration.vmware_host_port
|
port = self.configuration.vmware_host_port
|
||||||
|
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
|
|
||||||
image_transfer.upload_image(context,
|
image_transfer.upload_image(context,
|
||||||
timeout,
|
timeout,
|
||||||
image_service,
|
image_service,
|
||||||
|
@ -1416,7 +1419,8 @@ class VMwareVcVmdkDriver(driver.VolumeDriver):
|
||||||
vmdk_file_path=vmdk_file_path,
|
vmdk_file_path=vmdk_file_path,
|
||||||
vmdk_size=volume['size'] * units.Gi,
|
vmdk_size=volume['size'] * units.Gi,
|
||||||
image_name=image_meta['name'],
|
image_name=image_meta['name'],
|
||||||
image_version=1)
|
image_version=1,
|
||||||
|
store_id=store_id)
|
||||||
LOG.info("Done copying volume %(vol)s to a new image %(img)s",
|
LOG.info("Done copying volume %(vol)s to a new image %(img)s",
|
||||||
{'vol': volume['name'], 'img': image_meta['name']})
|
{'vol': volume['name'], 'img': image_meta['name']})
|
||||||
|
|
||||||
|
|
|
@ -285,6 +285,8 @@ class WindowsISCSIDriver(driver.ISCSIDriver):
|
||||||
|
|
||||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||||
"""Copy the volume to the specified image."""
|
"""Copy the volume to the specified image."""
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
disk_format = self._tgt_utils.get_supported_disk_format()
|
disk_format = self._tgt_utils.get_supported_disk_format()
|
||||||
temp_vhd_path = os.path.join(CONF.image_conversion_dir,
|
temp_vhd_path = os.path.join(CONF.image_conversion_dir,
|
||||||
str(image_meta['id']) + '.' + disk_format)
|
str(image_meta['id']) + '.' + disk_format)
|
||||||
|
@ -295,7 +297,8 @@ class WindowsISCSIDriver(driver.ISCSIDriver):
|
||||||
# must be exported first.
|
# must be exported first.
|
||||||
self._tgt_utils.export_snapshot(tmp_snap_name, temp_vhd_path)
|
self._tgt_utils.export_snapshot(tmp_snap_name, temp_vhd_path)
|
||||||
image_utils.upload_volume(context, image_service, image_meta,
|
image_utils.upload_volume(context, image_service, image_meta,
|
||||||
temp_vhd_path, 'vhd')
|
temp_vhd_path, 'vhd',
|
||||||
|
store_id=store_id)
|
||||||
finally:
|
finally:
|
||||||
fileutils.delete_if_exists(temp_vhd_path)
|
fileutils.delete_if_exists(temp_vhd_path)
|
||||||
|
|
||||||
|
|
|
@ -553,6 +553,8 @@ class WindowsSmbfsDriver(remotefs_drv.RevertToSnapshotMixin,
|
||||||
@coordination.synchronized('{self.driver_prefix}-{volume.id}')
|
@coordination.synchronized('{self.driver_prefix}-{volume.id}')
|
||||||
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
||||||
"""Copy the volume to the specified image."""
|
"""Copy the volume to the specified image."""
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
|
|
||||||
# If snapshots exist, flatten to a temporary image, and upload it
|
# If snapshots exist, flatten to a temporary image, and upload it
|
||||||
|
|
||||||
|
@ -582,7 +584,8 @@ class WindowsSmbfsDriver(remotefs_drv.RevertToSnapshotMixin,
|
||||||
image_service,
|
image_service,
|
||||||
image_meta,
|
image_meta,
|
||||||
upload_path,
|
upload_path,
|
||||||
root_file_fmt)
|
root_file_fmt,
|
||||||
|
store_id=store_id)
|
||||||
finally:
|
finally:
|
||||||
if temp_path:
|
if temp_path:
|
||||||
self._delete(temp_path)
|
self._delete(temp_path)
|
||||||
|
|
|
@ -1607,9 +1607,17 @@ class VolumeManager(manager.CleanableManager,
|
||||||
|
|
||||||
uri = 'cinder://%s' % image_volume.id
|
uri = 'cinder://%s' % image_volume.id
|
||||||
image_registered = None
|
image_registered = None
|
||||||
|
|
||||||
|
# retrieve store information from extra-specs
|
||||||
|
store_id = volume.volume_type.extra_specs.get('image_service:store_id')
|
||||||
|
location_metadata = {}
|
||||||
|
|
||||||
|
if store_id:
|
||||||
|
location_metadata['store'] = store_id
|
||||||
|
|
||||||
try:
|
try:
|
||||||
image_registered = image_service.add_location(
|
image_registered = image_service.add_location(
|
||||||
ctx, image_meta['id'], uri, {})
|
ctx, image_meta['id'], uri, location_metadata)
|
||||||
except (exception.NotAuthorized, exception.Invalid,
|
except (exception.NotAuthorized, exception.Invalid,
|
||||||
exception.NotFound):
|
exception.NotFound):
|
||||||
LOG.exception('Failed to register image volume location '
|
LOG.exception('Failed to register image volume location '
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
This release includes support for Glance multiple stores. An
|
||||||
|
operator may now specify which Glance store will be used when
|
||||||
|
a volume is uploaded to Glance as an image. Some details
|
||||||
|
about this feature:
|
||||||
|
|
||||||
|
* This feature is not directly user-facing. To enable it, an
|
||||||
|
operator must add the field ``image_service:store_id`` in the
|
||||||
|
volume-type extra-specs. The value of the field is a valid
|
||||||
|
store identifier (``id``) configured in Glance, which may be
|
||||||
|
discovered by making a ``GET /v2/info/stores`` call to the
|
||||||
|
Image Service API.
|
||||||
|
* If ``image_service:store_id`` is not set in the extra-specs for a
|
||||||
|
volume-type, then any volume of that type uploaded as an image will
|
||||||
|
be uploaded to the default store in Glance.
|
||||||
|
* The ``image_service:store_id`` can only be set in the extra-specs
|
||||||
|
for a volume-type when multiple glance stores are configured.
|
||||||
|
* Cinder validates proposed Glance store identifiers by contacting
|
||||||
|
Glance at the time the ``image_service:store_id`` is added to a
|
||||||
|
volume-type's extra-specs. Thus the Image Service API must be
|
||||||
|
available when a volume-type is updated.
|
Loading…
Reference in New Issue