Implement run_instance independently of Nova

Change-Id: I0f0ab143db55a545521fb0162955a6c432034fba
This commit is contained in:
Feodor Tersin 2014-09-29 14:04:40 +04:00
parent ae17e49c56
commit d0d6ac78d7
11 changed files with 472 additions and 102 deletions

View File

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

View File

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

View File

@ -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,
min_count=1, max_count=1,
network_interface=arg_network_interfaces,
security_group=ec2_security_groups,
**kwargs)
ec2_instance = ec2_reservation['instancesSet'][0]
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,
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)

View File

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

View File

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

View File

@ -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."""

View File

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

View File

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

View File

@ -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': []}

View File

@ -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',
min_count=1, max_count=1,
security_group=None,
network_interface=[
{'network_interface_id': port_id}
for port_id in port_ids])
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,
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,11 +489,14 @@ 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],
instances[1]])]
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)
for inst_id, ip in zip(
@ -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'])

View File

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