Merge "Added EC2 API Import Image support (via RegisterImage EC2 API call)" into stable/rocky

This commit is contained in:
Zuul 2018-08-23 09:26:07 +00:00 committed by Gerrit Code Review
commit 99ef804daf
3 changed files with 108 additions and 7 deletions

View File

@ -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):

View File

@ -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')

View File

@ -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)