Implement run_instance independently of Nova
Change-Id: I0f0ab143db55a545521fb0162955a6c432034fba
This commit is contained in:
parent
ae17e49c56
commit
d0d6ac78d7
@ -1005,9 +1005,12 @@ class CloudController(object):
|
||||
private_ip_address)
|
||||
|
||||
def run_instances(self, context, image_id, min_count, max_count,
|
||||
subnet_id=None, private_ip_address=None,
|
||||
network_interface=None, security_group=None,
|
||||
security_group_id=None, **kwargs):
|
||||
key_name=None, security_group_id=None,
|
||||
security_group=None, user_data=None, instance_type=None,
|
||||
placement=None, kernel_id=None, ramdisk_id=None,
|
||||
block_device_mapping=None, subnet_id=None,
|
||||
private_ip_address=None, client_token=None,
|
||||
network_interface=None, **kwargs):
|
||||
"""Launches the specified number of instances using an AMI.
|
||||
|
||||
Args:
|
||||
@ -1021,10 +1024,33 @@ class CloudController(object):
|
||||
If you specify more instances than EC2 can launch in the target
|
||||
Availability Zone, EC2 launches the largest possible number
|
||||
of instances above max_count.
|
||||
key_name (str): The name of the key pair.
|
||||
security_group_id (list of str): One or more security group IDs.
|
||||
security_group (list of str): One or more security group names.
|
||||
For VPC mode, you must use security_group_id.
|
||||
user_data (str): Base64-encoded MIME user data for the instances.
|
||||
instance_type (str): The instance type.
|
||||
placement (dict): Dict can contain:
|
||||
availability_zone (str): Availability Zone for the instance.
|
||||
kernel_id (str): The ID of the kernel.
|
||||
ramdisk_id (str): The ID of the RAM disk.
|
||||
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.
|
||||
delete_on_termination (bool): Indicates whether to delete
|
||||
the volume on instance termination.
|
||||
no_device (str): Suppresses the device mapping.
|
||||
subnet_id (str): The ID of the subnet to launch the instance into.
|
||||
private_ip_address (str): The primary IP address.
|
||||
You must specify a value from the IP address range
|
||||
of the subnet.
|
||||
client_token (str): Unique, case-sensitive identifier you provide
|
||||
to ensure idempotency of the request.
|
||||
network_interface (list of dicts): Dict can contain:
|
||||
network_interface_id (str): An existing interface to attach
|
||||
to a single instance. Requires n=1 instances.
|
||||
@ -1053,9 +1079,6 @@ class CloudController(object):
|
||||
IP address using private_ip_address.
|
||||
associate_public_ip_address (boolean): Indicates whether
|
||||
to assign a public IP address to an instance in a VPC.
|
||||
security_group (list of str): One or more security group names.
|
||||
For a nondefault VPC, you must use security_group_id.
|
||||
security_group_id (list of str): One or more security group IDs.
|
||||
kwargs: Other arguments supported by AWS EC2.
|
||||
|
||||
Returns:
|
||||
@ -1065,9 +1088,12 @@ class CloudController(object):
|
||||
uses the default security group.
|
||||
"""
|
||||
return instance.run_instances(context, image_id, min_count, max_count,
|
||||
subnet_id, private_ip_address,
|
||||
network_interface, security_group,
|
||||
security_group_id, **kwargs)
|
||||
key_name, security_group_id,
|
||||
security_group, user_data, instance_type,
|
||||
placement, kernel_id, ramdisk_id,
|
||||
block_device_mapping, subnet_id,
|
||||
private_ip_address, client_token,
|
||||
network_interface, **kwargs)
|
||||
|
||||
def terminate_instances(self, context, instance_id):
|
||||
"""Shuts down one or more instances.
|
||||
|
@ -175,6 +175,16 @@ def is_ec2_timestamp_expired(request, expires=None):
|
||||
return True
|
||||
|
||||
|
||||
def id_to_glance_id(context, image_id):
|
||||
"""Convert an internal (db) id to a glance id."""
|
||||
return novadb.s3_image_get(context, image_id)['uuid']
|
||||
|
||||
|
||||
def ec2_id_to_glance_id(context, ec2_id):
|
||||
image_id = ec2_id_to_id(ec2_id)
|
||||
return id_to_glance_id(context, image_id)
|
||||
|
||||
|
||||
# TODO(Alex) This function is copied as is from original cloud.py. It doesn't
|
||||
# check for the prefix which allows any prefix used for any object.
|
||||
def ec2_id_to_id(ec2_id):
|
||||
@ -221,6 +231,31 @@ def get_int_id_from_instance_uuid(context, instance_uuid):
|
||||
return novadb.ec2_instance_create(context, instance_uuid)['id']
|
||||
|
||||
|
||||
def get_volume_uuid_from_int_id(context, int_id):
|
||||
return novadb.get_volume_uuid_by_ec2_id(context, int_id)
|
||||
|
||||
|
||||
def ec2_vol_id_to_uuid(ec2_id):
|
||||
"""Get the corresponding UUID for the given ec2-id."""
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
# NOTE(jgriffith) first strip prefix to get just the numeric
|
||||
int_id = ec2_id_to_id(ec2_id)
|
||||
return get_volume_uuid_from_int_id(ctxt, int_id)
|
||||
|
||||
|
||||
def get_snapshot_uuid_from_int_id(context, int_id):
|
||||
return novadb.get_snapshot_uuid_by_ec2_id(context, int_id)
|
||||
|
||||
|
||||
def ec2_snap_id_to_uuid(ec2_id):
|
||||
"""Get the corresponding UUID for the given ec2-id."""
|
||||
ctxt = context.get_admin_context()
|
||||
|
||||
# NOTE(jgriffith) first strip prefix to get just the numeric
|
||||
int_id = ec2_id_to_id(ec2_id)
|
||||
return get_snapshot_uuid_from_int_id(ctxt, int_id)
|
||||
|
||||
# NOTE(ft): extra functions to use in vpc specific code or instead of
|
||||
# malformed existed functions
|
||||
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import random
|
||||
import re
|
||||
|
||||
from ec2api.api import clients
|
||||
from ec2api.api import ec2client
|
||||
@ -35,9 +37,12 @@ from ec2api.openstack.common import timeutils
|
||||
|
||||
|
||||
def run_instances(context, image_id, min_count, max_count,
|
||||
subnet_id=None, private_ip_address=None,
|
||||
network_interface=None, security_group=None,
|
||||
security_group_id=None, **kwargs):
|
||||
key_name=None, security_group_id=None,
|
||||
security_group=None, user_data=None, instance_type=None,
|
||||
placement=None, kernel_id=None, ramdisk_id=None,
|
||||
block_device_mapping=None, subnet_id=None,
|
||||
private_ip_address=None, client_token=None,
|
||||
network_interface=None, **kwargs):
|
||||
# TODO(ft): fix passing complex network parameters create_network_interface
|
||||
# TODO(ft): check the compatibility of complex network parameters and
|
||||
# multiple running
|
||||
@ -45,7 +50,23 @@ def run_instances(context, image_id, min_count, max_count,
|
||||
# network interface params function
|
||||
_check_min_max_count(min_count, max_count)
|
||||
|
||||
(ec2_security_groups,
|
||||
# TODO(ft): support client tokens
|
||||
|
||||
os_image, os_kernel_id, os_ramdisk_id = _parse_image_parameters(
|
||||
context, image_id, kernel_id, ramdisk_id)
|
||||
|
||||
nova = clients.nova(context)
|
||||
os_flavor = next((f for f in nova.flavors.list()
|
||||
if f.name == instance_type), None)
|
||||
if not os_flavor:
|
||||
raise exception.InvalidParameterValue(value=instance_type,
|
||||
parameter='InstanceType')
|
||||
|
||||
bdm = _parse_block_device_mapping(block_device_mapping, os_image)
|
||||
|
||||
# TODO(ft): support auto_assign_floating_ip
|
||||
|
||||
(security_groups_names,
|
||||
vpc_network_parameters) = _merge_network_interface_parameters(
|
||||
security_group,
|
||||
subnet_id, private_ip_address, security_group_id,
|
||||
@ -98,34 +119,44 @@ def run_instances(context, image_id, min_count, max_count,
|
||||
|
||||
ec2 = ec2client.ec2client(context)
|
||||
# NOTE(ft): run instances one by one using created ports
|
||||
ec2_instance_network_pairs = []
|
||||
network_interfaces_by_instances = {}
|
||||
ec2_instance_ids = []
|
||||
for network_interfaces in instance_network_interfaces:
|
||||
arg_network_interfaces = [{'network_interface_id': eni['os_id']}
|
||||
for eni in network_interfaces]
|
||||
ec2_reservation = ec2.run_instances(
|
||||
image_id=image_id,
|
||||
nics = [{'port-id': eni['os_id']} for eni in network_interfaces]
|
||||
os_instance = nova.servers.create(
|
||||
'EC2 server', os_image.id, os_flavor,
|
||||
min_count=1, max_count=1,
|
||||
network_interface=arg_network_interfaces,
|
||||
security_group=ec2_security_groups,
|
||||
**kwargs)
|
||||
ec2_instance = ec2_reservation['instancesSet'][0]
|
||||
kernel_id=os_kernel_id, ramdisk_id=os_ramdisk_id,
|
||||
availability_zone=(placement or {}).get('availability_zone'),
|
||||
block_device_mapping=bdm,
|
||||
security_group=security_groups_names,
|
||||
nics=nics,
|
||||
key_name=key_name, userdata=user_data)
|
||||
|
||||
ec2_instance_id = ec2utils.id_to_ec2_inst_id(os_instance.id)
|
||||
cleaner.addCleanup(ec2.terminate_instances,
|
||||
instance_id=ec2_instance['instanceId'])
|
||||
ec2_instance_network_pairs.append((ec2_instance,
|
||||
network_interfaces,))
|
||||
instance_id=ec2_instance_id)
|
||||
nova.servers.update(os_instance, name=ec2_instance_id)
|
||||
|
||||
network_interfaces_by_instances[ec2_instance_id] = (
|
||||
network_interfaces)
|
||||
ec2_instance_ids.append(ec2_instance_id)
|
||||
|
||||
# TODO(ft): receive port from a create_network_interface sub-function
|
||||
os_ports = neutron.list_ports()['ports']
|
||||
os_ports = dict((p['id'], p) for p in os_ports)
|
||||
ec2_instances = ec2.describe_instances(instance_id=ec2_instance_ids)
|
||||
ec2_instances = [i for r in ec2_instances['reservationSet']
|
||||
for i in r['instancesSet']]
|
||||
attach_time = timeutils.isotime(None, True)
|
||||
# TODO(ft): Process min and max counts on running errors accordingly to
|
||||
# their meanings. Correct error messages are also critical
|
||||
ec2_instances = []
|
||||
for ec2_instance, network_interfaces in ec2_instance_network_pairs:
|
||||
for ec2_instance in ec2_instances:
|
||||
instance_ports_info = []
|
||||
instance_id = ec2utils.ec2_id_to_id(ec2_instance['instanceId'])
|
||||
delete_on_termination = iter(delete_on_termination_flags)
|
||||
for network_interface in network_interfaces:
|
||||
for network_interface in network_interfaces_by_instances[
|
||||
ec2_instance['instanceId']]:
|
||||
# TODO(ft): implement update items in DB layer to prevent
|
||||
# record by record modification
|
||||
# Alternatively a create_network_interface sub-function can
|
||||
@ -141,17 +172,16 @@ def run_instances(context, image_id, min_count, max_count,
|
||||
os_port = os_ports[network_interface['os_id']]
|
||||
instance_ports_info.append((network_interface, os_port, [],))
|
||||
|
||||
_format_instance(context, ec2_instance, instance_ports_info,
|
||||
security_groups)
|
||||
ec2_instances.append(ec2_instance)
|
||||
_format_instance(context, ec2_instance,
|
||||
instance_ports_info, security_groups)
|
||||
|
||||
# TODO(ft): since we run instances separately each instance has its
|
||||
# own ec2_reservation id. Now we return ec2_reservation id of
|
||||
# the last started instance
|
||||
# If we aren't able to update OpenStack to fit ec2 requirements,
|
||||
# we should have our own ec2_reservation id to use it instead of Nova's.
|
||||
ec2_reservation['instancesSet'] = ec2_instances
|
||||
return ec2_reservation
|
||||
ec2_reservation_id = _generate_reservation_id()
|
||||
return _format_reservation(context, ec2_reservation_id, ec2_instances)
|
||||
|
||||
|
||||
def terminate_instances(context, instance_id):
|
||||
@ -267,7 +297,20 @@ def _format_instance(context, ec2_instance, ports_info, security_groups):
|
||||
return ec2_instance
|
||||
|
||||
|
||||
def _format_reservation(context, ec2_reservation_id, ec2_instances):
|
||||
return {'reservationId': ec2_reservation_id,
|
||||
'ownerId': context.project_id,
|
||||
'instancesSet': ec2_instances,
|
||||
# TODO(ft): Check AWS behavior: can it start zero instances with
|
||||
# successfull result?
|
||||
'groupSet': ec2_instances[0].get('groupSet')}
|
||||
|
||||
|
||||
def _check_min_max_count(min_count, max_count):
|
||||
# TODO(ft): figure out appropriate aws message and use them
|
||||
min_count = int(min_count)
|
||||
max_count = int(max_count)
|
||||
|
||||
if min_count < 1:
|
||||
msg = _('Minimum instance count must be greater than zero')
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
@ -280,6 +323,52 @@ def _check_min_max_count(min_count, max_count):
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
|
||||
|
||||
def _parse_image_parameters(context, image_id, kernel_id, ramdisk_id):
|
||||
glance = clients.glance(context)
|
||||
if kernel_id:
|
||||
os_kernel_id = ec2utils.ec2_id_to_glance_id(context, kernel_id)
|
||||
glance.images.get(os_kernel_id)
|
||||
if ramdisk_id:
|
||||
os_ramdisk_id = ec2utils.ec2_id_to_glance_id(context, ramdisk_id)
|
||||
glance.images.get(os_ramdisk_id)
|
||||
os_image_id = ec2utils.ec2_id_to_glance_id(context, image_id)
|
||||
os_image = glance.images.get(os_image_id)
|
||||
|
||||
if _cloud_get_image_state(os_image) != 'available':
|
||||
# TODO(ft): Change the message with the real AWS message
|
||||
msg = _('Image must be available')
|
||||
raise exception.ImageNotActive(message=msg)
|
||||
|
||||
return os_image, kernel_id, ramdisk_id
|
||||
|
||||
|
||||
def _parse_block_device_mapping(block_device_mapping, os_image):
|
||||
# NOTE(ft): The following code allows reconfiguration of devices
|
||||
# according to list of new parameters supplied in EC2 call.
|
||||
# This code merges these parameters with information taken from image.
|
||||
image_root_device_name = os_image.properties.get('root_device_name')
|
||||
image_bdm = dict(
|
||||
(_block_device_strip_dev(bd.get('device_name') or
|
||||
image_root_device_name),
|
||||
bd)
|
||||
for bd in os_image.properties.get('block_device_mapping', [])
|
||||
if bd.get('device_name') or bd.get('boot_index') == 0)
|
||||
|
||||
for args_bd in (block_device_mapping or []):
|
||||
_cloud_parse_block_device_mapping(args_bd)
|
||||
dev_name = _block_device_strip_dev(args_bd.get('device_name'))
|
||||
if (not dev_name or dev_name not in image_bdm or
|
||||
'snapshot_id' in args_bd or 'volume_id' in args_bd):
|
||||
continue
|
||||
image_bd = image_bdm[dev_name]
|
||||
for key in ('device_name', 'delete_on_termination', 'virtual_name',
|
||||
'snapshot_id', 'volume_id', 'volume_size',
|
||||
'no_device'):
|
||||
args_bd[key] = args_bd.get(key, image_bd.get(key))
|
||||
|
||||
return block_device_mapping
|
||||
|
||||
|
||||
def _merge_network_interface_parameters(security_group_names,
|
||||
subnet_id,
|
||||
private_ip_address,
|
||||
@ -441,3 +530,57 @@ def _create_network_interfaces(context, cleaner, params):
|
||||
network_interfaces.append(network_interface)
|
||||
|
||||
return network_interfaces
|
||||
|
||||
|
||||
# NOTE(ft): following functions are copied from various parts of Nova
|
||||
|
||||
_dev = re.compile('^/dev/')
|
||||
|
||||
|
||||
def _block_device_strip_dev(device_name):
|
||||
"""remove leading '/dev/'."""
|
||||
return _dev.sub('', device_name) if device_name else device_name
|
||||
|
||||
|
||||
def _cloud_parse_block_device_mapping(bdm):
|
||||
"""Parse BlockDeviceMappingItemType into flat hash
|
||||
|
||||
BlockDevicedMapping.<N>.DeviceName
|
||||
BlockDevicedMapping.<N>.Ebs.SnapshotId
|
||||
BlockDevicedMapping.<N>.Ebs.VolumeSize
|
||||
BlockDevicedMapping.<N>.Ebs.DeleteOnTermination
|
||||
BlockDevicedMapping.<N>.Ebs.NoDevice
|
||||
BlockDevicedMapping.<N>.VirtualName
|
||||
=> remove .Ebs and allow volume id in SnapshotId
|
||||
"""
|
||||
ebs = bdm.pop('ebs', None)
|
||||
if ebs:
|
||||
ec2_id = ebs.pop('snapshot_id', None)
|
||||
if ec2_id:
|
||||
if ec2_id.startswith('snap-'):
|
||||
bdm['snapshot_id'] = ec2utils.ec2_snap_id_to_uuid(ec2_id)
|
||||
elif ec2_id.startswith('vol-'):
|
||||
bdm['volume_id'] = ec2utils.ec2_vol_id_to_uuid(ec2_id)
|
||||
else:
|
||||
# NOTE(ft): AWS returns undocumented InvalidSnapshotID.NotFound
|
||||
raise exception.InvalidSnapshotIDMalformed(snapshot_id=ec2_id)
|
||||
ebs.setdefault('delete_on_termination', True)
|
||||
bdm.update(ebs)
|
||||
return bdm
|
||||
|
||||
|
||||
def _utils_generate_uid(topic, size=8):
|
||||
characters = '01234567890abcdefghijklmnopqrstuvwxyz'
|
||||
choices = [random.choice(characters) for _x in xrange(size)]
|
||||
return '%s-%s' % (topic, ''.join(choices))
|
||||
|
||||
|
||||
def _generate_reservation_id():
|
||||
return _utils_generate_uid('r')
|
||||
|
||||
|
||||
def _cloud_get_image_state(image):
|
||||
state = image.status
|
||||
if state == 'active':
|
||||
state = 'available'
|
||||
return image.properties.get('image_state', state)
|
||||
|
@ -142,6 +142,21 @@ class EC2NotFound(NotFound):
|
||||
code = 400
|
||||
|
||||
|
||||
class ImageNotFound(EC2NotFound):
|
||||
ec2_code = 'InvalidAMIID.NotFound'
|
||||
msg_fmt = _("The image id '[%(image_id)s]' does not exist")
|
||||
|
||||
|
||||
class VolumeNotFound(NotFound):
|
||||
ec2_code = 'InvalidVolume.NotFound'
|
||||
msg_fmt = _("Volume %(volume_id)s could not be found.")
|
||||
|
||||
|
||||
class SnapshotNotFound(NotFound):
|
||||
ec2_code = 'InvalidSnapshot.NotFound'
|
||||
msg_fmt = _("Snapshot %(snapshot_id)s could not be found.")
|
||||
|
||||
|
||||
class InstanceNotFound(EC2NotFound):
|
||||
ec2_code = 'InvalidInstanceID.NotFound'
|
||||
msg_fmt = _("Instance %(instance_id)s could not be found.")
|
||||
@ -300,3 +315,15 @@ class NetworkInterfaceLimitExceeded(Invalid):
|
||||
# TODO(Alex) Change next class with the real AWS exception
|
||||
class RuleAlreadyExists(Invalid):
|
||||
msg_fmt = _('The rule already exists.')
|
||||
|
||||
|
||||
class ImageNotActive(Invalid):
|
||||
ec2_code = 'InvalidAMIID.Unavailable'
|
||||
# TODO(ft): Change the message with the real AWS message
|
||||
msg_fmt = _("Image %(image_id)s is not active.")
|
||||
|
||||
|
||||
class InvalidSnapshotIDMalformed(Invalid):
|
||||
ec2_code = 'InvalidSnapshotID.Malformed'
|
||||
# TODO(ft): Change the message with the real AWS message
|
||||
msg_fmg = _('The snapshot %(snapshot_id)s ID is not valid')
|
||||
|
@ -80,6 +80,25 @@ MAX_INT = 0x7FFFFFFF
|
||||
####################
|
||||
|
||||
|
||||
def s3_image_get(context, image_id):
|
||||
"""Find local s3 image represented by the provided id."""
|
||||
return IMPL.s3_image_get(context, image_id)
|
||||
|
||||
|
||||
###################
|
||||
|
||||
|
||||
def get_volume_uuid_by_ec2_id(context, ec2_id):
|
||||
return IMPL.get_volume_uuid_by_ec2_id(context, ec2_id)
|
||||
|
||||
|
||||
def get_snapshot_uuid_by_ec2_id(context, ec2_id):
|
||||
return IMPL.get_snapshot_uuid_by_ec2_id(context, ec2_id)
|
||||
|
||||
|
||||
###################
|
||||
|
||||
|
||||
def get_ec2_instance_id_by_uuid(context, instance_id):
|
||||
"""Get ec2 id through uuid from instance_id_mappings table."""
|
||||
return IMPL.get_ec2_instance_id_by_uuid(context, instance_id)
|
||||
|
@ -163,9 +163,52 @@ def model_query(context, model, *args, **kwargs):
|
||||
return query
|
||||
|
||||
|
||||
####################
|
||||
|
||||
|
||||
def s3_image_get(context, image_id):
|
||||
"""Find local s3 image represented by the provided id."""
|
||||
result = (model_query(context, models.S3Image, read_deleted="yes").
|
||||
filter_by(id=image_id).
|
||||
first())
|
||||
|
||||
if not result:
|
||||
raise exception.ImageNotFound(image_id=image_id)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
##################
|
||||
|
||||
|
||||
@require_context
|
||||
def get_volume_uuid_by_ec2_id(context, ec2_id):
|
||||
result = (model_query(context, models.VolumeIdMapping, read_deleted='yes').
|
||||
filter_by(id=ec2_id).
|
||||
first())
|
||||
|
||||
if not result:
|
||||
raise exception.VolumeNotFound(volume_id=ec2_id)
|
||||
|
||||
return result['uuid']
|
||||
|
||||
|
||||
@require_context
|
||||
def get_snapshot_uuid_by_ec2_id(context, ec2_id):
|
||||
result = (model_query(context, models.SnapshotIdMapping,
|
||||
read_deleted='yes').
|
||||
filter_by(id=ec2_id).
|
||||
first())
|
||||
|
||||
if not result:
|
||||
raise exception.SnapshotNotFound(snapshot_id=ec2_id)
|
||||
|
||||
return result['uuid']
|
||||
|
||||
|
||||
###################
|
||||
|
||||
|
||||
@require_context
|
||||
def ec2_instance_create(context, instance_uuid, id=None):
|
||||
"""Create ec2 compatible instance by provided uuid."""
|
||||
|
@ -49,6 +49,30 @@ class NovaBase(models.SoftDeleteMixin,
|
||||
super(NovaBase, self).save(session=session)
|
||||
|
||||
|
||||
class S3Image(BASE, NovaBase):
|
||||
"""Compatibility layer for the S3 image service talking to Glance."""
|
||||
__tablename__ = 's3_images'
|
||||
__table_args__ = ()
|
||||
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
|
||||
uuid = Column(String(36), nullable=False)
|
||||
|
||||
|
||||
class VolumeIdMapping(BASE, NovaBase):
|
||||
"""Compatibility layer for the EC2 volume service."""
|
||||
__tablename__ = 'volume_id_mappings'
|
||||
__table_args__ = ()
|
||||
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
|
||||
uuid = Column(String(36), nullable=False)
|
||||
|
||||
|
||||
class SnapshotIdMapping(BASE, NovaBase):
|
||||
"""Compatibility layer for the EC2 snapshot service."""
|
||||
__tablename__ = 'snapshot_id_mappings'
|
||||
__table_args__ = ()
|
||||
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
|
||||
uuid = Column(String(36), nullable=False)
|
||||
|
||||
|
||||
class InstanceIdMapping(BASE, NovaBase):
|
||||
"""Compatibility layer for the EC2 instance service."""
|
||||
__tablename__ = 'instance_id_mappings'
|
||||
|
@ -36,9 +36,11 @@ class ApiTestCase(test_base.BaseTestCase):
|
||||
neutron_patcher = mock.patch('neutronclient.v2_0.client.Client')
|
||||
self.neutron = neutron_patcher.start().return_value
|
||||
self.addCleanup(neutron_patcher.stop)
|
||||
nova_servers_patcher = mock.patch('novaclient.v1_1.client.Client')
|
||||
self.nova_servers = nova_servers_patcher.start().return_value.servers
|
||||
self.addCleanup(nova_servers_patcher.stop)
|
||||
nova_patcher = mock.patch('novaclient.v1_1.client.Client')
|
||||
nova_mock = nova_patcher.start()
|
||||
self.nova_servers = nova_mock.return_value.servers
|
||||
self.nova_flavors = nova_mock.return_value.flavors
|
||||
self.addCleanup(nova_patcher.stop)
|
||||
db_api_patcher = mock.patch('ec2api.db.api.IMPL')
|
||||
self.db_api = db_api_patcher.start()
|
||||
self.addCleanup(db_api_patcher.stop)
|
||||
|
@ -160,7 +160,8 @@ ID_EC2_INSTANCE_1 = ec2utils.get_ec2_id(ID_DB_INSTANCE_1, 'i')
|
||||
ID_EC2_INSTANCE_2 = ec2utils.get_ec2_id(ID_DB_INSTANCE_2, 'i')
|
||||
ID_OS_INSTANCE_1 = random_os_id()
|
||||
ID_OS_INSTANCE_2 = random_os_id()
|
||||
|
||||
ID_EC2_RESERVATION_1 = 'r-%s' % random_db_id()
|
||||
ID_EC2_RESERVATION_2 = 'r-%s' % random_db_id()
|
||||
|
||||
# DHCP options constants
|
||||
ID_DB_DHCP_OPTIONS_1 = random_db_id()
|
||||
@ -838,7 +839,9 @@ def gen_ec2_instance(ec2_instance_id, private_ip_address='',
|
||||
return ec2_instance
|
||||
|
||||
|
||||
def gen_ec2_reservation(ec2_instances):
|
||||
def gen_ec2_reservation(ec2_reservation_id, ec2_instances):
|
||||
"""Generate EC2 Reservation dictionary."""
|
||||
return {'instancesSet': [inst for inst in ec2_instances],
|
||||
'fakeKey': 'fakeValue'}
|
||||
return {'reservationId': ec2_reservation_id,
|
||||
'ownerId': ID_OS_PROJECT,
|
||||
'instancesSet': [inst for inst in ec2_instances],
|
||||
'groupSet': []}
|
||||
|
@ -13,6 +13,7 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import itertools
|
||||
|
||||
@ -36,6 +37,28 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
self.create_network_interface = (
|
||||
create_network_interface_patcher.start())
|
||||
self.addCleanup(create_network_interface_patcher.stop)
|
||||
glance_patcher = mock.patch('glanceclient.client.Client')
|
||||
self.glance = glance_patcher.start().return_value
|
||||
self.addCleanup(glance_patcher.stop)
|
||||
ec2_id_to_glance_id_patcher = (
|
||||
mock.patch('ec2api.api.ec2utils.ec2_id_to_glance_id'))
|
||||
self.ec2_id_to_glance_id = ec2_id_to_glance_id_patcher.start()
|
||||
self.addCleanup(ec2_id_to_glance_id_patcher.stop)
|
||||
id_to_ec2_inst_id_patcher = (
|
||||
mock.patch('ec2api.api.ec2utils.id_to_ec2_inst_id'))
|
||||
self.id_to_ec2_inst_id = id_to_ec2_inst_id_patcher.start()
|
||||
self.addCleanup(id_to_ec2_inst_id_patcher.stop)
|
||||
utils_generate_uid_patcher = (
|
||||
mock.patch('ec2api.api.instance._utils_generate_uid'))
|
||||
self.utils_generate_uid = utils_generate_uid_patcher.start()
|
||||
self.addCleanup(utils_generate_uid_patcher.stop)
|
||||
|
||||
self.fake_image_class = collections.namedtuple(
|
||||
'FakeImage', ['id', 'status', 'properties'])
|
||||
self.fake_flavor_class = collections.namedtuple(
|
||||
'FakeFlavor', ['name'])
|
||||
self.fake_instance_class = collections.namedtuple(
|
||||
'FakeInstance', ['id'])
|
||||
|
||||
def test_run_instances(self):
|
||||
"""Run instance with various network interface settings."""
|
||||
@ -48,13 +71,26 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
{'ports': [fakes.OS_PORT_1, fakes.OS_PORT_2]})
|
||||
self.create_network_interface.return_value = (
|
||||
{'networkInterface': fakes.EC2_NETWORK_INTERFACE_1})
|
||||
self.ec2.run_instances.return_value = (
|
||||
fakes.gen_ec2_reservation([fakes.gen_ec2_instance(
|
||||
fakes.ID_EC2_INSTANCE_1, private_ip_address=None)]))
|
||||
self.ec2.describe_instances.return_value = {
|
||||
'reservationSet': [fakes.gen_ec2_reservation(
|
||||
fakes.ID_EC2_RESERVATION_1,
|
||||
[fakes.gen_ec2_instance(fakes.ID_EC2_INSTANCE_1,
|
||||
private_ip_address=None)])]}
|
||||
self.isotime.return_value = fakes.TIME_ATTACH_NETWORK_INTERFACE
|
||||
self.id_to_ec2_inst_id.return_value = fakes.ID_EC2_INSTANCE_1
|
||||
self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1
|
||||
|
||||
self.glance.images.get.return_value = self.fake_image_class(
|
||||
'fake_image_id', 'active', {})
|
||||
self.ec2_id_to_glance_id.return_value = 'fake_image_id'
|
||||
fake_flavor = self.fake_flavor_class('fake_flavor')
|
||||
self.nova_flavors.list.return_value = [fake_flavor]
|
||||
self.nova_servers.create.return_value = self.fake_instance_class(
|
||||
fakes.ID_OS_INSTANCE_1)
|
||||
|
||||
def do_check(params, new_port=True, delete_on_termination=None):
|
||||
params.update({'ImageId': 'fake_image',
|
||||
params.update({'ImageId': 'ami-00000001',
|
||||
'InstanceType': 'fake_flavor',
|
||||
'MinCount': '1', 'MaxCount': '1'})
|
||||
resp = self.execute('RunInstances', params)
|
||||
self.assertEqual(200, resp['status'])
|
||||
@ -78,25 +114,30 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
ec2_instance_id=fakes.ID_EC2_INSTANCE_1,
|
||||
delete_on_termination=delete_port_on_termination,
|
||||
for_instance_output=True)
|
||||
expected_reservation = fakes.gen_ec2_reservation([
|
||||
fakes.gen_ec2_instance(
|
||||
expected_reservation = fakes.gen_ec2_reservation(
|
||||
fakes.ID_EC2_RESERVATION_1,
|
||||
[fakes.gen_ec2_instance(
|
||||
fakes.ID_EC2_INSTANCE_1, private_ip_address=None,
|
||||
ec2_network_interfaces=[eni])])
|
||||
self.assertThat(resp, matchers.DictMatches(expected_reservation))
|
||||
if new_port:
|
||||
self.create_network_interface.assert_called_once_with(
|
||||
mock.ANY, fakes.EC2_SUBNET_1['subnetId'])
|
||||
self.ec2.run_instances.assert_called_once_with(
|
||||
image_id='fake_image',
|
||||
self.nova_servers.create.assert_called_once_with(
|
||||
'EC2 server', 'fake_image_id', fake_flavor,
|
||||
min_count=1, max_count=1,
|
||||
kernel_id=None, ramdisk_id=None,
|
||||
availability_zone=None,
|
||||
block_device_mapping=None,
|
||||
security_group=None,
|
||||
network_interface=[
|
||||
{'network_interface_id': fakes.ID_OS_PORT_1}])
|
||||
nics=[{'port-id': fakes.ID_OS_PORT_1}],
|
||||
key_name=None, userdata=None)
|
||||
self.db_api.update_item.assert_called_once_with(
|
||||
mock.ANY, db_attached_eni)
|
||||
self.isotime.assert_called_once_with(None, True)
|
||||
|
||||
self.create_network_interface.reset_mock()
|
||||
self.nova_servers.reset_mock()
|
||||
self.ec2.reset_mock()
|
||||
self.db_api.reset_mock()
|
||||
self.isotime.reset_mock()
|
||||
@ -119,11 +160,14 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
"""Run 2 instances at once on 2 subnets in all combinations."""
|
||||
self._build_multiple_data_model()
|
||||
|
||||
ec2os_reservations = [
|
||||
fakes.gen_ec2_reservation([
|
||||
fakes.gen_ec2_instance(ec2_instance_id,
|
||||
private_ip_address=None)])
|
||||
for ec2_instance_id in self.IDS_EC2_INSTANCE]
|
||||
self.id_to_ec2_inst_id.side_effect = self.IDS_EC2_INSTANCE
|
||||
self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1
|
||||
|
||||
self.glance.images.get.return_value = self.fake_image_class(
|
||||
'fake_image_id', 'active', {})
|
||||
self.ec2_id_to_glance_id.return_value = 'fake_image_id'
|
||||
fake_flavor = self.fake_flavor_class('fake_flavor')
|
||||
self.nova_flavors.list.return_value = [fake_flavor]
|
||||
|
||||
ec2_instances = [
|
||||
fakes.gen_ec2_instance(
|
||||
@ -133,7 +177,8 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
for ec2_instance_id, eni_pair in zip(
|
||||
self.IDS_EC2_INSTANCE,
|
||||
zip(*[iter(self.EC2_ATTACHED_ENIS)] * 2))]
|
||||
ec2_reservation = fakes.gen_ec2_reservation(ec2_instances)
|
||||
ec2_reservation = fakes.gen_ec2_reservation(fakes.ID_EC2_RESERVATION_1,
|
||||
ec2_instances)
|
||||
|
||||
fakes_db_items = dict((eni['id'], eni)
|
||||
for eni in self.DB_DETACHED_ENIS)
|
||||
@ -145,16 +190,24 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
self.create_network_interface.side_effect = (
|
||||
[{'networkInterface': eni}
|
||||
for eni in self.EC2_DETACHED_ENIS])
|
||||
self.ec2.run_instances.side_effect = (
|
||||
[copy.deepcopy(r)
|
||||
for r in ec2os_reservations])
|
||||
self.ec2.describe_instances.return_value = {
|
||||
'reservationSet': [
|
||||
fakes.gen_ec2_reservation(
|
||||
fakes.ID_EC2_RESERVATION_1,
|
||||
[fakes.gen_ec2_instance(ec2_instance_id,
|
||||
private_ip_address=None)
|
||||
for ec2_instance_id in self.IDS_EC2_INSTANCE])]}
|
||||
self.nova_servers.create.side_effect = [
|
||||
self.fake_instance_class(os_instance_id)
|
||||
for os_instance_id in self.IDS_OS_INSTANCE]
|
||||
self.neutron.list_ports.return_value = (
|
||||
{'ports': self.OS_DETACHED_PORTS + [self.OS_FAKE_PORT]})
|
||||
self.isotime.return_value = fakes.TIME_ATTACH_NETWORK_INTERFACE
|
||||
|
||||
resp = self.execute(
|
||||
'RunInstances',
|
||||
{'ImageId': 'fake_image',
|
||||
{'ImageId': 'ami-00000001',
|
||||
'InstanceType': 'fake_flavor',
|
||||
'MinCount': '2',
|
||||
'MaxCount': '2',
|
||||
'NetworkInterface.1.SubnetId': fakes.ID_EC2_SUBNET_1,
|
||||
@ -168,13 +221,17 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
self.create_network_interface.assert_has_calls([
|
||||
mock.call(mock.ANY, ec2_subnet_id)
|
||||
for ec2_subnet_id in self.IDS_EC2_SUBNET_BY_PORT])
|
||||
self.ec2.run_instances.assert_has_calls([
|
||||
mock.call(image_id='fake_image',
|
||||
self.nova_servers.create.assert_has_calls([
|
||||
mock.call(
|
||||
'EC2 server', 'fake_image_id', fake_flavor,
|
||||
min_count=1, max_count=1,
|
||||
kernel_id=None, ramdisk_id=None,
|
||||
availability_zone=None,
|
||||
block_device_mapping=None,
|
||||
security_group=None,
|
||||
network_interface=[
|
||||
{'network_interface_id': port_id}
|
||||
for port_id in port_ids])
|
||||
nics=[{'port-id': port_id}
|
||||
for port_id in port_ids],
|
||||
key_name=None, userdata=None)
|
||||
for port_ids in zip(*[iter(self.IDS_OS_PORT)] * 2)])
|
||||
self.db_api.update_item.assert_has_calls([
|
||||
mock.call(mock.ANY, eni)
|
||||
@ -194,14 +251,27 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
{'ports': [fakes.OS_PORT_1, fakes.OS_PORT_2]})
|
||||
self.create_network_interface.return_value = (
|
||||
{'networkInterface': fakes.EC2_NETWORK_INTERFACE_1})
|
||||
self.ec2.run_instances.return_value = (
|
||||
fakes.gen_ec2_reservation([fakes.gen_ec2_instance(
|
||||
fakes.ID_EC2_INSTANCE_1, private_ip_address=None)]))
|
||||
self.ec2.describe_instances.return_value = {
|
||||
'reservationSet': [fakes.gen_ec2_reservation(
|
||||
fakes.ID_EC2_RESERVATION_1,
|
||||
[fakes.gen_ec2_instance(fakes.ID_EC2_INSTANCE_1,
|
||||
private_ip_address=None)])]}
|
||||
self.isotime.return_value = fakes.TIME_ATTACH_NETWORK_INTERFACE
|
||||
self.id_to_ec2_inst_id.return_value = fakes.ID_EC2_INSTANCE_1
|
||||
self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1
|
||||
|
||||
self.glance.images.get.return_value = self.fake_image_class(
|
||||
'fake_image_id', 'active', {})
|
||||
self.ec2_id_to_glance_id.return_value = 'fake_image_id'
|
||||
fake_flavor = self.fake_flavor_class('fake_flavor')
|
||||
self.nova_flavors.list.return_value = [fake_flavor]
|
||||
self.nova_servers.create.return_value = self.fake_instance_class(
|
||||
fakes.ID_OS_INSTANCE_1)
|
||||
format_instance.side_effect = Exception()
|
||||
|
||||
def do_check(params, new_port=True, delete_on_termination=None):
|
||||
params.update({'ImageId': 'fake_image',
|
||||
params.update({'ImageId': 'ami-00000001',
|
||||
'InstanceType': 'fake_flavor',
|
||||
'MinCount': '1', 'MaxCount': '1'})
|
||||
self.execute('RunInstances', params)
|
||||
|
||||
@ -419,10 +489,13 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
is_instance_ip_in_vpc_by_instance=[True, True]):
|
||||
def gen_reservation_set(instances):
|
||||
if separate_reservations:
|
||||
return [fakes.gen_ec2_reservation([instances[0]]),
|
||||
fakes.gen_ec2_reservation([instances[1]])]
|
||||
return [fakes.gen_ec2_reservation(
|
||||
fakes.ID_EC2_RESERVATION_1, [instances[0]]),
|
||||
fakes.gen_ec2_reservation(
|
||||
fakes.ID_EC2_RESERVATION_2, [instances[1]])]
|
||||
else:
|
||||
return [fakes.gen_ec2_reservation([instances[0],
|
||||
return [fakes.gen_ec2_reservation(
|
||||
fakes.ID_EC2_RESERVATION_1, [instances[0],
|
||||
instances[1]])]
|
||||
|
||||
instances = [fakes.gen_ec2_instance(inst_id, private_ip_address=ip)
|
||||
@ -634,29 +707,3 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
self.assertIn('device_id', list_ports_kwargs)
|
||||
self.assertEqual(sorted(instance_ids),
|
||||
sorted(list_ports_kwargs['device_id']))
|
||||
|
||||
|
||||
class InstanceIntegrationTestCase(base.ApiTestCase):
|
||||
|
||||
def test_run_instances(self):
|
||||
self.db_api.get_item_by_id.side_effect = (
|
||||
fakes.get_db_api_get_item_by_id(
|
||||
{fakes.ID_DB_SUBNET_1: fakes.DB_SUBNET_1,
|
||||
fakes.ID_DB_VPC_1: fakes.DB_VPC_1,
|
||||
fakes.ID_DB_NETWORK_INTERFACE_1:
|
||||
fakes.DB_NETWORK_INTERFACE_1}))
|
||||
self.db_api.add_item.return_value = fakes.DB_NETWORK_INTERFACE_1
|
||||
self.neutron.show_subnet.return_value = {'subnet': fakes.OS_SUBNET_1}
|
||||
self.neutron.create_port.return_value = {'port': fakes.OS_PORT_1}
|
||||
self.neutron.list_ports.return_value = {'ports': [fakes.OS_PORT_1]}
|
||||
self.ec2.run_instances.return_value = (
|
||||
fakes.gen_ec2_reservation([fakes.gen_ec2_instance(
|
||||
fakes.ID_EC2_INSTANCE_1, private_ip_address=None)]))
|
||||
self.isotime.return_value = fakes.TIME_ATTACH_NETWORK_INTERFACE
|
||||
|
||||
resp = self.execute('RunInstances',
|
||||
{'ImageId': 'fake_image',
|
||||
'MinCount': '1', 'MaxCount': '1',
|
||||
'SubnetId': fakes.ID_EC2_SUBNET_1})
|
||||
|
||||
self.assertEqual(200, resp['status'])
|
||||
|
@ -15,6 +15,7 @@ PasteDeploy>=1.5.0
|
||||
pbr>=0.6,!=0.7,<1.0
|
||||
pyasn1
|
||||
python-keystoneclient>=0.9.0
|
||||
python-glanceclient>=0.14.0
|
||||
python-neutronclient>=2.3.6,<3
|
||||
python-novaclient>=2.17.0
|
||||
Routes>=1.12.3,!=2.0
|
||||
|
Loading…
Reference in New Issue
Block a user