Port tests for instances
Change-Id: Ia2443478e86aeaef0a5833cae0ed09d519b2f51c
This commit is contained in:
parent
33221d6806
commit
f06c5b190d
|
@ -298,16 +298,20 @@ def describe_instances(context, instance_id=None, filter=None,
|
|||
|
||||
def reboot_instances(context, instance_id):
|
||||
return _foreach_instance(context, instance_id,
|
||||
(vm_states_ALLOW_SOFT_REBOOT +
|
||||
vm_states_ALLOW_HARD_REBOOT),
|
||||
lambda instance: instance.reboot())
|
||||
|
||||
|
||||
def stop_instances(context, instance_id, force=False):
|
||||
return _foreach_instance(context, instance_id,
|
||||
[vm_states_ACTIVE, vm_states_RESCUED,
|
||||
vm_states_ERROR],
|
||||
lambda instance: instance.stop())
|
||||
|
||||
|
||||
def start_instances(context, instance_id):
|
||||
return _foreach_instance(context, instance_id,
|
||||
return _foreach_instance(context, instance_id, [vm_states_STOPPED],
|
||||
lambda instance: instance.start())
|
||||
|
||||
|
||||
|
@ -419,14 +423,10 @@ def _get_idempotent_run(context, client_token):
|
|||
instances_info = []
|
||||
instance_ids = []
|
||||
for os_instance in os_instances:
|
||||
instance = instances.pop(os_instance['id'])
|
||||
instance = instances[os_instance.id]
|
||||
novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id)
|
||||
instances_info.append((instance, os_instance, novadb_instance,))
|
||||
instance_ids.append(instance['id'])
|
||||
|
||||
# NOTE(ft): delete obsolete instances
|
||||
if instances:
|
||||
_remove_instances(context, instances.itervalues())
|
||||
if not instances_info:
|
||||
return
|
||||
ec2_network_interfaces = (
|
||||
|
@ -687,10 +687,15 @@ def _get_ip_info_for_instance(os_instance):
|
|||
return fixed_ip, fixed_ip6, floating_ip
|
||||
|
||||
|
||||
def _foreach_instance(context, instance_ids, func):
|
||||
def _foreach_instance(context, instance_ids, valid_states, func):
|
||||
instances = ec2utils.get_db_items(context, 'i', instance_ids)
|
||||
os_instances = _get_os_instances_by_instances(context, instances,
|
||||
exactly=True)
|
||||
for os_instance in os_instances:
|
||||
if getattr(os_instance, 'OS-EXT-STS:vm_state') not in valid_states:
|
||||
raise exception.IncorrectInstanceState(
|
||||
instance_id=next(inst['id'] for inst in instances
|
||||
if inst['os_id'] == os_instance.id))
|
||||
for os_instance in os_instances:
|
||||
func(os_instance)
|
||||
return True
|
||||
|
@ -699,15 +704,17 @@ def _foreach_instance(context, instance_ids, func):
|
|||
def _get_os_instances_by_instances(context, instances, exactly=False):
|
||||
nova = clients.nova(context)
|
||||
os_instances = []
|
||||
found_obsolete_instance = False
|
||||
obsolete_instances = []
|
||||
for instance in instances:
|
||||
try:
|
||||
os_instances.append(nova.servers.get(instance['os_id']))
|
||||
except nova_exception.NotFound:
|
||||
db_api.delete_item(context, instance['id'])
|
||||
found_obsolete_instance = True
|
||||
if found_obsolete_instance and exactly:
|
||||
raise exception.InvalidInstanceIDNotFound(id=instance['id'])
|
||||
obsolete_instances.append(instance)
|
||||
if obsolete_instances:
|
||||
_remove_instances(context, obsolete_instances)
|
||||
if exactly:
|
||||
raise exception.InvalidInstanceIDNotFound(id=instance['id'])
|
||||
|
||||
return os_instances
|
||||
|
||||
|
@ -747,10 +754,6 @@ class InstanceEngineNeutron(object):
|
|||
private_ip_address=None, client_token=None,
|
||||
network_interface=None, iam_instance_profile=None,
|
||||
ebs_optimized=None):
|
||||
# TODO(ft): fix passing complex network parameters to
|
||||
# create_network_interface
|
||||
# TODO(ft): check the compatibility of complex network parameters and
|
||||
# multiple running
|
||||
os_image, os_kernel_id, os_ramdisk_id = _parse_image_parameters(
|
||||
context, image_id, kernel_id, ramdisk_id)
|
||||
|
||||
|
@ -906,6 +909,60 @@ class InstanceEngineNeutron(object):
|
|||
eni['attachment']['instanceId']].append(eni)
|
||||
return ec2_network_interfaces
|
||||
|
||||
def merge_network_interface_parameters(self,
|
||||
security_group_names,
|
||||
subnet_id,
|
||||
private_ip_address,
|
||||
security_group_ids,
|
||||
network_interfaces):
|
||||
network_interfaces = network_interfaces or []
|
||||
|
||||
if ((subnet_id or private_ip_address or security_group_ids or
|
||||
security_group_names) and
|
||||
(len(network_interfaces) > 1 or
|
||||
# NOTE(ft): the only case in AWS when simple subnet_id
|
||||
# and/or private_ip_address parameters are compatible with
|
||||
# network_interface parameter is default behavior change of
|
||||
# public IP association for passed subnet_id by specifying
|
||||
# the only element in network_interfaces:
|
||||
# {"device_index": 0,
|
||||
# "associate_public_ip_address": <boolean>}
|
||||
# Both keys must be in the dict, and no other keys
|
||||
# are allowed
|
||||
# We should support such combination of parameters for
|
||||
# compatibility purposes, even if we ignore device_index
|
||||
# and associate_public_ip_address in all other code
|
||||
len(network_interfaces) == 1 and
|
||||
(len(network_interfaces[0]) != 2 or
|
||||
'associate_public_ip_address' not in network_interfaces[0]
|
||||
or 'device_index' not in network_interfaces[0]))):
|
||||
msg = _(' Network interfaces and an instance-level subnet ID or '
|
||||
'private IP address or security groups may not be '
|
||||
'specified on the same request')
|
||||
raise exception.InvalidParameterCombination(msg)
|
||||
|
||||
if subnet_id:
|
||||
if security_group_names:
|
||||
msg = _('The parameter groupName cannot be used with '
|
||||
'the parameter subnet')
|
||||
raise exception.InvalidParameterCombination(msg)
|
||||
param = {'subnet_id': subnet_id}
|
||||
if private_ip_address:
|
||||
param['private_ip_address'] = private_ip_address
|
||||
if security_group_ids:
|
||||
param['security_group_id'] = security_group_ids
|
||||
return None, [param]
|
||||
elif private_ip_address:
|
||||
msg = _('Specifying an IP address is only valid for VPC instances '
|
||||
'and thus requires a subnet in which to launch')
|
||||
raise exception.InvalidParameterCombination(msg)
|
||||
elif security_group_ids:
|
||||
msg = _('VPC security groups may not be used for a non-VPC launch')
|
||||
raise exception.InvalidParameterCombination(msg)
|
||||
else:
|
||||
# NOTE(ft): only one of this variables is not empty
|
||||
return security_group_names, network_interfaces
|
||||
|
||||
def check_network_interface_parameters(self, params,
|
||||
min_instance_count,
|
||||
max_instance_count):
|
||||
|
@ -1052,60 +1109,6 @@ class InstanceEngineNeutron(object):
|
|||
'for EC2 Classic mode'))
|
||||
return ec2_classic_os_networks[0]
|
||||
|
||||
def merge_network_interface_parameters(self,
|
||||
security_group_names,
|
||||
subnet_id,
|
||||
private_ip_address,
|
||||
security_group_ids,
|
||||
network_interfaces):
|
||||
network_interfaces = network_interfaces or []
|
||||
|
||||
if ((subnet_id or private_ip_address or security_group_ids or
|
||||
security_group_names) and
|
||||
(len(network_interfaces) > 1 or
|
||||
# NOTE(ft): the only case in AWS when simple subnet_id
|
||||
# and/or private_ip_address parameters are compatible with
|
||||
# network_interface parameter is default behavior change of
|
||||
# public IP association for passed subnet_id by specifying
|
||||
# the only element in network_interfaces:
|
||||
# {"device_index": 0,
|
||||
# "associate_public_ip_address": <boolean>}
|
||||
# Both keys must be in the dict, and no other keys
|
||||
# are allowed
|
||||
# We should support such combination of parameters for
|
||||
# compatibility purposes, even if we ignore device_index
|
||||
# and associate_public_ip_address in all other code
|
||||
len(network_interfaces) == 1 and
|
||||
(len(network_interfaces[0]) != 2 or
|
||||
'associate_public_ip_address' not in network_interfaces[0]
|
||||
or 'device_index' not in network_interfaces[0]))):
|
||||
msg = _(' Network interfaces and an instance-level subnet ID or '
|
||||
'private IP address or security groups may not be '
|
||||
'specified on the same request')
|
||||
raise exception.InvalidParameterCombination(msg)
|
||||
|
||||
if subnet_id:
|
||||
if security_group_names:
|
||||
msg = _('The parameter groupName cannot be used with '
|
||||
'the parameter subnet')
|
||||
raise exception.InvalidParameterCombination(msg)
|
||||
param = {'subnet_id': subnet_id}
|
||||
if private_ip_address:
|
||||
param['private_ip_address'] = private_ip_address
|
||||
if security_group_ids:
|
||||
param['security_group_id'] = security_group_ids
|
||||
return None, [param]
|
||||
elif private_ip_address:
|
||||
msg = _('Specifying an IP address is only valid for VPC instances '
|
||||
'and thus requires a subnet in which to launch')
|
||||
raise exception.InvalidParameterCombination(msg)
|
||||
elif security_group_ids:
|
||||
msg = _('VPC security groups may not be used for a non-VPC launch')
|
||||
raise exception.InvalidParameterCombination(msg)
|
||||
else:
|
||||
# NOTE(ft): only one of this variables is not empty
|
||||
return security_group_names, network_interfaces
|
||||
|
||||
def format_network_interfaces(self, context, instances_network_interfaces):
|
||||
neutron = clients.neutron(context)
|
||||
|
||||
|
@ -1273,6 +1276,7 @@ def _cloud_format_ramdisk_id(context, instance_ref, image_ids=None):
|
|||
|
||||
|
||||
def _cloud_format_instance_type(context, os_instance):
|
||||
# TODO(ft): cache flavors
|
||||
return clients.nova(context).flavors.get(os_instance.flavor['id']).name
|
||||
|
||||
|
||||
|
|
|
@ -257,6 +257,12 @@ class IncorrectState(EC2Exception):
|
|||
"'%(reason)s'")
|
||||
|
||||
|
||||
class IncorrectInstanceState(IncorrectState):
|
||||
ec2_code = 'IncorrectInstanceState'
|
||||
msg_fmt = _("The instance '%(instance_id)s' is not in a state from which "
|
||||
"the requested operation can be performed.")
|
||||
|
||||
|
||||
class InvalidVpcRange(Invalid):
|
||||
ec2_code = 'InvalidVpc.Range'
|
||||
msg_fmt = _("The CIDR '%(cidr_block)s' is invalid.")
|
||||
|
|
|
@ -150,6 +150,8 @@ ID_EC2_RESERVATION_2 = random_ec2_id('r')
|
|||
|
||||
ROOT_DEVICE_NAME_INSTANCE_1 = '/dev/vda'
|
||||
ROOT_DEVICE_NAME_INSTANCE_2 = '/dev/sdb1'
|
||||
IPV6_INSTANCE_2 = 'fe80:b33f::a8bb:ccff:fedd:eeff'
|
||||
CLIENT_TOKEN_INSTANCE_2 = 'client-token-2'
|
||||
|
||||
# DHCP options constants
|
||||
ID_EC2_DHCP_OPTIONS_1 = random_ec2_id('dopt')
|
||||
|
@ -479,6 +481,7 @@ DB_INSTANCE_2 = {
|
|||
'vpc_id': None,
|
||||
'reservation_id': ID_EC2_RESERVATION_2,
|
||||
'launch_index': 0,
|
||||
'client_token': CLIENT_TOKEN_INSTANCE_2,
|
||||
}
|
||||
|
||||
NOVADB_INSTANCE_1 = {
|
||||
|
@ -592,6 +595,7 @@ EC2_INSTANCE_2 = {
|
|||
'amiLaunchIndex': 0,
|
||||
'placement': {'availabilityZone': NAME_AVAILABILITY_ZONE},
|
||||
'dnsName': None,
|
||||
'dnsNameV6': IPV6_INSTANCE_2,
|
||||
'instanceState': {'code': 0, 'name': 'pending'},
|
||||
'imageId': None,
|
||||
'productCodesSet': [],
|
||||
|
@ -607,6 +611,7 @@ EC2_INSTANCE_2 = {
|
|||
'attachTime': None}}],
|
||||
'instanceType': 'fake_flavor',
|
||||
'rootDeviceName': ROOT_DEVICE_NAME_INSTANCE_2,
|
||||
'clientToken': CLIENT_TOKEN_INSTANCE_2,
|
||||
}
|
||||
EC2_RESERVATION_1 = {
|
||||
'reservationId': ID_EC2_RESERVATION_1,
|
||||
|
@ -646,11 +651,25 @@ class OSInstance(object):
|
|||
setattr(self, 'OS-EXT-AZ:availability_zone', availability_zone)
|
||||
|
||||
def get(self):
|
||||
None
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
None
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
def reboot(self):
|
||||
pass
|
||||
|
||||
def get_password(self):
|
||||
return None
|
||||
|
||||
def get_console_output(self):
|
||||
return None
|
||||
|
||||
OS_INSTANCE_1 = OSInstance(
|
||||
ID_OS_INSTANCE_1, {'id': 'fakeFlavorId'},
|
||||
|
@ -674,7 +693,12 @@ OS_INSTANCE_2 = OSInstance(
|
|||
ID_OS_INSTANCE_2, {'id': 'fakeFlavorId'},
|
||||
security_groups=[{'name': NAME_DEFAULT_OS_SECURITY_GROUP},
|
||||
{'name': NAME_OTHER_OS_SECURITY_GROUP}],
|
||||
availability_zone=NAME_AVAILABILITY_ZONE)
|
||||
availability_zone=NAME_AVAILABILITY_ZONE,
|
||||
addresses={
|
||||
ID_EC2_SUBNET_1: [{'addr': IPV6_INSTANCE_2,
|
||||
'version': 6,
|
||||
'OS-EXT-IPS:type': 'fixed'}]},
|
||||
)
|
||||
|
||||
|
||||
# DHCP options objects
|
||||
|
|
|
@ -13,12 +13,14 @@
|
|||
# limitations under the License.
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
import itertools
|
||||
|
||||
import mock
|
||||
from oslotest import base as test_base
|
||||
|
||||
from ec2api.api import instance as instance_api
|
||||
from ec2api import exception
|
||||
from ec2api.tests import base
|
||||
from ec2api.tests import fakes
|
||||
from ec2api.tests import matchers
|
||||
|
@ -292,6 +294,152 @@ class InstanceTestCase(base.ApiTestCase):
|
|||
mock.call(mock.ANY, 'i', tools.purge_dict(db_instance, ['id']))
|
||||
for db_instance in self.DB_INSTANCES])
|
||||
|
||||
@mock.patch('ec2api.api.instance._format_reservation')
|
||||
@mock.patch('ec2api.api.instance.InstanceEngineNeutron.'
|
||||
'get_ec2_classic_os_network')
|
||||
def test_run_instances_other_parameters(self, get_ec2_classic_os_network,
|
||||
format_reservation):
|
||||
self.glance.images.get.return_value = fakes.OSImage(fakes.OS_IMAGE_1)
|
||||
get_ec2_classic_os_network.return_value = {'id': fakes.random_os_id()}
|
||||
format_reservation.return_value = {}
|
||||
|
||||
def do_check(engine, extra_kwargs={}, extra_db_instance={}):
|
||||
instance_api.instance_engine = engine
|
||||
|
||||
resp = self.execute('RunInstances',
|
||||
{'ImageId': fakes.ID_EC2_IMAGE_1,
|
||||
'InstanceType': 'fake_flavor',
|
||||
'MinCount': '1', 'MaxCount': '1',
|
||||
'SecurityGroup.1': 'Default',
|
||||
'Placement.AvailabilityZone': 'fake_zone',
|
||||
'ClientToken': 'fake_client_token'})
|
||||
self.assertEqual(200, resp['http_status_code'])
|
||||
|
||||
self.nova_servers.create.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, mock.ANY, min_count=1, max_count=1,
|
||||
userdata=None, block_device_mapping=None,
|
||||
kernel_id=None, ramdisk_id=None, key_name=None,
|
||||
availability_zone='fake_zone', security_groups=['Default'],
|
||||
**extra_kwargs)
|
||||
self.nova_servers.reset_mock()
|
||||
db_instance = {'os_id': mock.ANY,
|
||||
'reservation_id': mock.ANY,
|
||||
'launch_index': 0,
|
||||
'client_token': 'fake_client_token'}
|
||||
db_instance.update(extra_db_instance)
|
||||
self.db_api.add_item.assert_called_once_with(
|
||||
mock.ANY, 'i', db_instance)
|
||||
self.db_api.reset_mock()
|
||||
|
||||
do_check(
|
||||
instance_api.InstanceEngineNeutron(),
|
||||
extra_kwargs={
|
||||
'nics': [
|
||||
{'net-id': get_ec2_classic_os_network.return_value['id']}],
|
||||
},
|
||||
extra_db_instance={'vpc_id': None})
|
||||
do_check(instance_api.InstanceEngineNova())
|
||||
|
||||
@mock.patch('ec2api.api.instance._format_reservation')
|
||||
@mock.patch('ec2api.api.instance._get_os_instances_by_instances')
|
||||
def test_idempotent_run(self, get_os_instances_by_instances,
|
||||
format_reservation):
|
||||
instance_engine = mock.MagicMock()
|
||||
instance_api.instance_engine = instance_engine
|
||||
get_ec2_network_interfaces = instance_engine.get_ec2_network_interfaces
|
||||
|
||||
instances = [{'id': fakes.random_ec2_id('i'),
|
||||
'os_id': fakes.random_os_id(),
|
||||
'reservation_id': fakes.random_ec2_id('r'),
|
||||
'client_token': 'client-token-%s' % ind}
|
||||
for ind in range(3)]
|
||||
os_instances = [fakes.OSInstance(inst['os_id'])
|
||||
for inst in instances]
|
||||
format_reservation.return_value = {'key': 'value'}
|
||||
|
||||
# NOTE(ft): check select corresponding instance by client_token
|
||||
self.db_api.get_items.return_value = [instances[0], instances[1]]
|
||||
get_os_instances_by_instances.return_value = [os_instances[1]]
|
||||
self.novadb.instance_get_by_uuid.return_value = 'novadb_instance'
|
||||
get_ec2_network_interfaces.return_value = 'ec2_network_interfaces'
|
||||
|
||||
resp = self.execute('RunInstances',
|
||||
{'MinCount': '1', 'MaxCount': '1',
|
||||
'ImageId': fakes.ID_EC2_IMAGE_1,
|
||||
'InstanceType': 'fake_flavor',
|
||||
'ClientToken': 'client-token-1'})
|
||||
self.assertEqual({'http_status_code': 200,
|
||||
'key': 'value'},
|
||||
resp)
|
||||
format_reservation.assert_called_once_with(
|
||||
mock.ANY, instances[1]['reservation_id'],
|
||||
[(instances[1], os_instances[1], 'novadb_instance')],
|
||||
'ec2_network_interfaces')
|
||||
get_os_instances_by_instances.assert_called_once_with(
|
||||
mock.ANY, {instances[1]['os_id']: instances[1]})
|
||||
self.novadb.instance_get_by_uuid.assert_called_once_with(
|
||||
mock.ANY, os_instances[1].id)
|
||||
get_ec2_network_interfaces.assert_called_once_with(
|
||||
mock.ANY, [instances[1]['id']])
|
||||
|
||||
# NOTE(ft): check pass to general run_instances logic if no
|
||||
# corresponding client_token is found
|
||||
instance_engine.run_instances.return_value = {}
|
||||
resp = self.execute('RunInstances',
|
||||
{'MinCount': '1', 'MaxCount': '1',
|
||||
'ImageId': fakes.ID_EC2_IMAGE_1,
|
||||
'InstanceType': 'fake_flavor',
|
||||
'ClientToken': 'client-token-2'})
|
||||
self.assertTrue(instance_engine.run_instances.called)
|
||||
|
||||
# NOTE(ft): check pass to general run_instances logic if no more
|
||||
# corresponding OS instance exists
|
||||
instance_engine.reset_mock()
|
||||
get_os_instances_by_instances.return_value = []
|
||||
resp = self.execute('RunInstances',
|
||||
{'MinCount': '1', 'MaxCount': '1',
|
||||
'ImageId': fakes.ID_EC2_IMAGE_1,
|
||||
'InstanceType': 'fake_flavor',
|
||||
'ClientToken': 'client-token-1'})
|
||||
self.assertTrue(instance_engine.run_instances.called)
|
||||
|
||||
# NOTE(ft): check case for several instances with same client_token,
|
||||
# but one no more exists in OS
|
||||
format_reservation.reset_mock()
|
||||
get_os_instances_by_instances.reset_mock()
|
||||
instance_engine.reset_mock()
|
||||
self.novadb.reset_mock()
|
||||
for inst in instances:
|
||||
inst['reservation_id'] = instances[0]['reservation_id']
|
||||
inst['client_token'] = 'client-token'
|
||||
self.db_api.get_items.return_value = instances
|
||||
get_os_instances_by_instances.return_value = [os_instances[0],
|
||||
os_instances[2]]
|
||||
self.novadb.instance_get_by_uuid.side_effect = ['novadb-instance-0',
|
||||
'novadb-instance-2']
|
||||
get_ec2_network_interfaces.return_value = 'ec2_network_interfaces'
|
||||
|
||||
resp = self.execute('RunInstances',
|
||||
{'MinCount': '1', 'MaxCount': '1',
|
||||
'ImageId': fakes.ID_EC2_IMAGE_1,
|
||||
'InstanceType': 'fake_flavor',
|
||||
'ClientToken': 'client-token'})
|
||||
self.assertEqual({'http_status_code': 200,
|
||||
'key': 'value'},
|
||||
resp)
|
||||
format_reservation.assert_called_once_with(
|
||||
mock.ANY, instances[0]['reservation_id'],
|
||||
[(instances[0], os_instances[0], 'novadb-instance-0'),
|
||||
(instances[2], os_instances[2], 'novadb-instance-2')],
|
||||
'ec2_network_interfaces')
|
||||
get_os_instances_by_instances.assert_called_once_with(
|
||||
mock.ANY, dict((inst['os_id'], inst) for inst in instances))
|
||||
self.assertEqual([mock.call(mock.ANY, os_instances[0].id),
|
||||
mock.call(mock.ANY, os_instances[2].id)],
|
||||
self.novadb.instance_get_by_uuid.mock_calls)
|
||||
get_ec2_network_interfaces.assert_called_once_with(
|
||||
mock.ANY, [instances[0]['id'], instances[2]['id']])
|
||||
|
||||
def test_run_instances_rollback(self):
|
||||
instance_api.instance_engine = (
|
||||
instance_api.InstanceEngineNeutron())
|
||||
|
@ -353,6 +501,31 @@ class InstanceTestCase(base.ApiTestCase):
|
|||
fakes.ID_EC2_NETWORK_INTERFACE_1},
|
||||
new_port=False)
|
||||
|
||||
def test_run_instances_invalid_parameters(self):
|
||||
resp = self.execute('RunInstances',
|
||||
{'ImageId': fakes.ID_EC2_IMAGE_1,
|
||||
'MinCount': '0', 'MaxCount': '0'})
|
||||
self.assertEqual(400, resp['http_status_code'])
|
||||
self.assertEqual('InvalidParameterValue', resp['Error']['Code'])
|
||||
|
||||
resp = self.execute('RunInstances',
|
||||
{'ImageId': fakes.ID_EC2_IMAGE_1,
|
||||
'MinCount': '1', 'MaxCount': '0'})
|
||||
self.assertEqual(400, resp['http_status_code'])
|
||||
self.assertEqual('InvalidParameterValue', resp['Error']['Code'])
|
||||
|
||||
resp = self.execute('RunInstances',
|
||||
{'ImageId': fakes.ID_EC2_IMAGE_1,
|
||||
'MinCount': '0', 'MaxCount': '1'})
|
||||
self.assertEqual(400, resp['http_status_code'])
|
||||
self.assertEqual('InvalidParameterValue', resp['Error']['Code'])
|
||||
|
||||
resp = self.execute('RunInstances',
|
||||
{'ImageId': fakes.ID_EC2_IMAGE_1,
|
||||
'MinCount': '2', 'MaxCount': '1'})
|
||||
self.assertEqual(400, resp['http_status_code'])
|
||||
self.assertEqual('InvalidParameterValue', resp['Error']['Code'])
|
||||
|
||||
@mock.patch.object(fakes.OSInstance, 'delete', autospec=True)
|
||||
@mock.patch.object(fakes.OSInstance, 'get', autospec=True)
|
||||
def test_terminate_instances(self, os_instance_get, os_instance_delete):
|
||||
|
@ -479,6 +652,95 @@ class InstanceTestCase(base.ApiTestCase):
|
|||
detached_enis=[self.DB_ATTACHED_ENIS[1]],
|
||||
deleted_enis=[])
|
||||
|
||||
def test_terminate_instances_invalid_parameters(self):
|
||||
resp = self.execute('TerminateInstances',
|
||||
{'InstanceId.1': fakes.random_ec2_id('i')})
|
||||
self.assertEqual(400, resp['http_status_code'])
|
||||
self.assertEqual('InvalidInstanceID.NotFound', resp['Error']['Code'])
|
||||
|
||||
@mock.patch('ec2api.api.instance._get_os_instances_by_instances')
|
||||
def _test_instances_operation(self, operation, os_instance_operation,
|
||||
valid_state, invalid_state,
|
||||
get_os_instances_by_instances):
|
||||
os_instance_1 = copy.deepcopy(fakes.OS_INSTANCE_1)
|
||||
os_instance_2 = copy.deepcopy(fakes.OS_INSTANCE_2)
|
||||
for inst in (os_instance_1, os_instance_2):
|
||||
setattr(inst, 'OS-EXT-STS:vm_state', valid_state)
|
||||
|
||||
self.db_api.get_items_by_ids.return_value = [fakes.DB_INSTANCE_1,
|
||||
fakes.DB_INSTANCE_2]
|
||||
get_os_instances_by_instances.return_value = [os_instance_1,
|
||||
os_instance_2]
|
||||
|
||||
resp = self.execute(operation,
|
||||
{'InstanceId.1': fakes.ID_EC2_INSTANCE_1,
|
||||
'InstanceId.2': fakes.ID_EC2_INSTANCE_2})
|
||||
self.assertEqual({'http_status_code': 200,
|
||||
'return': True},
|
||||
resp)
|
||||
self.assertEqual([mock.call(os_instance_1), mock.call(os_instance_2)],
|
||||
os_instance_operation.mock_calls)
|
||||
self.db_api.get_items_by_ids.assert_called_once_with(
|
||||
mock.ANY, 'i', set([fakes.ID_EC2_INSTANCE_1,
|
||||
fakes.ID_EC2_INSTANCE_2]))
|
||||
get_os_instances_by_instances.assert_called_once_with(
|
||||
mock.ANY, [fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2], exactly=True)
|
||||
|
||||
setattr(os_instance_2, 'OS-EXT-STS:vm_state', invalid_state)
|
||||
os_instance_operation.reset_mock()
|
||||
resp = self.execute('StartInstances',
|
||||
{'InstanceId.1': fakes.ID_EC2_INSTANCE_1,
|
||||
'InstanceId.2': fakes.ID_EC2_INSTANCE_2})
|
||||
self.assertEqual(400, resp['http_status_code'])
|
||||
self.assertEqual('IncorrectInstanceState', resp['Error']['Code'])
|
||||
self.assertEqual(0, os_instance_operation.call_count)
|
||||
|
||||
@mock.patch.object(fakes.OSInstance, 'start', autospec=True)
|
||||
def test_start_instances(self, os_instance_start):
|
||||
self._test_instances_operation('StartInstances', os_instance_start,
|
||||
instance_api.vm_states_STOPPED,
|
||||
instance_api.vm_states_ACTIVE)
|
||||
|
||||
@mock.patch.object(fakes.OSInstance, 'stop', autospec=True)
|
||||
def test_stop_instances(self, os_instance_stop):
|
||||
self._test_instances_operation('StopInstances', os_instance_stop,
|
||||
instance_api.vm_states_ACTIVE,
|
||||
instance_api.vm_states_STOPPED)
|
||||
|
||||
@mock.patch.object(fakes.OSInstance, 'reboot', autospec=True)
|
||||
def test_reboot_instances(self, os_instance_reboot):
|
||||
self._test_instances_operation('RebootInstances', os_instance_reboot,
|
||||
instance_api.vm_states_ACTIVE,
|
||||
instance_api.vm_states_BUILDING)
|
||||
|
||||
@mock.patch('ec2api.openstack.common.timeutils.utcnow')
|
||||
def _test_instance_get_operation(self, operation, getter, key, utcnow):
|
||||
self.db_api.get_item_by_id.return_value = fakes.DB_INSTANCE_2
|
||||
self.nova_servers.get.return_value = fakes.OS_INSTANCE_2
|
||||
getter.return_value = 'fake_data'
|
||||
utcnow.return_value = datetime.datetime(2015, 1, 19, 23, 34, 45, 123)
|
||||
resp = self.execute(operation,
|
||||
{'InstanceId': fakes.ID_EC2_INSTANCE_2})
|
||||
self.assertEqual({'http_status_code': 200,
|
||||
'instanceId': fakes.ID_EC2_INSTANCE_2,
|
||||
'timestamp': '2015-01-19T23:34:45.000Z',
|
||||
key: 'fake_data'},
|
||||
resp)
|
||||
self.db_api.get_item_by_id.assert_called_once_with(
|
||||
mock.ANY, 'i', fakes.ID_EC2_INSTANCE_2)
|
||||
self.nova_servers.get.assert_called_once_with(fakes.ID_OS_INSTANCE_2)
|
||||
getter.assert_called_once_with(fakes.OS_INSTANCE_2)
|
||||
|
||||
@mock.patch.object(fakes.OSInstance, 'get_password', autospec=True)
|
||||
def test_get_password_data(self, get_password):
|
||||
self._test_instance_get_operation('GetPasswordData',
|
||||
get_password, 'passwordData')
|
||||
|
||||
@mock.patch.object(fakes.OSInstance, 'get_console_output', autospec=True)
|
||||
def test_console_output(self, get_console_output):
|
||||
self._test_instance_get_operation('GetConsoleOutput',
|
||||
get_console_output, 'output')
|
||||
|
||||
def test_describe_instances(self):
|
||||
"""Describe 2 instances, one of which is vpc instance."""
|
||||
instance_api.instance_engine = (
|
||||
|
@ -521,6 +783,16 @@ class InstanceTestCase(base.ApiTestCase):
|
|||
fakes.EC2_RESERVATION_2]},
|
||||
orderless_lists=True))
|
||||
|
||||
resp = self.execute('DescribeInstances',
|
||||
{'Filter.1.Name': 'key-name',
|
||||
'Filter.1.Value.1': 'a',
|
||||
'Filter.1.Value.2': 'b',
|
||||
'Filter.2.Name': 'client-token',
|
||||
'Filter.2.Value.1': 'a string'})
|
||||
self.assertEqual({'http_status_code': 200,
|
||||
'reservationSet': []},
|
||||
resp)
|
||||
|
||||
self.db_api.get_items_by_ids.return_value = [fakes.DB_INSTANCE_2]
|
||||
resp = self.execute('DescribeInstances', {'InstanceId.1':
|
||||
fakes.ID_EC2_INSTANCE_2})
|
||||
|
@ -633,6 +905,44 @@ class InstanceTestCase(base.ApiTestCase):
|
|||
ec2_enis_by_instance=[[self.EC2_ATTACHED_ENIS[1]], []],
|
||||
ec2_instance_ips=[fakes.IP_FIRST_SUBNET_2, fakes.IP_LAST_SUBNET_2])
|
||||
|
||||
@mock.patch('ec2api.api.instance._remove_instances')
|
||||
def test_describe_instances_auto_remove(self, remove_instances):
|
||||
instance_api.instance_engine = (
|
||||
instance_api.InstanceEngineNova())
|
||||
self.db_api.get_items.side_effect = (
|
||||
fakes.get_db_api_get_items(
|
||||
{'i': [fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2],
|
||||
'ami': [],
|
||||
'vol': [fakes.DB_VOLUME_2]}))
|
||||
self.nova_servers.list.return_value = [fakes.OS_INSTANCE_2]
|
||||
self.novadb.instance_get_by_uuid.return_value = (
|
||||
fakes.NOVADB_INSTANCE_2)
|
||||
self.novadb.block_device_mapping_get_all_by_instance.return_value = (
|
||||
fakes.NOVADB_BDM_INSTANCE_2)
|
||||
|
||||
resp = self.execute('DescribeInstances', {})
|
||||
|
||||
self.assertThat(resp,
|
||||
matchers.DictMatches(
|
||||
{'http_status_code': 200,
|
||||
'reservationSet': [fakes.EC2_RESERVATION_2]},
|
||||
orderless_lists=True))
|
||||
remove_instances.assert_called_once_with(
|
||||
mock.ANY, [fakes.DB_INSTANCE_1])
|
||||
|
||||
def test_describe_instances_invalid_parameters(self):
|
||||
resp = self.execute('DescribeInstances', {'InstanceId.1':
|
||||
fakes.random_ec2_id('i')})
|
||||
self.assertEqual(400, resp['http_status_code'])
|
||||
self.assertEqual('InvalidInstanceID.NotFound', resp['Error']['Code'])
|
||||
|
||||
self.db_api.get_items_by_ids.return_value = [fakes.DB_INSTANCE_2]
|
||||
resp = self.execute('DescribeInstances',
|
||||
{'InstanceId.1': fakes.ID_EC2_INSTANCE_2,
|
||||
'InstanceId.2': fakes.random_ec2_id('i')})
|
||||
self.assertEqual(400, resp['http_status_code'])
|
||||
self.assertEqual('InvalidInstanceID.NotFound', resp['Error']['Code'])
|
||||
|
||||
def _build_multiple_data_model(self):
|
||||
# NOTE(ft): generate necessary fake data
|
||||
# We need 4 detached ports in 2 subnets.
|
||||
|
@ -744,26 +1054,82 @@ class InstanceTestCase(base.ApiTestCase):
|
|||
fakes.random_os_id(), ec2_fake_eni,
|
||||
fakes.ID_OS_SUBNET_2, ['fake_ip'])
|
||||
|
||||
def _assert_list_ports_is_called_with_filter(self, instance_ids):
|
||||
# NOTE(ft): compare manually due to the order of instance ids in
|
||||
# list_ports call depends of values of instance EC2 ids
|
||||
# But neither assert_any_called nor matchers.DictMatches can not
|
||||
# compare lists excluding the order of elements
|
||||
list_ports_calls = self.neutron.list_ports.mock_calls
|
||||
self.assertEqual(1, len(list_ports_calls))
|
||||
self.assertEqual((), list_ports_calls[0][1])
|
||||
list_ports_kwargs = list_ports_calls[0][2]
|
||||
self.assertEqual(len(list_ports_kwargs), 1)
|
||||
self.assertIn('device_id', list_ports_kwargs)
|
||||
self.assertEqual(sorted(instance_ids),
|
||||
sorted(list_ports_kwargs['device_id']))
|
||||
|
||||
|
||||
# TODO(ft): add tests for get_vpc_default_security_group_id
|
||||
# and format_network_interfaces
|
||||
# TODO(ft): add tests for get_vpc_default_security_group_id,
|
||||
# format_network_interfaces, get_os_instances_by_instances, remove_instances,
|
||||
# format_reservation
|
||||
|
||||
class InstancePrivateTestCase(test_base.BaseTestCase):
|
||||
|
||||
@mock.patch('glanceclient.client.Client')
|
||||
@mock.patch('ec2api.db.api.IMPL')
|
||||
def test_parse_image_parameters(self, db_api, glance):
|
||||
fake_context = mock.Mock(service_catalog=[{'type': 'fake'}])
|
||||
image_id = fakes.random_ec2_id('ami')
|
||||
os_image_id = fakes.random_os_id()
|
||||
db_api.get_public_items.return_value = [{'id': image_id,
|
||||
'os_id': os_image_id}]
|
||||
os_image = fakes.OSImage({
|
||||
'id': fakes.random_os_id(),
|
||||
'owner': fakes.ID_OS_PROJECT,
|
||||
'is_public': True,
|
||||
'status': None,
|
||||
'container_format': 'ami',
|
||||
'name': 'fake_name',
|
||||
'properties': {}})
|
||||
glance.return_value.images.get.return_value = os_image
|
||||
|
||||
self.assertRaises(exception.ImageNotActive,
|
||||
instance_api._parse_image_parameters,
|
||||
fake_context, image_id, None, None)
|
||||
|
||||
os_image.status = 'active'
|
||||
os_image.properties['image_state'] = 'decrypting'
|
||||
|
||||
self.assertRaises(exception.ImageNotActive,
|
||||
instance_api._parse_image_parameters,
|
||||
fake_context, image_id, None, None)
|
||||
|
||||
@mock.patch('ec2api.api.instance.novadb')
|
||||
@mock.patch('novaclient.v1_1.client.Client')
|
||||
@mock.patch('ec2api.db.api.IMPL')
|
||||
def test_format_instance(self, db_api, nova, novadb):
|
||||
fake_context = mock.Mock(service_catalog=[{'type': 'fake'}])
|
||||
fake_flavor = mock.Mock()
|
||||
fake_flavor.configure_mock(name='fake_flavor')
|
||||
nova.return_value.flavors.get.return_value = fake_flavor
|
||||
|
||||
instance = {'id': fakes.random_ec2_id('i'),
|
||||
'os_id': fakes.random_os_id(),
|
||||
'launch_index': 0}
|
||||
os_instance = fakes.OSInstance(instance['os_id'],
|
||||
flavor={'id': 'fakeFlavorId'})
|
||||
novadb_instance = {'kernel_id': None,
|
||||
'ramdisk_id': None,
|
||||
'hostname': instance['id']}
|
||||
|
||||
setattr(os_instance, 'OS-EXT-STS:vm_state',
|
||||
instance_api.vm_states_ACTIVE)
|
||||
formatted_instance = instance_api._format_instance(
|
||||
fake_context, instance, os_instance, novadb_instance, [], {})
|
||||
self.assertEqual({'name': instance_api.inst_state_RUNNING,
|
||||
'code': instance_api.inst_state_RUNNING_CODE},
|
||||
formatted_instance['instanceState'])
|
||||
|
||||
setattr(os_instance, 'OS-EXT-STS:vm_state',
|
||||
instance_api.vm_states_STOPPED)
|
||||
formatted_instance = instance_api._format_instance(
|
||||
fake_context, instance, os_instance, novadb_instance, [], {})
|
||||
self.assertEqual({'name': instance_api.inst_state_STOPPED,
|
||||
'code': instance_api.inst_state_STOPPED_CODE},
|
||||
formatted_instance['instanceState'])
|
||||
|
||||
os_instance.image = {'id': fakes.random_os_id()}
|
||||
formatted_instance = instance_api._format_instance(
|
||||
fake_context, instance, os_instance, novadb_instance, [], {})
|
||||
db_api.add_item_id.assert_called_once_with(
|
||||
mock.ANY, 'ami', os_instance.image['id'])
|
||||
|
||||
@mock.patch('cinderclient.v1.client.Client')
|
||||
@mock.patch('ec2api.api.instance.novadb')
|
||||
def test_format_instance_bdm(self, novadb, cinder):
|
||||
|
@ -927,3 +1293,16 @@ class InstancePrivateTestCase(test_base.BaseTestCase):
|
|||
'deleteOnTermination': False,
|
||||
'volumeId': 'vol-00000002',
|
||||
'attachTime': '', }}]}))
|
||||
|
||||
def test_block_device_strip_dev(self):
|
||||
self.assertEqual(
|
||||
instance_api._block_device_strip_dev('/dev/sda'), 'sda')
|
||||
self.assertEqual(instance_api._block_device_strip_dev('sda'), 'sda')
|
||||
|
||||
def test_block_device_prepend_dev(self):
|
||||
mapping = ['/dev/sda', 'sdb', 'sdc', 'sdd', 'sde']
|
||||
expected = ['/dev/sda', '/dev/sdb', '/dev/sdc', '/dev/sdd', '/dev/sde']
|
||||
|
||||
for m, e in zip(mapping, expected):
|
||||
prepended = instance_api._block_device_prepend_dev(m)
|
||||
self.assertEqual(e, prepended)
|
||||
|
|
Loading…
Reference in New Issue