Extract image manipulation methods

Change-Id: Ia261740fa168f157e62ae3b9ff45861b1bd6473c
This commit is contained in:
Feodor Tersin 2014-12-20 00:00:33 +04:00
parent 92e11cc9d8
commit cc05d7afe9
7 changed files with 791 additions and 49 deletions

View File

@ -17,7 +17,9 @@ from keystoneclient.v2_0 import client as kc
from novaclient import client as novaclient
from novaclient import shell as novashell
from oslo.config import cfg
from oslo import messaging
from ec2api import context as ec2_context
from ec2api.openstack.common.gettextutils import _
from ec2api.openstack.common import log as logging
@ -121,6 +123,18 @@ def keystone(context):
return _keystone
def nova_cert(context):
_cert_api = _rpcapi_CertAPI(context)
return _cert_api
def rpc_init(conf):
global _rpc_TRANSPORT
# NOTE(ft): set control_exchange parameter to use Nova cert topic
messaging.set_transport_defaults('nova')
_rpc_TRANSPORT = messaging.get_transport(conf)
def _url_for(context, **kwargs):
service_catalog = context.service_catalog
if not service_catalog:
@ -139,3 +153,39 @@ def _url_for(context, **kwargs):
return None
return None
class _rpcapi_CertAPI(object):
'''Client side of the cert rpc API.'''
def __init__(self, context):
super(_rpcapi_CertAPI, self).__init__()
target = messaging.Target(topic=CONF.cert_topic, version='2.0')
self.client = _rpc_get_client(target)
self.context = context
def decrypt_text(self, text):
cctxt = self.client.prepare()
return cctxt.call(self.context, 'decrypt_text',
project_id=self.context.project_id,
text=text)
_rpc_TRANSPORT = None
def _rpc_get_client(target):
assert _rpc_TRANSPORT is not None
serializer = _rpc_RequestContextSerializer()
return messaging.RPCClient(_rpc_TRANSPORT,
target,
serializer=serializer)
class _rpc_RequestContextSerializer(messaging.NoOpSerializer):
def serialize_context(self, context):
return context.to_dict()
def deserialize_context(self, context):
return ec2_context.RequestContext.from_dict(context)

View File

