Add API to Create/Update/Delete Images in Glance
Co-Authored-By: Matt Borland <matt.borland@hpe.com> Change-Id: I44360da3c30856dd875ab6b30c024b29dc4de4c7 Partially-Implements: blueprint angularize-images-table
This commit is contained in:
parent
ebec8331de
commit
ae6d42cbbd
@ -15,13 +15,13 @@
|
||||
"""
|
||||
|
||||
from six.moves import zip as izip
|
||||
|
||||
from django.views import generic
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api.rest import utils as rest_utils
|
||||
from openstack_dashboard.api.rest import urls
|
||||
|
||||
|
||||
CLIENT_KEYWORDS = {'resource_type', 'marker', 'sort_dir', 'sort_key', 'paginate'}
|
||||
|
||||
|
||||
@ -52,6 +52,43 @@ class Image(generic.View):
|
||||
"""
|
||||
return api.glance.image_get(request, image_id).to_dict()
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def patch(self, request, image_id):
|
||||
"""Update a specific image
|
||||
|
||||
Update an Image using the parameters supplied in the POST
|
||||
application/json object. The parameters are:
|
||||
|
||||
:param name: (required) the name to give the image
|
||||
:param description: (optional) description of the image
|
||||
:param disk_format: (required) format of the image
|
||||
:param kernel: (optional) kernel to use for the image
|
||||
:param ramdisk: (optional) Ramdisk to use for the image
|
||||
:param architecture: (optional) the Architecture of the image
|
||||
:param min_disk: (optional) the minimum disk size for the image to boot with
|
||||
:param min_ram: (optional) the minimum ram for the image to boot with
|
||||
:param visibility: (required) takes 'public', 'shared', and 'private'
|
||||
:param protected: (required) true if the image is protected
|
||||
|
||||
Any parameters not listed above will be assigned as custom properties
|
||||
for the image.
|
||||
|
||||
http://localhost/api/glance/images/cc758c90-3d98-4ea1-af44-aab405c9c915
|
||||
|
||||
"""
|
||||
meta = create_image_metadata(request.DATA)
|
||||
meta['purge_props'] = False
|
||||
|
||||
api.glance.image_update(request, image_id, **meta)
|
||||
|
||||
@rest_utils.ajax()
|
||||
def delete(self, request, image_id):
|
||||
"""Delete a specific image
|
||||
|
||||
DELETE http://localhost/api/glance/images/cc758c90-3d98-4ea1-af44-aab405c9c915
|
||||
"""
|
||||
api.glance.image_delete(request, image_id)
|
||||
|
||||
|
||||
@urls.register
|
||||
class ImageProperties(generic.View):
|
||||
@ -124,6 +161,46 @@ class Images(generic.View):
|
||||
'has_prev_data': has_prev_data,
|
||||
}
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def post(self, request):
|
||||
"""Create an Image.
|
||||
|
||||
Create an Image using the parameters supplied in the POST
|
||||
application/json object. The parameters are:
|
||||
|
||||
:param name: the name to give the image
|
||||
:param description: (optional) description of the image
|
||||
:param source_type: (required) source type. current only 'url' is supported
|
||||
:param image_url: (required) URL to get the image
|
||||
:param disk_format: (required) format of the image
|
||||
:param kernel: (optional) kernel to use for the image
|
||||
:param ramdisk: (optional) Ramdisk to use for the image
|
||||
:param architecture: (optional) the Architecture of the image
|
||||
:param min_disk: (optional) the minimum disk size for the image to boot with
|
||||
:param min_ram: (optional) the minimum ram for the image to boot with
|
||||
:param visibility: (required) takes 'public', 'private', and 'shared'
|
||||
:param protected: (required) true if the image is protected
|
||||
:param import_data: (optional) true to copy the image data to the image service
|
||||
or use it from the current location
|
||||
|
||||
Any parameters not listed above will be assigned as custom properties
|
||||
for the image.
|
||||
|
||||
This returns the new image object on success.
|
||||
"""
|
||||
meta = create_image_metadata(request.DATA)
|
||||
|
||||
if request.DATA.get('import_data'):
|
||||
meta['copy_from'] = request.DATA.get('image_url')
|
||||
else:
|
||||
meta['location'] = request.DATA.get('image_url')
|
||||
|
||||
image = api.glance.image_create(request, **meta)
|
||||
return rest_utils.CreatedResponse(
|
||||
'/api/glance/images/%s' % image.name,
|
||||
image.to_dict()
|
||||
)
|
||||
|
||||
|
||||
@urls.register
|
||||
class MetadefsNamespaces(generic.View):
|
||||
@ -175,3 +252,61 @@ class MetadefsNamespaces(generic.View):
|
||||
return dict(izip(names, api.glance.metadefs_namespace_full_list(
|
||||
request, filters=filters, **kwargs
|
||||
)))
|
||||
|
||||
def create_image_metadata(data):
|
||||
try:
|
||||
"""Use the given dict of image form data to generate the metadata used for
|
||||
creating the image in glance.
|
||||
"""
|
||||
|
||||
meta = {'protected': data.get('protected'),
|
||||
'min_disk': data.get('min_disk', 0),
|
||||
'min_ram': data.get('min_ram', 0),
|
||||
'name': data.get('name'),
|
||||
'disk_format': data.get('disk_format'),
|
||||
'container_format': data.get('container_format'),
|
||||
'properties': {}}
|
||||
|
||||
# 'description' and 'architecture' will be directly mapped
|
||||
# into the .properties by the handle_unknown_properties function.
|
||||
# 'kernel' and 'ramdisk' need to get specifically mapped for backwards
|
||||
# compatibility.
|
||||
if data.get('kernel'):
|
||||
meta['properties']['kernel_id'] = data.get('kernel')
|
||||
if data.get('ramdisk'):
|
||||
meta['properties']['ramdisk_id'] = data.get('ramdisk')
|
||||
handle_unknown_properties(data, meta)
|
||||
handle_visibility(data.get('visibility'), meta)
|
||||
|
||||
except KeyError as e:
|
||||
raise rest_utils.AjaxError(400, 'missing required parameter '
|
||||
"'%s'" % e.args[0])
|
||||
|
||||
return meta
|
||||
|
||||
def handle_unknown_properties(data, meta):
|
||||
# The Glance API takes in both known and unknown fields. Unknown fields
|
||||
# are assumed as metadata. To achieve this and continue to use the
|
||||
# existing horizon api wrapper, we need this function. This way, the
|
||||
# client REST mirrors the Glance API.
|
||||
known_props = ['visibility', 'protected', 'disk_format',
|
||||
'container_format', 'min_disk', 'min_ram', 'name',
|
||||
'properties', 'kernel', 'ramdisk',
|
||||
'tags', 'import_data', 'source',
|
||||
'image_url', 'source_type']
|
||||
other_props = {k: v for (k, v) in data.items() if not k in known_props}
|
||||
meta['properties'].update(other_props)
|
||||
|
||||
def handle_visibility(visibility, meta):
|
||||
# The following expects a 'visibility' parameter to be passed via
|
||||
# the AJAX call, then translates this to a Glance API v1 is_public
|
||||
# parameter. In the future, if the 'visibility' param is exposed on the
|
||||
# glance API, you can check for version, e.g.:
|
||||
# if float(api.glance.get_version()) < 2.0:
|
||||
mapping_to_v1 = {'public': True, 'private': False, 'shared': False}
|
||||
# note: presence of 'visibility' previously checked for in general call
|
||||
try:
|
||||
meta['is_public'] = mapping_to_v1[visibility]
|
||||
except KeyError as e:
|
||||
raise rest_utils.AjaxError(400, "invalid visibility option: %s" % e.args[0])
|
||||
|
||||
|
@ -34,6 +34,9 @@
|
||||
var service = {
|
||||
getVersion: getVersion,
|
||||
getImage: getImage,
|
||||
createImage: createImage,
|
||||
updateImage: updateImage,
|
||||
deleteImage: deleteImage,
|
||||
getImageProps: getImageProps,
|
||||
editImageProps: editImageProps,
|
||||
getImages: getImages,
|
||||
@ -45,6 +48,12 @@
|
||||
///////////////
|
||||
|
||||
// Version
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.glance.getVersion
|
||||
* @description
|
||||
* Get the version of the Glance API
|
||||
*/
|
||||
function getVersion() {
|
||||
return apiService.get('/api/glance/version/')
|
||||
.error(function () {
|
||||
@ -58,8 +67,10 @@
|
||||
* @name horizon.app.core.openstack-service-api.glance.getImage
|
||||
* @description
|
||||
* Get a single image by ID
|
||||
*
|
||||
* @param {string} id
|
||||
* Specifies the id of the image to request.
|
||||
*
|
||||
*/
|
||||
function getImage(id) {
|
||||
return apiService.get('/api/glance/images/' + id)
|
||||
@ -68,6 +79,131 @@
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.glance.createImage
|
||||
* @description
|
||||
* Create a new image. This returns the new image object on success.
|
||||
*
|
||||
* @param {object} image
|
||||
* The image to create
|
||||
*
|
||||
* @param {string} image.name
|
||||
* Name of the image. Required.
|
||||
*
|
||||
* @param {string} image.description
|
||||
* Description of the image. Optional.
|
||||
*
|
||||
* @param {string} image.source_type
|
||||
* Source Type for the image. Only 'url' is supported. Required.
|
||||
*
|
||||
* @param {string} image.disk_format
|
||||
* Format of the image. Required.
|
||||
*
|
||||
* @param {string} image.kernel
|
||||
* Kernel to use for the image. Optional.
|
||||
*
|
||||
* @param {string} image.ramdisk
|
||||
* RamDisk to use for the image. Optional.
|
||||
*
|
||||
* @param {string} image.architecture
|
||||
* Architecture the image. Optional.
|
||||
*
|
||||
* @param {string} image.min_disk
|
||||
* The minimum disk size required to boot the image. Optional.
|
||||
*
|
||||
* @param {string} image.min_ram
|
||||
* The minimum memory size required to boot the image. Optional.
|
||||
*
|
||||
* @param {boolean} image.visibility
|
||||
* values of 'public', 'private', and 'shared' are valid. Required.
|
||||
*
|
||||
* @param {boolean} image.protected
|
||||
* True if the image is protected, false otherwise. Required.
|
||||
*
|
||||
* @param {boolean} image.import_data
|
||||
* True to import the image data to the image service otherwise
|
||||
* image data will be used in its current location
|
||||
*
|
||||
* Any parameters not listed above will be assigned as custom properites.
|
||||
*
|
||||
*/
|
||||
function createImage(image) {
|
||||
return apiService.post('/api/glance/images/', image)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to create the image.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.glance.getImage
|
||||
* @description
|
||||
* Update an existing image.
|
||||
*
|
||||
* @param {object} image
|
||||
* The image to update
|
||||
*
|
||||
* @param {string} image.id
|
||||
* ID of the image to update. Required. Read Only.
|
||||
*
|
||||
* @param {string} image.name
|
||||
* Name of the image. Required.
|
||||
*
|
||||
* @param {string} image.description
|
||||
* Description of the image. Optional.
|
||||
*
|
||||
* @param {string} image.disk_format
|
||||
* Disk format of the image. Required.
|
||||
*
|
||||
* @param {string} image.kernel
|
||||
* Kernel to use for the image. Optional.
|
||||
*
|
||||
* @param {string} image.ramdisk
|
||||
* RamDisk to use for the image. Optional.
|
||||
*
|
||||
* @param {string} image.architecture
|
||||
* Architecture the image. Optional.
|
||||
*
|
||||
* @param {string} image.min_disk
|
||||
* The minimum disk size required to boot the image. Optional.
|
||||
*
|
||||
* @param {string} image.min_ram
|
||||
* The minimum memory size required to boot the image. Optional.
|
||||
*
|
||||
* @param {boolean} image.visibility
|
||||
* Values of 'public', 'private', and 'shared' are valid. Required.
|
||||
*
|
||||
* @param {boolean} image.protected
|
||||
* True if the image is protected, false otherwise. Required.
|
||||
*
|
||||
* Any parameters not listed above will be assigned as custom properites.
|
||||
*/
|
||||
function updateImage(image) {
|
||||
return apiService.patch('/api/glance/images/' + image.id + '/', image)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to update the image.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.glance.deleteImage
|
||||
* @description
|
||||
* Deletes single Image by ID.
|
||||
*
|
||||
* @param {string} imageId
|
||||
* The Id of the image to delete.
|
||||
*
|
||||
* @param {boolean} suppressError
|
||||
* If passed in, this will not show the default error handling
|
||||
*
|
||||
*/
|
||||
function deleteImage(imageId, suppressError) {
|
||||
var promise = apiService.delete('/api/glance/images/' + imageId);
|
||||
|
||||
return suppressError ? promise : promise.error(function() {
|
||||
toastService.add('error', gettext('Unable to delete the image with id: ') + imageId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.glance.getImageProps
|
||||
* @description
|
||||
|
@ -22,18 +22,17 @@
|
||||
var apiService = {};
|
||||
var toastService = {};
|
||||
|
||||
beforeEach(
|
||||
module('horizon.mock.openstack-service-api',
|
||||
function($provide, initServices) {
|
||||
testCall = initServices($provide, apiService, toastService);
|
||||
})
|
||||
);
|
||||
beforeEach(function() {
|
||||
module('horizon.mock.openstack-service-api', function($provide, initServices) {
|
||||
testCall = initServices($provide, apiService, toastService);
|
||||
});
|
||||
|
||||
beforeEach(module('horizon.app.core.openstack-service-api'));
|
||||
module('horizon.app.core.openstack-service-api');
|
||||
|
||||
beforeEach(inject(['horizon.app.core.openstack-service-api.glance', function(glanceAPI) {
|
||||
service = glanceAPI;
|
||||
}]));
|
||||
inject(['horizon.app.core.openstack-service-api.glance', function(glanceAPI) {
|
||||
service = glanceAPI;
|
||||
}]);
|
||||
});
|
||||
|
||||
it('defines the service', function() {
|
||||
expect(service).toBeDefined();
|
||||
@ -55,6 +54,40 @@
|
||||
42
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "deleteImage",
|
||||
"method": "delete",
|
||||
"path": "/api/glance/images/42",
|
||||
"error": "Unable to delete the image with id: 42",
|
||||
"testInput": [
|
||||
42
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "createImage",
|
||||
"method": "post",
|
||||
"path": "/api/glance/images/",
|
||||
"data": {
|
||||
name: '1'
|
||||
},
|
||||
"error": "Unable to create the image.",
|
||||
"testInput": [
|
||||
{name: '1'}
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "updateImage",
|
||||
"method": "patch",
|
||||
"path": "/api/glance/images/1/",
|
||||
"data": {
|
||||
id: '1',
|
||||
name: '1'
|
||||
},
|
||||
"error": "Unable to update the image.",
|
||||
"testInput": [
|
||||
{name: '1', id: '1'}
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "getImageProps",
|
||||
"method": "get",
|
||||
@ -137,6 +170,11 @@
|
||||
expect(service.getNamespaces("whatever", true)).toBe("promise");
|
||||
});
|
||||
|
||||
it('supresses the error if instructed for deleteImage', function() {
|
||||
spyOn(apiService, 'delete').and.returnValue("promise");
|
||||
expect(service.deleteImage("whatever", true)).toBe("promise");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
|
@ -63,6 +63,43 @@ class ImagesRestTestCase(test.TestCase):
|
||||
request, '1', ['c', 'd'], a='1', b='2'
|
||||
)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_delete(self, gc):
|
||||
request = self.mock_rest_request()
|
||||
glance.Image().delete(request, "1")
|
||||
gc.image_delete.assert_called_once_with(request, "1")
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_edit(self, gc):
|
||||
request = self.mock_rest_request(body='''{"name": "Test",
|
||||
"disk_format": "aki", "container_format": "aki",
|
||||
"visibility": "public", "protected": false,
|
||||
"image_url": "test.com",
|
||||
"source_type": "url", "architecture": "testArch",
|
||||
"description": "description", "kernel": "kernel",
|
||||
"min_disk": 10, "min_ram": 5, "ramdisk": 10 }
|
||||
''')
|
||||
|
||||
metadata = {'name': 'Test',
|
||||
'disk_format': 'aki',
|
||||
'container_format': 'aki',
|
||||
'is_public': True,
|
||||
'protected': False,
|
||||
'min_disk': 10,
|
||||
'min_ram': 5,
|
||||
'properties': {
|
||||
'description': 'description',
|
||||
'architecture': 'testArch',
|
||||
'ramdisk_id': 10,
|
||||
'kernel_id': 'kernel',
|
||||
},
|
||||
'purge_props': False}
|
||||
|
||||
response = glance.Image().patch(request, "1")
|
||||
self.assertStatusCode(response, 204)
|
||||
self.assertEqual(response.content.decode('utf-8'), '')
|
||||
gc.image_update.assert_called_once_with(request, '1', **metadata)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_get_list_detailed(self, gc):
|
||||
kwargs = {
|
||||
@ -88,6 +125,183 @@ class ImagesRestTestCase(test.TestCase):
|
||||
filters=filters,
|
||||
**kwargs)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_create_basic(self, gc):
|
||||
request = self.mock_rest_request(body='''{"name": "Test",
|
||||
"disk_format": "aki", "import_data": false,
|
||||
"visibility": "public", "container_format": "aki",
|
||||
"protected": false, "image_url": "test.com",
|
||||
"source_type": "url", "architecture": "testArch",
|
||||
"description": "description", "kernel": "kernel",
|
||||
"min_disk": 10, "min_ram": 5, "ramdisk": 10 }
|
||||
''')
|
||||
new = gc.image_create.return_value
|
||||
new.to_dict.return_value = {'name': 'testimage'}
|
||||
new.name = 'testimage'
|
||||
|
||||
metadata = {'name': 'Test',
|
||||
'disk_format': 'aki',
|
||||
'container_format': 'aki',
|
||||
'is_public': True,
|
||||
'protected': False,
|
||||
'min_disk': 10,
|
||||
'min_ram': 5,
|
||||
'location': 'test.com',
|
||||
'properties': {
|
||||
'description': 'description',
|
||||
'architecture': 'testArch',
|
||||
'ramdisk_id': 10,
|
||||
'kernel_id': 'kernel',
|
||||
}}
|
||||
|
||||
response = glance.Images().post(request)
|
||||
self.assertStatusCode(response, 201)
|
||||
self.assertEqual(response.content.decode('utf-8'),
|
||||
'{"name": "testimage"}')
|
||||
self.assertEqual(response['location'], '/api/glance/images/testimage')
|
||||
gc.image_create.assert_called_once_with(request, **metadata)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_create_shared(self, gc):
|
||||
request = self.mock_rest_request(body='''{"name": "Test",
|
||||
"disk_format": "aki", "import_data": false,
|
||||
"visibility": "shared", "container_format": "aki",
|
||||
"protected": false, "image_url": "test.com",
|
||||
"source_type": "url", "architecture": "testArch",
|
||||
"description": "description", "kernel": "kernel",
|
||||
"min_disk": 10, "min_ram": 5, "ramdisk": 10 }
|
||||
''')
|
||||
new = gc.image_create.return_value
|
||||
new.to_dict.return_value = {'name': 'testimage'}
|
||||
new.name = 'testimage'
|
||||
|
||||
metadata = {'name': 'Test',
|
||||
'disk_format': 'aki',
|
||||
'container_format': 'aki',
|
||||
'is_public': False,
|
||||
'protected': False,
|
||||
'min_disk': 10,
|
||||
'min_ram': 5,
|
||||
'location': 'test.com',
|
||||
'properties': {
|
||||
'description': 'description',
|
||||
'architecture': 'testArch',
|
||||
'ramdisk_id': 10,
|
||||
'kernel_id': 'kernel',
|
||||
}}
|
||||
|
||||
response = glance.Images().post(request)
|
||||
self.assertStatusCode(response, 201)
|
||||
self.assertEqual(response.content.decode('utf-8'),
|
||||
'{"name": "testimage"}')
|
||||
self.assertEqual(response['location'], '/api/glance/images/testimage')
|
||||
gc.image_create.assert_called_once_with(request, **metadata)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_create_private(self, gc):
|
||||
request = self.mock_rest_request(body='''{"name": "Test",
|
||||
"disk_format": "aki", "import_data": false,
|
||||
"visibility": "private", "container_format": "aki",
|
||||
"protected": false, "image_url": "test.com",
|
||||
"source_type": "url", "architecture": "testArch",
|
||||
"description": "description", "kernel": "kernel",
|
||||
"min_disk": 10, "min_ram": 5, "ramdisk": 10 }
|
||||
''')
|
||||
new = gc.image_create.return_value
|
||||
new.to_dict.return_value = {'name': 'testimage'}
|
||||
new.name = 'testimage'
|
||||
|
||||
metadata = {'name': 'Test',
|
||||
'disk_format': 'aki',
|
||||
'container_format': 'aki',
|
||||
'is_public': False,
|
||||
'protected': False,
|
||||
'min_disk': 10,
|
||||
'min_ram': 5,
|
||||
'location': 'test.com',
|
||||
'properties': {
|
||||
'description': 'description',
|
||||
'architecture': 'testArch',
|
||||
'ramdisk_id': 10,
|
||||
'kernel_id': 'kernel',
|
||||
}}
|
||||
|
||||
response = glance.Images().post(request)
|
||||
self.assertStatusCode(response, 201)
|
||||
self.assertEqual(response.content.decode('utf-8'),
|
||||
'{"name": "testimage"}')
|
||||
self.assertEqual(response['location'], '/api/glance/images/testimage')
|
||||
gc.image_create.assert_called_once_with(request, **metadata)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_create_bad_visibility(self, gc):
|
||||
request = self.mock_rest_request(body='''{"name": "Test",
|
||||
"disk_format": "aki", "import_data": false,
|
||||
"visibility": "verybad", "container_format": "aki",
|
||||
"protected": false, "image_url": "test.com",
|
||||
"source_type": "url", "architecture": "testArch",
|
||||
"description": "description", "kernel": "kernel",
|
||||
"min_disk": 10, "min_ram": 5, "ramdisk": 10 }
|
||||
''')
|
||||
|
||||
response = glance.Images().post(request)
|
||||
self.assertStatusCode(response, 400)
|
||||
self.assertEqual(response.content.decode('utf-8'),
|
||||
'"invalid visibility option: verybad"')
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_create_required(self, gc):
|
||||
request = self.mock_rest_request(body='''{"name": "Test",
|
||||
"disk_format": "raw", "import_data": true,
|
||||
"container_format": "docker",
|
||||
"visibility": "public", "protected": false,
|
||||
"source_type": "url", "image_url": "test.com" }''')
|
||||
new = gc.image_create.return_value
|
||||
new.to_dict.return_value = {'name': 'testimage'}
|
||||
new.name = 'testimage'
|
||||
|
||||
metadata = {'name': 'Test',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'docker',
|
||||
'copy_from': 'test.com',
|
||||
'is_public': True,
|
||||
'protected': False,
|
||||
'min_disk': 0,
|
||||
'min_ram': 0,
|
||||
'properties': {}
|
||||
}
|
||||
response = glance.Images().post(request)
|
||||
self.assertStatusCode(response, 201)
|
||||
self.assertEqual(response['location'], '/api/glance/images/testimage')
|
||||
gc.image_create.assert_called_once_with(request, **metadata)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_create_additional_props(self, gc):
|
||||
request = self.mock_rest_request(body='''{"name": "Test",
|
||||
"disk_format": "raw", "import_data": true,
|
||||
"container_format": "docker",
|
||||
"visibility": "public", "protected": false,
|
||||
"arbitrary": "property", "another": "prop",
|
||||
"source_type": "url", "image_url": "test.com" }''')
|
||||
new = gc.image_create.return_value
|
||||
new.to_dict.return_value = {'name': 'testimage'}
|
||||
new.name = 'testimage'
|
||||
|
||||
metadata = {'name': 'Test',
|
||||
'disk_format': 'raw',
|
||||
'container_format': 'docker',
|
||||
'copy_from': 'test.com',
|
||||
'is_public': True,
|
||||
'protected': False,
|
||||
'min_disk': 0,
|
||||
'min_ram': 0,
|
||||
'properties': {'arbitrary': 'property', 'another': 'prop'}
|
||||
}
|
||||
response = glance.Images().post(request)
|
||||
self.assertStatusCode(response, 201)
|
||||
self.assertEqual(response['location'], '/api/glance/images/testimage')
|
||||
gc.image_create.assert_called_once_with(request, **metadata)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_namespace_get_list(self, gc):
|
||||
request = self.mock_rest_request(**{'GET': {}})
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
prelude: >
|
||||
Added new JS REST features for Glance
|
||||
features:
|
||||
- Image create/update/delete calls (post/patch/delete)
|
Loading…
Reference in New Issue
Block a user