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 six.moves import zip as izip
|
||||||
|
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
from openstack_dashboard import api
|
from openstack_dashboard import api
|
||||||
from openstack_dashboard.api.rest import utils as rest_utils
|
from openstack_dashboard.api.rest import utils as rest_utils
|
||||||
from openstack_dashboard.api.rest import urls
|
from openstack_dashboard.api.rest import urls
|
||||||
|
|
||||||
|
|
||||||
CLIENT_KEYWORDS = {'resource_type', 'marker', 'sort_dir', 'sort_key', 'paginate'}
|
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()
|
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
|
@urls.register
|
||||||
class ImageProperties(generic.View):
|
class ImageProperties(generic.View):
|
||||||
@ -124,6 +161,46 @@ class Images(generic.View):
|
|||||||
'has_prev_data': has_prev_data,
|
'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
|
@urls.register
|
||||||
class MetadefsNamespaces(generic.View):
|
class MetadefsNamespaces(generic.View):
|
||||||
@ -175,3 +252,61 @@ class MetadefsNamespaces(generic.View):
|
|||||||
return dict(izip(names, api.glance.metadefs_namespace_full_list(
|
return dict(izip(names, api.glance.metadefs_namespace_full_list(
|
||||||
request, filters=filters, **kwargs
|
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 = {
|
var service = {
|
||||||
getVersion: getVersion,
|
getVersion: getVersion,
|
||||||
getImage: getImage,
|
getImage: getImage,
|
||||||
|
createImage: createImage,
|
||||||
|
updateImage: updateImage,
|
||||||
|
deleteImage: deleteImage,
|
||||||
getImageProps: getImageProps,
|
getImageProps: getImageProps,
|
||||||
editImageProps: editImageProps,
|
editImageProps: editImageProps,
|
||||||
getImages: getImages,
|
getImages: getImages,
|
||||||
@ -45,6 +48,12 @@
|
|||||||
///////////////
|
///////////////
|
||||||
|
|
||||||
// Version
|
// Version
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name horizon.app.core.openstack-service-api.glance.getVersion
|
||||||
|
* @description
|
||||||
|
* Get the version of the Glance API
|
||||||
|
*/
|
||||||
function getVersion() {
|
function getVersion() {
|
||||||
return apiService.get('/api/glance/version/')
|
return apiService.get('/api/glance/version/')
|
||||||
.error(function () {
|
.error(function () {
|
||||||
@ -58,8 +67,10 @@
|
|||||||
* @name horizon.app.core.openstack-service-api.glance.getImage
|
* @name horizon.app.core.openstack-service-api.glance.getImage
|
||||||
* @description
|
* @description
|
||||||
* Get a single image by ID
|
* Get a single image by ID
|
||||||
|
*
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* Specifies the id of the image to request.
|
* Specifies the id of the image to request.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
function getImage(id) {
|
function getImage(id) {
|
||||||
return apiService.get('/api/glance/images/' + 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
|
* @name horizon.app.core.openstack-service-api.glance.getImageProps
|
||||||
* @description
|
* @description
|
||||||
|
@ -22,18 +22,17 @@
|
|||||||
var apiService = {};
|
var apiService = {};
|
||||||
var toastService = {};
|
var toastService = {};
|
||||||
|
|
||||||
beforeEach(
|
beforeEach(function() {
|
||||||
module('horizon.mock.openstack-service-api',
|
module('horizon.mock.openstack-service-api', function($provide, initServices) {
|
||||||
function($provide, initServices) {
|
testCall = initServices($provide, apiService, toastService);
|
||||||
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) {
|
inject(['horizon.app.core.openstack-service-api.glance', function(glanceAPI) {
|
||||||
service = glanceAPI;
|
service = glanceAPI;
|
||||||
}]));
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
it('defines the service', function() {
|
it('defines the service', function() {
|
||||||
expect(service).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
@ -55,6 +54,40 @@
|
|||||||
42
|
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",
|
"func": "getImageProps",
|
||||||
"method": "get",
|
"method": "get",
|
||||||
@ -137,6 +170,11 @@
|
|||||||
expect(service.getNamespaces("whatever", true)).toBe("promise");
|
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'
|
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')
|
@mock.patch.object(glance.api, 'glance')
|
||||||
def test_image_get_list_detailed(self, gc):
|
def test_image_get_list_detailed(self, gc):
|
||||||
kwargs = {
|
kwargs = {
|
||||||
@ -88,6 +125,183 @@ class ImagesRestTestCase(test.TestCase):
|
|||||||
filters=filters,
|
filters=filters,
|
||||||
**kwargs)
|
**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')
|
@mock.patch.object(glance.api, 'glance')
|
||||||
def test_namespace_get_list(self, gc):
|
def test_namespace_get_list(self, gc):
|
||||||
request = self.mock_rest_request(**{'GET': {}})
|
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