@ -1054,6 +1054,8 @@ class CloudController(object):
volume_id (str): The ID of the volume (Nova extension).
snapshot_id (str): The ID of the snapshot.
volume_size (str): The size of the volume, in GiBs.
volume_type (str): The volume type.
Not used now.
delete_on_termination (bool): Indicates whether to delete
the volume on instance termination.
iops (int): he number of IOPS to provision for the volume.
@ -1262,7 +1264,7 @@ class CloudController(object):
def get_console_output(self, context, instance_id):
return instance.get_console_output(context, instance_id)
def create_volume(self, context, availability_zone, size=None,
def create_volume(self, context, availability_zone=None, size=None,
snapshot_id=None, volume_type=None, name=None,
description=None, metadata=None, iops=None,
encrypted=None, kms_key_id=None):
@ -1272,6 +1274,7 @@ class CloudController(object):
context (RequestContext): The request context.
availability_zone (str): The Availability Zone in which to create
the volume.
It's required by AWS but optional for legacy Nova EC2 API.
instance_id (str): The size of the volume, in GiBs.
Valid values: 1-1024
If you're creating the volume from a snapshot and don't specify
@ -1420,6 +1423,121 @@ class CloudController(object):
return snapshot.describe_snapshots(context, snapshot_id, owner,
restorable_by, filter)
def create_image(self, context, instance_id, name=None, description=None,
no_reboot=False, block_device_mapping=None):
"""Creates an EBS-backed AMI from an EBS-backed instance.
Args:
context (RequestContext): The request context.
instance_id (str): The ID of the instance.
name (str): A name for the new image.
It's required by AWS but optional for legacy Nova EC2 API.
description (str): A description for the new image.
Not used now.
no_reboot (boolean): When the parameter is set to false, EC2
attempts to shut down the instance cleanly before image
creation and then reboots the instance.
block_device_mapping (list of dict): Dict can contain:
device_name (str): The device name exposed to the instance
(for example, /dev/sdh or xvdh).
virtual_name (str): The virtual device name (ephemeral[0..3]).
ebs (dict): Dict can contain:
volume_id (str): The ID of the volume (Nova extension).
snapshot_id (str): The ID of the snapshot.
volume_size (str): The size of the volume, in GiBs.
volume_type (str): The volume type.
Not used now.
delete_on_termination (bool): Indicates whether to delete
the volume on instance termination.
iops (int): he number of IOPS to provision for the volume.
Not used now.
encrypted (boolean): Whether the volume is encrypted.
Not used now.
no_device (str): Suppresses the device mapping.
Returns:
The ID of the new AMI.
"""
return image.create_image(context, instance_id, name, description,
no_reboot, block_device_mapping)
def register_image(self, context, name=None, image_location=None,
description=None, architecture=None,
root_device_name=None, block_device_mapping=None,
virtualization_type=None, kernel_id=None,
ramdisk_id=None, sriov_net_support=None):
"""Registers an AMI.
Args:
context (RequestContext): The request context.
name (str): A name for your AMI.
It's required by AWS but optional for legacy Nova EC2 API.
image_location (str): The full path to AMI manifest in S3 storage.
description (str): A description for your AMI.
Not used now.
architecture (str): The architecture of the AMI.
Not used now.
root_device_name (str): The name of the root device
block_device_mapping (list of dict): Dict can contain:
device_name (str): The device name exposed to the instance
(for example, /dev/sdh or xvdh).
virtual_name (str): The virtual device name (ephemeral[0..3]).
ebs (dict): Dict can contain:
volume_id (str): The ID of the volume (Nova extension).
snapshot_id (str): The ID of the snapshot.
volume_size (str): The size of the volume, in GiBs.
volume_type (str): The volume type.
Not used now.
delete_on_termination (bool): Indicates whether to delete
the volume on instance termination.
iops (int): he number of IOPS to provision for the volume.
Not used now.
encrypted (boolean): Whether the volume is encrypted.
Not used now.
no_device (str): Suppresses the device mapping.
virtualization_type (str): The type of virtualization.
Not used now.
kernel_id (str): The ID of the kernel.
Not used now.
ramdisk_id (str): The ID of the RAM disk.
Not used now.
sriov_net_support (str): SR-IOV mode for networking.
Not used now.
Returns:
The ID of the new AMI.
"""
return image.register_image(context, name, image_location,
description, architecture,
root_device_name, block_device_mapping,
virtualization_type, kernel_id,
ramdisk_id, sriov_net_support)
def deregister_image(self, context, image_id):
"""Deregisters the specified AMI.
Args:
context (RequestContext): The request context.
image_id (str): The ID of the AMI.
Returns:
true if the request succeeds.
"""
return image.deregister_image(context, image_id)
def update_image(self, context, image_id, **kwargs):
"""Update image metadata (Nova EC2 extension).
Args:
context (RequestContext): The request context.
image_id (str): The ID of the image.
**kwargs: Metadata key-value pairs to be added/updated.
Returns:
The updated image.
"""
pass
def describe_images(self, context, executable_by=None, image_id=None,
owner=None, filter=None):
"""Describes one or more of the images available to you.
@ -1431,6 +1549,7 @@ class CloudController(object):
Not used now.
image_id (list of str): One or more image IDs.
owner (list of str): Filters the images by the owner.
Not used now.
filter (list of filter dict): You can specify filters so that the
response includes information for only certain images.
@ -1439,3 +1558,50 @@ class CloudController(object):
"""
return image.describe_images(context, executable_by, image_id,
owner, filter)
def describe_image_attribute(self, context, image_id, attribute):
"""Describes the specified attribute of the specified AMI.
Args:
context (RequestContext): The request context.
image_id (str): The ID of the image.
attribute (str): The attribute of the network interface.
Valid values: description (unsupported now)| kernel | ramdisk |
launchPermission | productCodes (unsupported now)|
blockDeviceMapping | rootDeviceName (Nova EC2 extension)
Returns:
Specified attribute.
"""
return image.describe_image_attribute(context, image_id, attribute)
def modify_image_attribute(self, context, image_id, attribute,
user_group, operation_type,
description=None, launch_permission=None,
product_code=None, user_id=None, value=None):
"""Modifies the specified attribute of the specified AMI.
Args:
context (RequestContext): The request context.
image_id (str): The ID of the image.
attribute (str): The name of the attribute to modify.
It's optional for AWS but required for legacy Nova EC2 API.
Only 'launchPermission' is supported now.
user_group (list of str): One or more user groups.
It's optional for AWS but required for legacy Nova EC2 API.
Only 'all' group is supported now.
operation_type (str): The operation type.
It's optional for AWS but required for legacy Nova EC2 API.
Only 'add' and 'remove' operation types are supported now.
description: Not supported now.
launch_permission: : Not supported now.
product_code: : Not supported now.
user_id: : Not supported now.
value: : Not supported now.
Returns:
true if the request succeeds.
"""
return image.modify_image_attribute(context, image_id, attribute,
user_group, operation_type,
description, launch_permission,
product_code, user_id, value)

View File

@ -12,16 +12,185 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import binascii
import itertools
import json
import os
import re
import shutil
import tarfile
import tempfile
import time
import boto.s3.connection
import eventlet
from glanceclient import exc as glance_exception
from lxml import etree
from oslo.config import cfg
from oslo_concurrency import processutils
from ec2api.api import clients
from ec2api.api import common
from ec2api.api import ec2utils
from ec2api.api import instance as instance_api
from ec2api.api import utils
from ec2api import context as ec2_context
from ec2api.db import api as db_api
from ec2api import exception
from ec2api.openstack.common.gettextutils import _
from ec2api.openstack.common import timeutils
s3_opts = [
cfg.StrOpt('image_decryption_dir',
default='/tmp',
help='Parent directory for tempdir used for image decryption'),
cfg.StrOpt('s3_host',
default='$my_ip',
help='Hostname or IP for OpenStack to use when accessing '
'the S3 api'),
cfg.IntOpt('s3_port',
default=3333,
help='Port used when accessing the S3 api'),
cfg.BoolOpt('s3_use_ssl',
default=False,
help='Whether to use SSL when talking to S3'),
cfg.BoolOpt('s3_affix_tenant',
default=False,
help='Whether to affix the tenant id to the access key '
'when downloading from S3'),
]
CONF = cfg.CONF
CONF.register_opts(s3_opts)
rpcapi_opts = [
cfg.StrOpt('cert_topic',
default='cert',
help='The topic cert nodes listen on'),
]
CONF.register_opts(rpcapi_opts)
# TODO(yamahata): race condition
# At the moment there is no way to prevent others from
# manipulating instances/volumes/snapshots.
# As other code doesn't take it into consideration, here we don't
# care of it for now. Ostrich algorithm
def create_image(context, instance_id, name=None, description=None,
no_reboot=False, block_device_mapping=None):
instance = ec2utils.get_db_item(context, 'i', instance_id)
nova = clients.nova(context)
os_instance = nova.servers.get(instance['os_id'])
if not instance_api._is_ebs_instance(context, os_instance):
# TODO(ft): Change the error code and message with the real AWS ones
msg = _('The instance is not an EBS-backed instance.')
raise exception.InvalidParameterValue(value=instance_id,
parameter='InstanceId',
reason=msg)
restart_instance = False
if not no_reboot:
vm_state = getattr(os_instance, 'OS-EXT-STS:vm_state')
if vm_state not in (instance_api.vm_states_ACTIVE,
instance_api.vm_states_STOPPED):
# TODO(ft): Change the error code and message with the real AWS
# ones
msg = _('Instance must be run or stopped')
raise exception.IncorrectState(reason=msg)
if vm_state == instance_api.vm_states_ACTIVE:
restart_instance = True
os_instance.stop()
# wait instance for really stopped
start_time = time.time()
while vm_state != instance_api.vm_states_STOPPED:
time.sleep(1)
os_instance.get()
vm_state = getattr(os_instance, 'OS-EXT-STS:vm_state')
# NOTE(yamahata): timeout and error. 1 hour for now for safety.
# Is it too short/long?
# Or is there any better way?
timeout = 1 * 60 * 60
if time.time() > start_time + timeout:
err = _("Couldn't stop instance within %d sec") % timeout
raise exception.EC2Exception(message=err)
# meaningful image name
name_map = dict(instance=instance['os_id'], now=timeutils.isotime())
name = name or _('image of %(instance)s at %(now)s') % name_map
with utils.OnCrashCleaner() as cleaner:
os_image = os_instance.create_image(name)
cleaner.addCleanup(os_image.delete)
image = db_api.add_item(context, 'ami', {'os_id': os_image.id,
'is_public': False})
if restart_instance:
os_instance.start()
return {'imageId': image['id']}
def register_image(context, name=None, image_location=None,
description=None, architecture=None,
root_device_name=None, block_device_mapping=None,
virtualization_type=None, kernel_id=None,
ramdisk_id=None, sriov_net_support=None):
if image_location is None and name:
image_location = name
if image_location is None:
msg = _('imageLocation is required')
raise exception.MissingParameter(msg)
metadata = {'properties': {'image_location': image_location}}
if name:
metadata['name'] = name
else:
metadata['name'] = image_location
if root_device_name:
metadata['properties']['root_device_name'] = root_device_name
mappings = [instance_api._cloud_parse_block_device_mapping(context, bdm)
for bdm in block_device_mapping or []]
if mappings:
metadata['properties']['block_device_mapping'] = mappings
with utils.OnCrashCleaner() as cleaner:
os_image = _s3_create(context, metadata)
cleaner.addCleanup(os_image.delete)
image_type = ec2utils.image_type(os_image.container_format)
image = db_api.add_item(context, image_type, {'os_id': os_image.id,
'is_public': False})
return {'imageId': image['id']}
def deregister_image(context, image_id):
# TODO(ft): AWS returns AuthFailure for public images,
# but we return NotFound due searching for local images only
kind = image_id.split('-')[0]
image = ec2utils.get_db_item(context, kind, image_id)
glance = clients.glance(context)
try:
glance.images.delete(image['os_id'])
except glance_exception.HTTPNotFound:
pass
db_api.delete_item(context, image['id'])
return True
def update_image(context, image_id, **kwargs):
kind = image_id.split('-')[0]
image = ec2utils.get_db_item(context, kind, image_id)
glance = clients.glance(context)
return glance.images.update(image['os_id'], **kwargs)
class ImageDescriber(common.UniversalDescriber):
@ -53,9 +222,8 @@ class ImageDescriber(common.UniversalDescriber):
public_images)))
if len(images) < len(self.ids):
missed_ids = set(self.ids) - set(i['id']
for i in images.itervalues())
raise exception.InvalidAMIIDNotFound(
{'id': next(iter(missed_ids))})
for i in images)
raise exception.InvalidAMIIDNotFound(id=next(iter(missed_ids)))
self.images = images
self.snapshot_ids = dict((s['os_id'], s['id'])
for s in db_api.get_items(self.context, 'snap'))
@ -97,6 +265,91 @@ def describe_images(context, executable_by=None, image_id=None,
return {'imagesSet': formatted_images}
def describe_image_attribute(context, image_id, attribute):
def _block_device_mapping_attribute(image, result):
_cloud_format_mappings(image['properties'], result)
def _launch_permission_attribute(image, result):
result['launchPermission'] = []
if image['is_public']:
result['launchPermission'].append({'group': 'all'})
def _root_device_name_attribute(image, result):
_prop_root_dev_name = _block_device_properties_root_device_name
result['rootDeviceName'] = _prop_root_dev_name(image['properties'])
if result['rootDeviceName'] is None:
result['rootDeviceName'] = _block_device_DEFAULT_ROOT_DEV_NAME
def _kernel_attribute(image, result):
kernel_id = image['properties'].get('kernel_id')
if kernel_id:
result['kernel'] = {
'value': ec2utils.os_id_to_ec2_id(context, 'aki', kernel_id)
}
def _ramdisk_attribute(image, result):
ramdisk_id = image['properties'].get('ramdisk_id')
if ramdisk_id:
result['ramdisk'] = {
'value': ec2utils.os_id_to_ec2_id(context, 'ari', ramdisk_id)
}
supported_attributes = {
'blockDeviceMapping': _block_device_mapping_attribute,
'launchPermission': _launch_permission_attribute,
'rootDeviceName': _root_device_name_attribute,
'kernel': _kernel_attribute,
'ramdisk': _ramdisk_attribute,
}
# TODO(ft): AWS returns AuthFailure for public images,
# but we return NotFound due searching for local images only
kind = image_id.split('-')[0]
image = ec2utils.get_db_item(context, kind, image_id)
fn = supported_attributes.get(attribute)
if fn is None:
raise exception.InvalidAttribute(attr=attribute)
glance = clients.glance(context)
os_image = glance.images.get(image['os_id'])
result = {'imageId': image_id}
fn(os_image, result)
return result
def modify_image_attribute(context, image_id, attribute,
user_group, operation_type,
description=None, launch_permission=None,
product_code=None, user_id=None, value=None):
if attribute != 'launchPermission':
# TODO(ft): Change the error code and message with the real AWS ones
raise exception.InvalidAttribute(attr=attribute)
if not user_group:
msg = _('user or group not specified')
# TODO(ft): Change the error code and message with the real AWS ones
raise exception.MissingParameter(msg)
if len(user_group) != 1 and user_group[0] != 'all':
msg = _('only group "all" is supported')
raise exception.InvalidParameterValue(parameter='UserGroup',
value=user_group,
reason=msg)
if operation_type not in ['add', 'remove']:
msg = _('operation_type must be add or remove')
raise exception.InvalidParameterValue(parameter='OperationType',
value='operation_type',
reason=msg)
# TODO(ft): AWS returns AuthFailure for public images,
# but we return NotFound due searching for local images only
kind = image_id.split('-')[0]
image = ec2utils.get_db_item(context, kind, image_id)
glance = clients.glance(context)
image = glance.images.get(image['os_id'])
image.update(is_public=(operation_type == 'add'))
return True
def _format_image(context, image, os_image, images_dict, ids_dict,
snapshot_ids=None):
image_type = ec2utils.image_type(os_image.container_format)
@ -288,3 +541,236 @@ _ephemeral = re.compile('^ephemeral(\d|[1-9]\d+)$')
def _block_device_is_ephemeral(device_name):
return _ephemeral.match(device_name) is not None
def _s3_create(context, metadata):
"""Gets a manifest from s3 and makes an image."""
image_path = tempfile.mkdtemp(dir=CONF.image_decryption_dir)
image_location = metadata['properties']['image_location'].lstrip('/')
bucket_name = image_location.split('/')[0]
manifest_path = image_location[len(bucket_name) + 1:]
bucket = _s3_conn(context).get_bucket(bucket_name)
key = bucket.get_key(manifest_path)
manifest = key.get_contents_as_string()
manifest, image = _s3_parse_manifest(context, metadata, manifest)
def _update_image_state(image_state):
image.update(properties={'image_state': image_state})
def delayed_create():
"""This handles the fetching and decrypting of the part files."""
context.update_store()
try:
_update_image_state('downloading')
try:
parts = []
elements = manifest.find('image').getiterator('filename')
for fn_element in elements:
part = _s3_download_file(bucket, fn_element.text,
image_path)
parts.append(part)
# NOTE(vish): this may be suboptimal, should we use cat?
enc_filename = os.path.join(image_path, 'image.encrypted')
with open(enc_filename, 'w') as combined:
for filename in parts:
with open(filename) as part:
shutil.copyfileobj(part, combined)
except Exception:
_update_image_state('failed_download')
return
_update_image_state('decrypting')
try:
hex_key = manifest.find('image/ec2_encrypted_key').text
encrypted_key = binascii.a2b_hex(hex_key)
hex_iv = manifest.find('image/ec2_encrypted_iv').text
encrypted_iv = binascii.a2b_hex(hex_iv)
dec_filename = os.path.join(image_path, 'image.tar.gz')
_s3_decrypt_image(context, enc_filename, encrypted_key,
encrypted_iv, dec_filename)
except Exception:
_update_image_state('failed_decrypt')
return
_update_image_state('untarring')
try:
unz_filename = _s3_untarzip_image(image_path, dec_filename)
except Exception:
_update_image_state('failed_untar')
return
_update_image_state('uploading')
try:
with open(unz_filename) as image_file:
image.update(data=image_file)
except Exception:
_update_image_state('failed_upload')
return
_update_image_state('available')
shutil.rmtree(image_path)
except glance_exception.HTTPNotFound:
return
eventlet.spawn_n(delayed_create)
return image
def _s3_parse_manifest(context, metadata, manifest):
manifest = etree.fromstring(manifest)
image_format = 'ami'
try:
kernel_id = manifest.find('machine_configuration/kernel_id').text
if kernel_id == 'true':
image_format = 'aki'
kernel_id = None
except Exception:
kernel_id = None
try:
ramdisk_id = manifest.find('machine_configuration/ramdisk_id').text
if ramdisk_id == 'true':
image_format = 'ari'
ramdisk_id = None
except Exception:
ramdisk_id = None
try:
arch = manifest.find('machine_configuration/architecture').text
except Exception:
arch = 'x86_64'
# NOTE(yamahata):
# EC2 ec2-budlne-image --block-device-mapping accepts
# <virtual name>=<device name> where
# virtual name = {ami, root, swap, ephemeral<N>}
# where N is no negative integer
# device name = the device name seen by guest kernel.
# They are converted into
# block_device_mapping/mapping/{virtual, device}
#
# Do NOT confuse this with ec2-register's block device mapping
# argument.
mappings = []
try:
block_device_mapping = manifest.findall('machine_configuration/'
'block_device_mapping/'
'mapping')
for bdm in block_device_mapping:
mappings.append({'virtual': bdm.find('virtual').text,
'device': bdm.find('device').text})
except Exception:
mappings = []
properties = metadata['properties']
properties['architecture'] = arch
def _translate_dependent_image_id(image_key, image_id):
image_uuid = ec2utils.ec2_id_to_glance_id(context, image_id)
properties[image_key] = image_uuid
if kernel_id:
_translate_dependent_image_id('kernel_id', kernel_id)
if ramdisk_id:
_translate_dependent_image_id('ramdisk_id', ramdisk_id)
if mappings:
properties['mappings'] = mappings
metadata.update({'disk_format': image_format,
'container_format': image_format,
'is_public': False,
'properties': properties})
metadata['properties']['image_state'] = 'pending'
# TODO(bcwaldon): right now, this removes user-defined ids
# We need to re-enable this.
metadata.pop('id', None)
glance = clients.glance(context)
image = glance.images.create(**metadata)
return manifest, image
def _s3_download_file(bucket, filename, local_dir):
key = bucket.get_key(filename)
local_filename = os.path.join(local_dir, os.path.basename(filename))
key.get_contents_to_filename(local_filename)
return local_filename
def _s3_decrypt_image(context, encrypted_filename, encrypted_key,
encrypted_iv, decrypted_filename):
cert_client = clients.nova_cert(context)
try:
key = cert_client.decrypt_text(base64.b64encode(encrypted_key))
except Exception as exc:
msg = _('Failed to decrypt private key: %s') % exc
raise exception.EC2Exception(msg)
try:
iv = cert_client.decrypt_text(base64.b64encode(encrypted_iv))
except Exception as exc:
msg = _('Failed to decrypt initialization vector: %s') % exc
raise exception.EC2Exception(msg)
try:
processutils.execute('openssl', 'enc',
'-d', '-aes-128-cbc',
'-in', '%s' % (encrypted_filename,),
'-K', '%s' % (key,),
'-iv', '%s' % (iv,),
'-out', '%s' % (decrypted_filename,))
except processutils.ProcessExecutionError as exc:
raise exception.EC2Exception(_('Failed to decrypt image file '
'%(image_file)s: %(err)s') %
{'image_file': encrypted_filename,
'err': exc.stdout})
def _s3_untarzip_image(path, filename):
_s3_test_for_malicious_tarball(path, filename)
tar_file = tarfile.open(filename, 'r|gz')
tar_file.extractall(path)
image_file = tar_file.getnames()[0]
tar_file.close()
return os.path.join(path, image_file)
def _s3_test_for_malicious_tarball(path, filename):
"""Raises exception if extracting tarball would escape extract path."""
tar_file = tarfile.open(filename, 'r|gz')
for n in tar_file.getnames():
if not os.path.abspath(os.path.join(path, n)).startswith(path):
tar_file.close()
raise exception.Invalid(_('Unsafe filenames in image'))
tar_file.close()
def _s3_conn(context):
# NOTE(vish): access and secret keys for s3 server are not
# checked in nova-objectstore
access = context.access_key
if CONF.s3_affix_tenant:
access = '%s:%s' % (access, context.project_id)
secret = context.secret_key
calling = boto.s3.connection.OrdinaryCallingFormat()
return boto.s3.connection.S3Connection(aws_access_key_id=access,
aws_secret_access_key=secret,
is_secure=CONF.s3_use_ssl,
calling_format=calling,
port=CONF.s3_port,
host=CONF.s3_host)

View File

@ -341,6 +341,32 @@ def start_instances(context, instance_id):
lambda instance: instance.start())
def get_password_data(context, instance_id):
# NOTE(Alex): AWS supports one and only one instance_id here
instance = ec2utils.get_db_item(context, 'i', instance_id)
nova = clients.nova(context)
os_instance = nova.servers.get(instance['os_id'])
password = os_instance.get_password()
# NOTE(vish): this should be timestamp from the metadata fields
# but it isn't important enough to implement properly
now = timeutils.utcnow()
return {"instanceId": instance_id,
"timestamp": now,
"passwordData": password}
def get_console_output(context, instance_id):
# NOTE(Alex): AWS supports one and only one instance_id here
instance = ec2utils.get_db_item(context, 'i', instance_id)
nova = clients.nova(context)
os_instance = nova.servers.get(instance['os_id'])
console_output = os_instance.get_console_output()
now = timeutils.utcnow()
return {"instanceId": instance_id,
"timestamp": now,
"output": console_output}
def describe_instance_attribute(context, instance_id, attribute):
instance = db_api.get_item_by_id(context, 'i', instance_id)
nova = clients.nova(context)
@ -922,18 +948,6 @@ def _get_os_instances_by_instances(context, instances, exactly=False):
return os_instances
def _auto_create_instance_extension(context, instance, novadb_instance=None):
if not novadb_instance:
novadb_instance = novadb.instance_get_by_uuid(context,
instance['os_id'])
instance['reservation_id'] = novadb_instance['reservation_id']
instance['launch_index'] = novadb_instance['launch_index']
ec2utils.register_auto_create_db_item_extension(
'i', _auto_create_instance_extension)
def _get_ec2_classic_os_network(context, neutron):
os_subnet_ids = [eni['os_id']
for eni in db_api.get_items(context, 'subnet')]
@ -958,30 +972,35 @@ def _get_ec2_classic_os_network(context, neutron):
return ec2_classic_os_networks[0]
def get_password_data(context, instance_id):
# NOTE(Alex): AWS supports one and only one instance_id here
instance = ec2utils.get_db_item(context, 'i', instance_id)
nova = clients.nova(context)
os_instance = nova.servers.get(instance['os_id'])
password = os_instance.get_password()
# NOTE(vish): this should be timestamp from the metadata fields
# but it isn't important enough to implement properly
now = timeutils.utcnow()
return {"instanceId": instance_id,
"timestamp": now,
"passwordData": password}
def _is_ebs_instance(context, os_instance):
novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id)
root_device_name = _cloud_format_instance_root_device_name(novadb_instance)
root_device_short_name = _block_device_strip_dev(root_device_name)
if root_device_name == root_device_short_name:
root_device_name = _block_device_prepend_dev(root_device_name)
for bdm in novadb.block_device_mapping_get_all_by_instance(context,
os_instance.id):
volume_id = bdm['volume_id']
if (volume_id is None or bdm['no_device']):
continue
if ((bdm['snapshot_id'] or bdm['volume_id']) and
(bdm['device_name'] == root_device_name or
bdm['device_name'] == root_device_short_name)):
return True
return False
def get_console_output(context, instance_id):
# NOTE(Alex): AWS supports one and only one instance_id here
instance = ec2utils.get_db_item(context, 'i', instance_id)
nova = clients.nova(context)
os_instance = nova.servers.get(instance['os_id'])
console_output = os_instance.get_console_output()
now = timeutils.utcnow()
return {"instanceId": instance_id,
"timestamp": now,
"output": console_output}
def _auto_create_instance_extension(context, instance, novadb_instance=None):
if not novadb_instance:
novadb_instance = novadb.instance_get_by_uuid(context,
instance['os_id'])
instance['reservation_id'] = novadb_instance['reservation_id']
instance['launch_index'] = novadb_instance['launch_index']
ec2utils.register_auto_create_db_item_extension(
'i', _auto_create_instance_extension)
# NOTE(ft): following functions are copied from various parts of Nova

