Merge "Added EC2 API Import Image support (via RegisterImage EC2 API call)" into stable/rocky
This commit is contained in:
commit
99ef804daf
|
@ -43,7 +43,6 @@ from ec2api.db import api as db_api
|
|||
from ec2api import exception
|
||||
from ec2api.i18n import _
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
s3_opts = [
|
||||
|
@ -201,6 +200,12 @@ def register_image(context, name=None, image_location=None,
|
|||
root_device_name=None, block_device_mapping=None,
|
||||
virtualization_type=None, kernel_id=None,
|
||||
ramdisk_id=None, sriov_net_support=None):
|
||||
|
||||
# Setup default flags
|
||||
is_s3_import = False
|
||||
is_url_import = False
|
||||
|
||||
# Process the input arguments
|
||||
if not image_location and not root_device_name:
|
||||
# NOTE(ft): for backward compatibility with a hypothetical code
|
||||
# which uses name as image_location
|
||||
|
@ -218,7 +223,14 @@ def register_image(context, name=None, image_location=None,
|
|||
# TODO(ft): check the name is unique (at least for EBS image case)
|
||||
metadata['name'] = name
|
||||
if image_location:
|
||||
|
||||
# Resolve the import type
|
||||
metadata['image_location'] = image_location
|
||||
parsed_url = six.moves.urllib.parse.urlparse(image_location)
|
||||
is_s3_import = (parsed_url.scheme == '') or (parsed_url.scheme == 's3')
|
||||
is_url_import = not is_s3_import
|
||||
|
||||
# Check if the name is in the metadata
|
||||
if 'name' not in metadata:
|
||||
# NOTE(ft): it's needed for backward compatibility
|
||||
metadata['name'] = image_location
|
||||
|
@ -256,21 +268,42 @@ def register_image(context, name=None, image_location=None,
|
|||
metadata['ramdisk_id'] = ec2utils.get_os_image(context,
|
||||
ramdisk_id).id
|
||||
|
||||
# Begin the import/registration process
|
||||
with common.OnCrashCleaner() as cleaner:
|
||||
|
||||
# Setup the glance client
|
||||
glance = clients.glance(context)
|
||||
if 'image_location' in metadata:
|
||||
|
||||
# Check if this is an S3 import
|
||||
if is_s3_import:
|
||||
os_image = _s3_create(context, metadata)
|
||||
|
||||
# Condition for all non-S3 imports
|
||||
else:
|
||||
|
||||
# Create the image in glance
|
||||
metadata.update({'visibility': 'private',
|
||||
'container_format': 'bare',
|
||||
'disk_format': 'raw'})
|
||||
os_image = glance.images.create(**metadata)
|
||||
glance.images.upload(os_image.id, '', image_size=0)
|
||||
|
||||
# Kick-off the URL image import if from URL
|
||||
if is_url_import:
|
||||
glance.images.image_import(os_image.id, method='web-download',
|
||||
uri=metadata['image_location'])
|
||||
|
||||
# Otherwise, use the default method
|
||||
else:
|
||||
glance.images.upload(os_image.id, '', image_size=0)
|
||||
|
||||
# Add cleanups and complete the registration process
|
||||
cleaner.addCleanup(glance.images.delete, os_image.id)
|
||||
kind = _get_os_image_kind(os_image)
|
||||
image = db_api.add_item(context, kind, {'os_id': os_image.id,
|
||||
'is_public': False,
|
||||
'description': description})
|
||||
|
||||
# Return the image ID for the registration process
|
||||
return {'imageId': image['id']}
|
||||
|
||||
|
||||
|
@ -812,10 +845,21 @@ _s3_image_state_map = {'downloading': 'pending',
|
|||
|
||||
def _s3_create(context, metadata):
|
||||
"""Gets a manifest from s3 and makes an image."""
|
||||
image_location = metadata['image_location'].lstrip('/')
|
||||
bucket_name = image_location.split('/')[0]
|
||||
manifest_path = image_location[len(bucket_name) + 1:]
|
||||
|
||||
# Parse the metadata into bucket and manifest path
|
||||
parsed_url = six.moves.urllib.parse.urlparse(metadata['image_location'])
|
||||
if parsed_url.hostname is not None:
|
||||
# Handle s3://<BUCKET_NAME>/<KEY_PATH> case
|
||||
bucket_name = parsed_url.hostname
|
||||
manifest_path = parsed_url.path[1:]
|
||||
else:
|
||||
# Handle <BUCKET_NAME>/<KEY_PATH> case
|
||||
bucket_name = parsed_url.path.split('/')[0]
|
||||
manifest_path = '/'.join(parsed_url.path.split('/')[1:])
|
||||
|
||||
# Continue with S3 import
|
||||
s3_client = _s3_conn(context)
|
||||
image_location = '/'.join([bucket_name, manifest_path])
|
||||
key = s3_client.get_object(Bucket=bucket_name, Key=manifest_path)
|
||||
body = key['Body']
|
||||
if isinstance(body, six.string_types):
|
||||
|
|
|
@ -209,6 +209,8 @@ ID_OS_IMAGE_ARI_1 = random_os_id()
|
|||
ROOT_DEVICE_NAME_IMAGE_1 = '/dev/sda1'
|
||||
ROOT_DEVICE_NAME_IMAGE_2 = '/dev/sdb1'
|
||||
LOCATION_IMAGE_1 = 'fake_bucket/fake.img.manifest.xml'
|
||||
LOCATION_IMAGE_2 = 'https://download.cirros-cloud.net/0.4.0/' \
|
||||
+ 'cirros-0.4.0-aarch64-disk.img'
|
||||
|
||||
# volumes constants
|
||||
ID_EC2_VOLUME_1 = random_ec2_id('vol')
|
||||
|
|
|
@ -29,7 +29,6 @@ from ec2api.tests.unit import fakes
|
|||
from ec2api.tests.unit import matchers
|
||||
from ec2api.tests.unit import tools
|
||||
|
||||
|
||||
AMI_MANIFEST_XML = """<?xml version="1.0" ?>
|
||||
<manifest>
|
||||
<version>2011-06-17</version>
|
||||
|
@ -156,6 +155,62 @@ class ImageTestCase(base.ApiTestCase):
|
|||
self._test_create_image('ACTIVE', False)
|
||||
self._test_create_image('SHUTOFF', True)
|
||||
|
||||
@mock.patch('ec2api.api.instance._is_ebs_instance')
|
||||
def test_register_image_by_url(self, is_ebs_instance):
|
||||
self.set_mock_db_items(fakes.DB_INSTANCE_2)
|
||||
is_ebs_instance.return_value = True
|
||||
|
||||
# Setup the mock parameters
|
||||
image_id = fakes.random_ec2_id('ami')
|
||||
os_image_id = fakes.random_os_id()
|
||||
self.glance.images.create.return_value = fakes.OSImage(
|
||||
{'id': os_image_id},
|
||||
from_get=True)
|
||||
self.db_api.add_item.side_effect = tools.get_db_api_add_item(image_id)
|
||||
|
||||
# Setup Import Command
|
||||
import_command = 'RegisterImage'
|
||||
|
||||
# Setup the import arguments
|
||||
args = {
|
||||
'Name': 'TestImage123',
|
||||
'ImageLocation':
|
||||
fakes.LOCATION_IMAGE_2,
|
||||
'Architecture': 'x86_64'
|
||||
}
|
||||
|
||||
# Execute the import image process
|
||||
resp = self.execute(import_command, args)
|
||||
|
||||
# Assert that the image returned is equal to what was expected
|
||||
self.assertEqual({'imageId': image_id}, resp)
|
||||
|
||||
# Assert that Glance Image Create was called
|
||||
self.glance.images.create.assert_called_once_with(
|
||||
name='TestImage123',
|
||||
disk_format='raw',
|
||||
container_format='bare',
|
||||
visibility='private',
|
||||
architecture='x86_64',
|
||||
image_location=fakes.LOCATION_IMAGE_2)
|
||||
|
||||
# Assert that Glance Image Import was called
|
||||
self.glance.images.image_import.assert_called_once_with(
|
||||
os_image_id,
|
||||
method='web-download',
|
||||
uri=fakes.LOCATION_IMAGE_2)
|
||||
|
||||
# Assert that the image was created
|
||||
expected_image = {'is_public': False,
|
||||
'os_id': mock.ANY,
|
||||
'description': None}
|
||||
self.db_api.add_item.assert_called_once_with(
|
||||
mock.ANY, 'ami', expected_image)
|
||||
|
||||
# Reset all test settings/state
|
||||
self.db_api.reset_mock()
|
||||
self.glance.reset_mock()
|
||||
|
||||
@mock.patch('ec2api.api.instance._is_ebs_instance')
|
||||
def test_create_image_invalid_parameters(self, is_ebs_instance):
|
||||
self.set_mock_db_items(fakes.DB_INSTANCE_1)
|
||||
|
|
Loading…
Reference in New Issue