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):
|
def reboot_instances(context, instance_id):
|
||||||
return _foreach_instance(context, instance_id,
|
return _foreach_instance(context, instance_id,
|
||||||
|
(vm_states_ALLOW_SOFT_REBOOT +
|
||||||
|
vm_states_ALLOW_HARD_REBOOT),
|
||||||
lambda instance: instance.reboot())
|
lambda instance: instance.reboot())
|
||||||
|
|
||||||
|
|
||||||
def stop_instances(context, instance_id, force=False):
|
def stop_instances(context, instance_id, force=False):
|
||||||
return _foreach_instance(context, instance_id,
|
return _foreach_instance(context, instance_id,
|
||||||
|
[vm_states_ACTIVE, vm_states_RESCUED,
|
||||||
|
vm_states_ERROR],
|
||||||
lambda instance: instance.stop())
|
lambda instance: instance.stop())
|
||||||
|
|
||||||
|
|
||||||
def start_instances(context, instance_id):
|
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())
|
lambda instance: instance.start())
|
||||||
|
|
||||||
|
|
||||||
|
@ -419,14 +423,10 @@ def _get_idempotent_run(context, client_token):
|
||||||
instances_info = []
|
instances_info = []
|
||||||
instance_ids = []
|
instance_ids = []
|
||||||
for os_instance in os_instances:
|
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)
|
novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id)
|
||||||
instances_info.append((instance, os_instance, novadb_instance,))
|
instances_info.append((instance, os_instance, novadb_instance,))
|
||||||
instance_ids.append(instance['id'])
|
instance_ids.append(instance['id'])
|
||||||
|
|
||||||
# NOTE(ft): delete obsolete instances
|
|
||||||
if instances:
|
|
||||||
_remove_instances(context, instances.itervalues())
|
|
||||||
if not instances_info:
|
if not instances_info:
|
||||||
return
|
return
|
||||||
ec2_network_interfaces = (
|
ec2_network_interfaces = (
|
||||||
|
@ -687,10 +687,15 @@ def _get_ip_info_for_instance(os_instance):
|
||||||
return fixed_ip, fixed_ip6, floating_ip
|
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)
|
instances = ec2utils.get_db_items(context, 'i', instance_ids)
|
||||||
os_instances = _get_os_instances_by_instances(context, instances,
|
os_instances = _get_os_instances_by_instances(context, instances,
|
||||||
exactly=True)
|
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:
|
for os_instance in os_instances:
|
||||||
func(os_instance)
|
func(os_instance)
|
||||||
return True
|
return True
|
||||||
|
@ -699,15 +704,17 @@ def _foreach_instance(context, instance_ids, func):
|
||||||
def _get_os_instances_by_instances(context, instances, exactly=False):
|
def _get_os_instances_by_instances(context, instances, exactly=False):
|
||||||
nova = clients.nova(context)
|
nova = clients.nova(context)
|
||||||
os_instances = []
|
os_instances = []
|
||||||
found_obsolete_instance = False
|
obsolete_instances = []
|
||||||
for instance in instances:
|
for instance in instances:
|
||||||
try:
|
try:
|
||||||
os_instances.append(nova.servers.get(instance['os_id']))
|
os_instances.append(nova.servers.get(instance['os_id']))
|
||||||
except nova_exception.NotFound:
|
except nova_exception.NotFound:
|
||||||
db_api.delete_item(context, instance['id'])
|
db_api.delete_item(context, instance['id'])
|
||||||
found_obsolete_instance = True
|
obsolete_instances.append(instance)
|
||||||
if found_obsolete_instance and exactly:
|
if obsolete_instances:
|
||||||
raise exception.InvalidInstanceIDNotFound(id=instance['id'])
|
_remove_instances(context, obsolete_instances)
|
||||||
|
if exactly:
|
||||||
|
raise exception.InvalidInstanceIDNotFound(id=instance['id'])
|
||||||
|
|
||||||
return os_instances
|
return os_instances
|
||||||
|
|
||||||
|
@ -747,10 +754,6 @@ class InstanceEngineNeutron(object):
|
||||||
private_ip_address=None, client_token=None,
|
private_ip_address=None, client_token=None,
|
||||||
network_interface=None, iam_instance_profile=None,
|
network_interface=None, iam_instance_profile=None,
|
||||||
ebs_optimized=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(
|
os_image, os_kernel_id, os_ramdisk_id = _parse_image_parameters(
|
||||||
context, image_id, kernel_id, ramdisk_id)
|
context, image_id, kernel_id, ramdisk_id)
|
||||||
|
|
||||||
|
@ -906,6 +909,60 @@ class InstanceEngineNeutron(object):
|
||||||
eni['attachment']['instanceId']].append(eni)
|
eni['attachment']['instanceId']].append(eni)
|
||||||
return ec2_network_interfaces
|
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,
|
def check_network_interface_parameters(self, params,
|
||||||
min_instance_count,
|
min_instance_count,
|
||||||
max_instance_count):
|
max_instance_count):
|
||||||
|
@ -1052,60 +1109,6 @@ class InstanceEngineNeutron(object):
|
||||||
'for EC2 Classic mode'))
|
'for EC2 Classic mode'))
|
||||||
return ec2_classic_os_networks[0]
|
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):
|
def format_network_interfaces(self, context, instances_network_interfaces):
|
||||||
neutron = clients.neutron(context)
|
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):
|
def _cloud_format_instance_type(context, os_instance):
|
||||||
|
# TODO(ft): cache flavors
|
||||||
return clients.nova(context).flavors.get(os_instance.flavor['id']).name
|
return clients.nova(context).flavors.get(os_instance.flavor['id']).name
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -257,6 +257,12 @@ class IncorrectState(EC2Exception):
|
||||||
"'%(reason)s'")
|
"'%(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):
|
class InvalidVpcRange(Invalid):
|
||||||
ec2_code = 'InvalidVpc.Range'
|
ec2_code = 'InvalidVpc.Range'
|
||||||
msg_fmt = _("The CIDR '%(cidr_block)s' is invalid.")
|
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_1 = '/dev/vda'
|
||||||
ROOT_DEVICE_NAME_INSTANCE_2 = '/dev/sdb1'
|
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
|
# DHCP options constants
|
||||||
ID_EC2_DHCP_OPTIONS_1 = random_ec2_id('dopt')
|
ID_EC2_DHCP_OPTIONS_1 = random_ec2_id('dopt')
|
||||||
|
@ -479,6 +481,7 @@ DB_INSTANCE_2 = {
|
||||||
'vpc_id': None,
|
'vpc_id': None,
|
||||||
'reservation_id': ID_EC2_RESERVATION_2,
|
'reservation_id': ID_EC2_RESERVATION_2,
|
||||||
'launch_index': 0,
|
'launch_index': 0,
|
||||||
|
'client_token': CLIENT_TOKEN_INSTANCE_2,
|
||||||
}
|
}
|
||||||
|
|
||||||
NOVADB_INSTANCE_1 = {
|
NOVADB_INSTANCE_1 = {
|
||||||
|
@ -592,6 +595,7 @@ EC2_INSTANCE_2 = {
|
||||||
'amiLaunchIndex': 0,
|
'amiLaunchIndex': 0,
|
||||||
'placement': {'availabilityZone': NAME_AVAILABILITY_ZONE},
|
'placement': {'availabilityZone': NAME_AVAILABILITY_ZONE},
|
||||||
'dnsName': None,
|
'dnsName': None,
|
||||||
|
'dnsNameV6': IPV6_INSTANCE_2,
|
||||||
'instanceState': {'code': 0, 'name': 'pending'},
|
'instanceState': {'code': 0, 'name': 'pending'},
|
||||||
'imageId': None,
|
'imageId': None,
|
||||||
'productCodesSet': [],
|
'productCodesSet': [],
|
||||||
|
@ -607,6 +611,7 @@ EC2_INSTANCE_2 = {
|
||||||
'attachTime': None}}],
|
'attachTime': None}}],
|
||||||
'instanceType': 'fake_flavor',
|
'instanceType': 'fake_flavor',
|
||||||
'rootDeviceName': ROOT_DEVICE_NAME_INSTANCE_2,
|
'rootDeviceName': ROOT_DEVICE_NAME_INSTANCE_2,
|
||||||
|
'clientToken': CLIENT_TOKEN_INSTANCE_2,
|
||||||
}
|
}
|
||||||
EC2_RESERVATION_1 = {
|
EC2_RESERVATION_1 = {
|
||||||
'reservationId': ID_EC2_RESERVATION_1,
|
'reservationId': ID_EC2_RESERVATION_1,
|
||||||
|
@ -646,11 +651,25 @@ class OSInstance(object):
|
||||||
setattr(self, 'OS-EXT-AZ:availability_zone', availability_zone)
|
setattr(self, 'OS-EXT-AZ:availability_zone', availability_zone)
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
None
|
pass
|
||||||
|
|
||||||
def delete(self):
|
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(
|
OS_INSTANCE_1 = OSInstance(
|
||||||
ID_OS_INSTANCE_1, {'id': 'fakeFlavorId'},
|
ID_OS_INSTANCE_1, {'id': 'fakeFlavorId'},
|
||||||
|
@ -674,7 +693,12 @@ OS_INSTANCE_2 = OSInstance(
|
||||||
ID_OS_INSTANCE_2, {'id': 'fakeFlavorId'},
|
ID_OS_INSTANCE_2, {'id': 'fakeFlavorId'},
|
||||||
security_groups=[{'name': NAME_DEFAULT_OS_SECURITY_GROUP},
|
security_groups=[{'name': NAME_DEFAULT_OS_SECURITY_GROUP},
|
||||||
{'name': NAME_OTHER_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
|
# DHCP options objects
|
||||||
|
|
|
@ -13,12 +13,14 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
from oslotest import base as test_base
|
from oslotest import base as test_base
|
||||||
|
|
||||||
from ec2api.api import instance as instance_api
|
from ec2api.api import instance as instance_api
|
||||||
|
from ec2api import exception
|
||||||
from ec2api.tests import base
|
from ec2api.tests import base
|
||||||
from ec2api.tests import fakes
|
from ec2api.tests import fakes
|
||||||
from ec2api.tests import matchers
|
from ec2api.tests import matchers
|
||||||
|
@ -292,6 +294,152 @@ class InstanceTestCase(base.ApiTestCase):
|
||||||
mock.call(mock.ANY, 'i', tools.purge_dict(db_instance, ['id']))
|
mock.call(mock.ANY, 'i', tools.purge_dict(db_instance, ['id']))
|
||||||
for db_instance in self.DB_INSTANCES])
|
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):
|
def test_run_instances_rollback(self):
|
||||||
instance_api.instance_engine = (
|
instance_api.instance_engine = (
|
||||||
instance_api.InstanceEngineNeutron())
|
instance_api.InstanceEngineNeutron())
|
||||||
|
@ -353,6 +501,31 @@ class InstanceTestCase(base.ApiTestCase):
|
||||||
fakes.ID_EC2_NETWORK_INTERFACE_1},
|
fakes.ID_EC2_NETWORK_INTERFACE_1},
|
||||||
new_port=False)
|
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, 'delete', autospec=True)
|
||||||
@mock.patch.object(fakes.OSInstance, 'get', autospec=True)
|
@mock.patch.object(fakes.OSInstance, 'get', autospec=True)
|
||||||
def test_terminate_instances(self, os_instance_get, os_instance_delete):
|
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]],
|
detached_enis=[self.DB_ATTACHED_ENIS[1]],
|
||||||
deleted_enis=[])
|
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):
|
def test_describe_instances(self):
|
||||||
"""Describe 2 instances, one of which is vpc instance."""
|
"""Describe 2 instances, one of which is vpc instance."""
|
||||||
instance_api.instance_engine = (
|
instance_api.instance_engine = (
|
||||||
|
@ -521,6 +783,16 @@ class InstanceTestCase(base.ApiTestCase):
|
||||||
fakes.EC2_RESERVATION_2]},
|
fakes.EC2_RESERVATION_2]},
|
||||||
orderless_lists=True))
|
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]
|
self.db_api.get_items_by_ids.return_value = [fakes.DB_INSTANCE_2]
|
||||||
resp = self.execute('DescribeInstances', {'InstanceId.1':
|
resp = self.execute('DescribeInstances', {'InstanceId.1':
|
||||||
fakes.ID_EC2_INSTANCE_2})
|
fakes.ID_EC2_INSTANCE_2})
|
||||||
|
@ -633,6 +905,44 @@ class InstanceTestCase(base.ApiTestCase):
|
||||||
ec2_enis_by_instance=[[self.EC2_ATTACHED_ENIS[1]], []],
|
ec2_enis_by_instance=[[self.EC2_ATTACHED_ENIS[1]], []],
|
||||||
ec2_instance_ips=[fakes.IP_FIRST_SUBNET_2, fakes.IP_LAST_SUBNET_2])
|
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):
|
def _build_multiple_data_model(self):
|
||||||
# NOTE(ft): generate necessary fake data
|
# NOTE(ft): generate necessary fake data
|
||||||
# We need 4 detached ports in 2 subnets.
|
# We need 4 detached ports in 2 subnets.
|
||||||
|
@ -744,26 +1054,82 @@ class InstanceTestCase(base.ApiTestCase):
|
||||||
fakes.random_os_id(), ec2_fake_eni,
|
fakes.random_os_id(), ec2_fake_eni,
|
||||||
fakes.ID_OS_SUBNET_2, ['fake_ip'])
|
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,
|
||||||
# TODO(ft): add tests for get_vpc_default_security_group_id
|
# format_network_interfaces, get_os_instances_by_instances, remove_instances,
|
||||||
# and format_network_interfaces
|
# format_reservation
|
||||||
|
|
||||||
class InstancePrivateTestCase(test_base.BaseTestCase):
|
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('cinderclient.v1.client.Client')
|
||||||
@mock.patch('ec2api.api.instance.novadb')
|
@mock.patch('ec2api.api.instance.novadb')
|
||||||
def test_format_instance_bdm(self, novadb, cinder):
|
def test_format_instance_bdm(self, novadb, cinder):
|
||||||
|
@ -927,3 +1293,16 @@ class InstancePrivateTestCase(test_base.BaseTestCase):
|
||||||
'deleteOnTermination': False,
|
'deleteOnTermination': False,
|
||||||
'volumeId': 'vol-00000002',
|
'volumeId': 'vol-00000002',
|
||||||
'attachTime': '', }}]}))
|
'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