Merge "Add support to get disk_formats from glance"
This commit is contained in:
commit
3a829be260
@ -29,11 +29,13 @@ from django.conf import settings
|
|||||||
from django.core.files.uploadedfile import InMemoryUploadedFile
|
from django.core.files.uploadedfile import InMemoryUploadedFile
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.core.files.uploadedfile import TemporaryUploadedFile
|
from django.core.files.uploadedfile import TemporaryUploadedFile
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from glanceclient.v2 import client
|
from glanceclient.v2 import client
|
||||||
import six
|
import six
|
||||||
from six.moves import _thread as thread
|
from six.moves import _thread as thread
|
||||||
|
|
||||||
|
from horizon import messages
|
||||||
from horizon.utils.memoized import memoized
|
from horizon.utils.memoized import memoized
|
||||||
from openstack_dashboard.api import base
|
from openstack_dashboard.api import base
|
||||||
from openstack_dashboard.contrib.developer.profiler import api as profiler
|
from openstack_dashboard.contrib.developer.profiler import api as profiler
|
||||||
@ -345,6 +347,32 @@ def image_update(request, image_id, **kwargs):
|
|||||||
{'file': filename, 'e': e})
|
{'file': filename, 'e': e})
|
||||||
|
|
||||||
|
|
||||||
|
def get_image_formats(request):
|
||||||
|
image_format_choices = settings.OPENSTACK_IMAGE_BACKEND['image_formats']
|
||||||
|
try:
|
||||||
|
glance_schemas = get_image_schemas(request)
|
||||||
|
glance_formats = \
|
||||||
|
glance_schemas['properties']['disk_format']['enum']
|
||||||
|
supported_formats = []
|
||||||
|
for value, name in image_format_choices:
|
||||||
|
if value in glance_formats:
|
||||||
|
supported_formats.append((value, name))
|
||||||
|
else:
|
||||||
|
LOG.warning('OPENSTACK_IMAGE_BACKEND has a format "%s" '
|
||||||
|
'unsupported by glance', value)
|
||||||
|
except Exception:
|
||||||
|
supported_formats = image_format_choices
|
||||||
|
msg = _('Unable to retrieve image format list.')
|
||||||
|
messages.error(request, msg)
|
||||||
|
|
||||||
|
return supported_formats
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace
|
||||||
|
def get_image_schemas(request):
|
||||||
|
return glanceclient(request).schemas.get('image').raw()
|
||||||
|
|
||||||
|
|
||||||
def get_image_upload_mode():
|
def get_image_upload_mode():
|
||||||
mode = settings.HORIZON_IMAGES_UPLOAD_MODE
|
mode = settings.HORIZON_IMAGES_UPLOAD_MODE
|
||||||
if mode not in ('off', 'legacy', 'direct'):
|
if mode not in ('off', 'legacy', 'direct'):
|
||||||
|
@ -58,8 +58,18 @@ class Settings(generic.View):
|
|||||||
plain_settings = {k: getattr(settings, k, None) for k
|
plain_settings = {k: getattr(settings, k, None) for k
|
||||||
in settings_allowed if k not in self.SPECIALS}
|
in settings_allowed if k not in self.SPECIALS}
|
||||||
plain_settings.update(self.SPECIALS)
|
plain_settings.update(self.SPECIALS)
|
||||||
|
plain_settings.update(self.disk_formats(request))
|
||||||
return plain_settings
|
return plain_settings
|
||||||
|
|
||||||
|
def disk_formats(self, request):
|
||||||
|
# The purpose of OPENSTACK_IMAGE_FORMATS is to provide a simple object
|
||||||
|
# that does not contain the lazy-loaded translations, so the list can
|
||||||
|
# be sent as JSON to the client-side (Angular).
|
||||||
|
return {'OPENSTACK_IMAGE_FORMATS': [
|
||||||
|
value
|
||||||
|
for (value, name) in api.glance.get_image_formats(request)
|
||||||
|
]}
|
||||||
|
|
||||||
|
|
||||||
@urls.register
|
@urls.register
|
||||||
class Timezones(generic.View):
|
class Timezones(generic.View):
|
||||||
|
@ -27,12 +27,15 @@ INDEX_TEMPLATE = 'horizon/common/_data_table_view.html'
|
|||||||
|
|
||||||
|
|
||||||
class ImageCreateViewTest(test.BaseAdminViewTests):
|
class ImageCreateViewTest(test.BaseAdminViewTests):
|
||||||
|
@mock.patch.object(api.glance, 'get_image_schemas')
|
||||||
@mock.patch.object(api.glance, 'image_list_detailed')
|
@mock.patch.object(api.glance, 'image_list_detailed')
|
||||||
def test_admin_image_create_view_uses_admin_template(self,
|
def test_admin_image_create_view_uses_admin_template(self,
|
||||||
mock_image_list):
|
mock_image_list,
|
||||||
|
mock_schemas_list):
|
||||||
filters1 = {'disk_format': 'aki'}
|
filters1 = {'disk_format': 'aki'}
|
||||||
filters2 = {'disk_format': 'ari'}
|
filters2 = {'disk_format': 'ari'}
|
||||||
|
|
||||||
|
mock_schemas_list.return_value = self.image_schemas.first()
|
||||||
mock_image_list.return_value = [self.images.list(), False, False]
|
mock_image_list.return_value = [self.images.list(), False, False]
|
||||||
|
|
||||||
res = self.client.get(
|
res = self.client.get(
|
||||||
|
@ -174,7 +174,8 @@ class CreateImageForm(CreateParent):
|
|||||||
if not policy.check((("image", "publicize_image"),), request):
|
if not policy.check((("image", "publicize_image"),), request):
|
||||||
self._hide_is_public()
|
self._hide_is_public()
|
||||||
|
|
||||||
self.fields['disk_format'].choices = IMAGE_FORMAT_CHOICES
|
self.fields['disk_format'].choices = \
|
||||||
|
api.glance.get_image_formats(request)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
kernel_images = api.glance.image_list_detailed(
|
kernel_images = api.glance.image_list_detailed(
|
||||||
|
@ -38,8 +38,9 @@ IMAGES_INDEX_URL = reverse('horizon:project:images:index')
|
|||||||
|
|
||||||
|
|
||||||
class CreateImageFormTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
class CreateImageFormTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
||||||
|
@mock.patch.object(api.glance, 'get_image_formats')
|
||||||
@mock.patch.object(api.glance, 'image_list_detailed')
|
@mock.patch.object(api.glance, 'image_list_detailed')
|
||||||
def test_no_location_or_file(self, mock_image_list):
|
def test_no_location_or_file(self, mock_image_list, mock_schemas_list):
|
||||||
mock_image_list.side_effect = [
|
mock_image_list.side_effect = [
|
||||||
[self.images.list(), False, False],
|
[self.images.list(), False, False],
|
||||||
[self.images.list(), False, False]
|
[self.images.list(), False, False]
|
||||||
@ -133,8 +134,9 @@ class UpdateImageFormTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class ImageViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
class ImageViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
||||||
|
@mock.patch.object(api.glance, 'get_image_schemas')
|
||||||
@mock.patch.object(api.glance, 'image_list_detailed')
|
@mock.patch.object(api.glance, 'image_list_detailed')
|
||||||
def test_image_create_get(self, mock_image_list):
|
def test_image_create_get(self, mock_image_list, mock_schemas_list):
|
||||||
mock_image_list.side_effect = [
|
mock_image_list.side_effect = [
|
||||||
[self.images.list(), False, False],
|
[self.images.list(), False, False],
|
||||||
[self.images.list(), False, False]
|
[self.images.list(), False, False]
|
||||||
@ -151,7 +153,9 @@ class ImageViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||||||
mock_image_list.assert_has_calls(image_calls)
|
mock_image_list.assert_has_calls(image_calls)
|
||||||
|
|
||||||
@override_settings(IMAGES_ALLOW_LOCATION=True)
|
@override_settings(IMAGES_ALLOW_LOCATION=True)
|
||||||
def test_image_create_post_location_v2(self):
|
@mock.patch.object(api.glance, 'get_image_schemas')
|
||||||
|
def test_image_create_post_location_v2(self, mock_schemas_list):
|
||||||
|
mock_schemas_list.return_value = self.image_schemas.first()
|
||||||
data = {
|
data = {
|
||||||
'source_type': u'url',
|
'source_type': u'url',
|
||||||
'image_url': u'http://cloud-images.ubuntu.com/releases/'
|
'image_url': u'http://cloud-images.ubuntu.com/releases/'
|
||||||
@ -161,7 +165,9 @@ class ImageViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||||||
api_data = {'location': data['image_url']}
|
api_data = {'location': data['image_url']}
|
||||||
self._test_image_create(data, api_data)
|
self._test_image_create(data, api_data)
|
||||||
|
|
||||||
def test_image_create_post_upload_v2(self):
|
@mock.patch.object(api.glance, 'get_image_schemas')
|
||||||
|
def test_image_create_post_upload_v2(self, mock_schemas_list):
|
||||||
|
mock_schemas_list.return_value = self.image_schemas.first()
|
||||||
temp_file = tempfile.NamedTemporaryFile()
|
temp_file = tempfile.NamedTemporaryFile()
|
||||||
temp_file.write(b'123')
|
temp_file.write(b'123')
|
||||||
temp_file.flush()
|
temp_file.flush()
|
||||||
@ -173,7 +179,9 @@ class ImageViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||||||
api_data = {'data': test.IsA(InMemoryUploadedFile)}
|
api_data = {'data': test.IsA(InMemoryUploadedFile)}
|
||||||
self._test_image_create(data, api_data)
|
self._test_image_create(data, api_data)
|
||||||
|
|
||||||
def test_image_create_post_with_kernel_ramdisk_v2(self):
|
@mock.patch.object(api.glance, 'get_image_schemas')
|
||||||
|
def test_image_create_post_with_kernel_ramdisk_v2(self, mock_schemas_list):
|
||||||
|
mock_schemas_list.return_value = self.image_schemas.first()
|
||||||
temp_file = tempfile.NamedTemporaryFile()
|
temp_file = tempfile.NamedTemporaryFile()
|
||||||
temp_file.write(b'123')
|
temp_file.write(b'123')
|
||||||
temp_file.flush()
|
temp_file.flush()
|
||||||
|
@ -719,7 +719,7 @@ class UploadToImageForm(forms.SelfHandlingForm):
|
|||||||
# I can only use 'raw', 'vmdk', 'vdi' or 'qcow2' so qemu-img will not
|
# I can only use 'raw', 'vmdk', 'vdi' or 'qcow2' so qemu-img will not
|
||||||
# have issues when processes image request from cinder.
|
# have issues when processes image request from cinder.
|
||||||
disk_format_choices = [(value, name) for value, name
|
disk_format_choices = [(value, name) for value, name
|
||||||
in IMAGE_FORMAT_CHOICES
|
in glance.get_image_formats(request)
|
||||||
if value in VALID_DISK_FORMATS]
|
if value in VALID_DISK_FORMATS]
|
||||||
self.fields['disk_format'].choices = disk_format_choices
|
self.fields['disk_format'].choices = disk_format_choices
|
||||||
self.fields['disk_format'].initial = 'raw'
|
self.fields['disk_format'].initial = 'raw'
|
||||||
|
@ -1668,9 +1668,10 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||||||
self.mock_volume_set_bootable.assert_called_once_with(
|
self.mock_volume_set_bootable.assert_called_once_with(
|
||||||
test.IsHttpRequest(), volume.id, True)
|
test.IsHttpRequest(), volume.id, True)
|
||||||
|
|
||||||
|
@mock.patch.object(api.glance, 'get_image_schemas')
|
||||||
@mock.patch.object(cinder, 'volume_upload_to_image')
|
@mock.patch.object(cinder, 'volume_upload_to_image')
|
||||||
@mock.patch.object(cinder, 'volume_get')
|
@mock.patch.object(cinder, 'volume_get')
|
||||||
def test_upload_to_image(self, mock_get, mock_upload):
|
def test_upload_to_image(self, mock_get, mock_upload, mock_schemas_list):
|
||||||
volume = self.cinder_volumes.get(name='v2_volume')
|
volume = self.cinder_volumes.get(name='v2_volume')
|
||||||
loaded_resp = {'container_format': 'bare',
|
loaded_resp = {'container_format': 'bare',
|
||||||
'disk_format': 'raw',
|
'disk_format': 'raw',
|
||||||
@ -1687,6 +1688,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||||||
'container_format': 'bare',
|
'container_format': 'bare',
|
||||||
'disk_format': 'raw'}
|
'disk_format': 'raw'}
|
||||||
|
|
||||||
|
mock_schemas_list.return_value = self.image_schemas.first()
|
||||||
mock_get.return_value = volume
|
mock_get.return_value = volume
|
||||||
mock_upload.return_value = loaded_resp
|
mock_upload.return_value = loaded_resp
|
||||||
|
|
||||||
|
@ -278,14 +278,6 @@ if os.path.exists(LOCAL_SETTINGS_DIR_PATH):
|
|||||||
_LOG.exception(
|
_LOG.exception(
|
||||||
"Can not exec settings snippet %s", filename)
|
"Can not exec settings snippet %s", filename)
|
||||||
|
|
||||||
# The purpose of OPENSTACK_IMAGE_FORMATS is to provide a simple object
|
|
||||||
# that does not contain the lazy-loaded translations, so the list can
|
|
||||||
# be sent as JSON to the client-side (Angular).
|
|
||||||
# TODO(amotoki): Do we really need this here? Can't we calculate this
|
|
||||||
# in openstack_dashboard.api.rest.config?
|
|
||||||
OPENSTACK_IMAGE_FORMATS = [fmt for (fmt, name)
|
|
||||||
in OPENSTACK_IMAGE_BACKEND['image_formats']]
|
|
||||||
|
|
||||||
if USER_MENU_LINKS is None:
|
if USER_MENU_LINKS is None:
|
||||||
USER_MENU_LINKS = []
|
USER_MENU_LINKS = []
|
||||||
if SHOW_OPENRC_FILE:
|
if SHOW_OPENRC_FILE:
|
||||||
|
@ -52,6 +52,7 @@ def data(TEST):
|
|||||||
TEST.images_api = utils.TestDataContainer()
|
TEST.images_api = utils.TestDataContainer()
|
||||||
TEST.snapshots = utils.TestDataContainer()
|
TEST.snapshots = utils.TestDataContainer()
|
||||||
TEST.metadata_defs = utils.TestDataContainer()
|
TEST.metadata_defs = utils.TestDataContainer()
|
||||||
|
TEST.image_schemas = utils.TestDataContainer()
|
||||||
TEST.imagesV2 = utils.TestDataContainer()
|
TEST.imagesV2 = utils.TestDataContainer()
|
||||||
TEST.snapshotsV2 = utils.TestDataContainer()
|
TEST.snapshotsV2 = utils.TestDataContainer()
|
||||||
|
|
||||||
@ -479,3 +480,148 @@ def data(TEST):
|
|||||||
}
|
}
|
||||||
metadef = Namespace(metadef_dict)
|
metadef = Namespace(metadef_dict)
|
||||||
TEST.metadata_defs.add(metadef)
|
TEST.metadata_defs.add(metadef)
|
||||||
|
|
||||||
|
image_schemas_dict = {
|
||||||
|
'additionalProperties': {'type': 'string'},
|
||||||
|
'links': [
|
||||||
|
{'href': '{self}', 'rel': 'self'},
|
||||||
|
{'href': '{file}', 'rel': 'enclosure'},
|
||||||
|
{'href': '{schema}', 'rel': 'describedby'}
|
||||||
|
],
|
||||||
|
'name': 'image',
|
||||||
|
'properties': {
|
||||||
|
'architecture': {
|
||||||
|
'is_base': False,
|
||||||
|
'type': 'string'
|
||||||
|
},
|
||||||
|
'checksum': {
|
||||||
|
'maxLength': 32,
|
||||||
|
'readOnly': True,
|
||||||
|
'type': ['null', 'string']
|
||||||
|
},
|
||||||
|
'container_format': {
|
||||||
|
'enum': [
|
||||||
|
None,
|
||||||
|
'ami',
|
||||||
|
'ari',
|
||||||
|
'aki',
|
||||||
|
'bare',
|
||||||
|
'ovf',
|
||||||
|
'ova',
|
||||||
|
'docker',
|
||||||
|
'compressed'
|
||||||
|
],
|
||||||
|
'type': ['null', 'string']
|
||||||
|
},
|
||||||
|
'created_at': {'readOnly': True, 'type': 'string'},
|
||||||
|
'direct_url': {'readOnly': True, 'type': 'string'},
|
||||||
|
'disk_format': {
|
||||||
|
'enum': [None, 'raw', 'qcow2'],
|
||||||
|
'type': ['null', 'string']
|
||||||
|
},
|
||||||
|
'file': {'readOnly': True, 'type': 'string'},
|
||||||
|
'id': {'type': 'string'},
|
||||||
|
'instance_uuid': {'is_base': False, 'type': 'string'},
|
||||||
|
'kernel_id': {
|
||||||
|
'is_base': False,
|
||||||
|
'type': ['null', 'string']
|
||||||
|
},
|
||||||
|
'locations': {
|
||||||
|
'items': {
|
||||||
|
'properties': {
|
||||||
|
'metadata': {'type': 'object'},
|
||||||
|
'url': {'maxLength': 255, 'type': 'string'},
|
||||||
|
'validation_data': {
|
||||||
|
'additionalProperties': False,
|
||||||
|
'properties': {
|
||||||
|
'checksum': {
|
||||||
|
'maxLength': 32,
|
||||||
|
'minLength': 32,
|
||||||
|
'type': 'string'
|
||||||
|
},
|
||||||
|
'os_hash_algo': {
|
||||||
|
'maxLength': 64,
|
||||||
|
'type': 'string'
|
||||||
|
},
|
||||||
|
'os_hash_value': {
|
||||||
|
'maxLength': 128,
|
||||||
|
'type': 'string'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'required': ['os_hash_algo', 'os_hash_value'],
|
||||||
|
'type': 'object',
|
||||||
|
'writeOnly': True
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'required': ['url', 'metadata'],
|
||||||
|
'type': 'object'
|
||||||
|
},
|
||||||
|
'type': 'array'
|
||||||
|
},
|
||||||
|
'min_disk': {'type': 'integer'},
|
||||||
|
'min_ram': {'type': 'integer'},
|
||||||
|
'name': {'maxLength': 255, 'type': ['null', 'string']},
|
||||||
|
'os_distro': {'is_base': False, 'type': 'string'},
|
||||||
|
'os_hash_algo': {
|
||||||
|
'maxLength': 64,
|
||||||
|
'readOnly': True,
|
||||||
|
'type': ['null', 'string']
|
||||||
|
},
|
||||||
|
'os_hash_value': {
|
||||||
|
'maxLength': 128,
|
||||||
|
'readOnly': True,
|
||||||
|
'type': ['null', 'string']
|
||||||
|
},
|
||||||
|
'os_hidden': {'type': 'boolean'},
|
||||||
|
'os_version': {'is_base': False, 'type': 'string'},
|
||||||
|
'owner': {
|
||||||
|
'description': 'Owner of the image',
|
||||||
|
'maxLength': 255,
|
||||||
|
'type': ['null', 'string']
|
||||||
|
},
|
||||||
|
'protected': {'type': 'boolean'},
|
||||||
|
'ramdisk_id': {
|
||||||
|
'is_base': False,
|
||||||
|
'type': ['null', 'string']
|
||||||
|
},
|
||||||
|
'schema': {'readOnly': True, 'type': 'string'},
|
||||||
|
'self': {'readOnly': True, 'type': 'string'},
|
||||||
|
'size': {'readOnly': True, 'type': ['null', 'integer']},
|
||||||
|
'status': {
|
||||||
|
'enum': [
|
||||||
|
'queued',
|
||||||
|
'saving',
|
||||||
|
'active',
|
||||||
|
'killed',
|
||||||
|
'deleted',
|
||||||
|
'uploading',
|
||||||
|
'importing',
|
||||||
|
'pending_delete',
|
||||||
|
'deactivated'
|
||||||
|
],
|
||||||
|
'readOnly': True,
|
||||||
|
'type': 'string'
|
||||||
|
},
|
||||||
|
'stores': {'readOnly': True, 'type': 'string'},
|
||||||
|
'tags': {
|
||||||
|
'items': {'maxLength': 255, 'type': 'string'},
|
||||||
|
'type': 'array'
|
||||||
|
},
|
||||||
|
'updated_at': {'readOnly': True, 'type': 'string'},
|
||||||
|
'virtual_size': {
|
||||||
|
'readOnly': True,
|
||||||
|
'type': ['null', 'integer']
|
||||||
|
},
|
||||||
|
'visibility': {
|
||||||
|
'enum': [
|
||||||
|
'community',
|
||||||
|
'public',
|
||||||
|
'private',
|
||||||
|
'shared'
|
||||||
|
],
|
||||||
|
'type': 'string'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schemas = Namespace(image_schemas_dict)
|
||||||
|
TEST.image_schemas.add(schemas)
|
||||||
|
@ -12,15 +12,18 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from openstack_dashboard.api.rest import config
|
import mock
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.test import helpers as test
|
from openstack_dashboard.test import helpers as test
|
||||||
|
|
||||||
|
|
||||||
class ConfigRestTestCase(test.TestCase):
|
class ConfigRestTestCase(test.TestCase):
|
||||||
|
|
||||||
def test_settings_config_get(self):
|
@mock.patch.object(api.glance, 'get_image_schemas')
|
||||||
|
def test_settings_config_get(self, mock_schemas_list):
|
||||||
request = self.mock_rest_request()
|
request = self.mock_rest_request()
|
||||||
response = config.Settings().get(request)
|
response = api.rest.config.Settings().get(request)
|
||||||
self.assertStatusCode(response, 200)
|
self.assertStatusCode(response, 200)
|
||||||
self.assertIn(b"REST_API_SETTING_1", response.content)
|
self.assertIn(b"REST_API_SETTING_1", response.content)
|
||||||
self.assertIn(b"REST_API_SETTING_2", response.content)
|
self.assertIn(b"REST_API_SETTING_2", response.content)
|
||||||
|
@ -314,6 +314,19 @@ class GlanceApiTests(test.APIMockTestCase):
|
|||||||
mock_images_get.assert_called_once_with('empty')
|
mock_images_get.assert_called_once_with('empty')
|
||||||
self.assertIsNone(image.name)
|
self.assertIsNone(image.name)
|
||||||
|
|
||||||
|
@mock.patch.object(api.glance, 'glanceclient')
|
||||||
|
def test_get_image_formats(self, mock_glanceclient):
|
||||||
|
glance_schemas = self.image_schemas.first()
|
||||||
|
glanceclient = mock_glanceclient.return_value
|
||||||
|
mock_schemas_list = glanceclient.schemas.get('image').raw()
|
||||||
|
mock_schemas_list.return_value = glance_schemas
|
||||||
|
disk_formats = [
|
||||||
|
item
|
||||||
|
for item in glance_schemas['properties']['disk_format']['enum']
|
||||||
|
if item
|
||||||
|
]
|
||||||
|
self.assertListEqual(sorted(disk_formats), sorted(['raw', 'qcow2']))
|
||||||
|
|
||||||
@mock.patch.object(api.glance, 'glanceclient')
|
@mock.patch.object(api.glance, 'glanceclient')
|
||||||
def test_metadefs_namespace_list(self, mock_glanceclient):
|
def test_metadefs_namespace_list(self, mock_glanceclient):
|
||||||
metadata_defs = self.metadata_defs.list()
|
metadata_defs = self.metadata_defs.list()
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added support to retrieve supported disk formats from glance,
|
||||||
|
so you can adjust disk_formats only inside glance-api.conf.
|
||||||
|
You still can use IMAGE_BACKEND_SETTINGS to adjust format naming.
|
Loading…
Reference in New Issue
Block a user