View File

@ -20,6 +20,7 @@ import sys
from oslo.config import cfg
from ec2api.api import clients
from ec2api import config
from ec2api.openstack.common import log as logging
from ec2api import service
@ -31,6 +32,7 @@ CONF.import_opt('use_ssl', 'ec2api.service')
def main():
config.parse_args(sys.argv)
logging.setup('ec2api')
clients.rpc_init(cfg.CONF)
server = service.WSGIService(
'ec2api', use_ssl=CONF.use_ssl, max_url_len=16384)

View File

@ -186,6 +186,21 @@ function iniget() {
echo ${line#*=}
}
# Copy an option from Nova INI file or from environment if it's set
function copynovaopt() {
local option_name=$1
local env_var
local option
env_var=${option_name^^}
if [ ${!env_var+x} ]; then
option=${!env_var}
elif ini_has_option "$NOVA_CONF" DEFAULT $option_name; then
option=$(iniget $NOVA_CONF DEFAULT $option_name)
else
return 0
fi
iniset $CONF_FILE DEFAULT $option_name $option
}
#get nova settings
if [[ -z "$NOVA_CONNECTION" ]]; then
@ -204,11 +219,7 @@ if [[ -z "$NOVA_CONNECTION" ]]; then
NOVA_CONNECTION=$(iniget $NOVA_CONF sql connection)
fi
fi
if [[ -z "$NOVA_CONNECTION" ]]; then
echo "$reason"
echo "Please set NOVA_CONNECTION environment variable to the connection string to Nova DB"
exit 1
fi
die_if_not_set $LINENO NOVA_CONNECTION "$reason. Please set NOVA_CONNECTION environment variable to the connection string to Nova DB"
fi
if [[ -z "$EXTERNAL_NETWORK" ]]; then
declare -a newtron_output
@ -220,14 +231,9 @@ if [[ -z "$EXTERNAL_NETWORK" ]]; then
else
EXTERNAL_NETWORK=$(echo $newtron_output | awk -F '|' '{ print $3 }')
fi
if [[ -z "$EXTERNAL_NETWORK" ]]; then
echo $reason
echo "Please set PUBLIC_NETWORK environment variable to the external network dedicated to EC2 elastic IP operations"
exit 1
fi
die_if_not_set $LINENO EXTERNAL_NETWORK "$reason. Please set PUBLIC_NETWORK environment variable to the external network dedicated to EC2 elastic IP operations"
fi
#create keystone user with admin privileges
ADMIN_ROLE=$(get_data 2 admin 1 keystone role-list)
die_if_not_set $LINENO ADMIN_ROLE "Fail to get ADMIN_ROLE by 'keystone role-list' "
@ -284,6 +290,16 @@ iniset $CONF_FILE keystone_authtoken admin_tenant_name $SERVICE_TENANT
iniset $CONF_FILE keystone_authtoken auth_protocol $AUTH_PROTO
iniset $CONF_FILE keystone_authtoken auth_port $AUTH_PORT
if [[ -f "$NOVA_CONF" ]]; then
copynovaopt s3_host
copynovaopt s3_port
copynovaopt s3_affix_tenant
copynovaopt s3_use_ssl
copynovaopt cert_topic
copynovaopt rabbit_hosts
copynovaopt rabbit_password
# TODO(ft): it's necessary to support other available messaging implementations
fi
#init cache dir
echo Creating signing dir

View File

@ -1,13 +1,16 @@
anyjson>=0.3.3
argparse
Babel>=1.3
boto>=2.32.1
eventlet>=0.13.0
greenlet>=0.3.2
httplib2>=0.7.5
iso8601>=0.1.9
jsonschema>=2.0.0,<3.0.0
lxml>=2.3
oslo.concurrency>=0.3.0
oslo.config>=1.4.0.0a2
oslo.messaging>=1.4.0,!=1.5.0
Paste
PasteDeploy>=1.5.0
pbr>=0.6,!=0.7,<1.0