Implement describe_instances, store reservation_id
Change-Id: I64c8e62eed6b769953ad0452da676259e9f8900d
This commit is contained in:
@@ -180,11 +180,26 @@ def id_to_glance_id(context, image_id):
|
||||
return novadb.s3_image_get(context, image_id)['uuid']
|
||||
|
||||
|
||||
def glance_id_to_id(context, glance_id):
|
||||
"""Convert a glance id to an internal (db) id."""
|
||||
if glance_id is None:
|
||||
return
|
||||
try:
|
||||
return novadb.s3_image_get_by_uuid(context, glance_id)['id']
|
||||
except exception.NotFound:
|
||||
return novadb.s3_image_create(context, glance_id)['id']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def glance_id_to_ec2_id(context, glance_id, image_type='ami'):
|
||||
image_id = glance_id_to_id(context, glance_id)
|
||||
return image_ec2_id(image_id, image_type=image_type)
|
||||
|
||||
|
||||
# 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):
|
||||
@@ -195,6 +210,12 @@ def ec2_id_to_id(ec2_id):
|
||||
raise exception.InvalidEc2Id(ec2_id=ec2_id)
|
||||
|
||||
|
||||
def image_ec2_id(image_id, image_type='ami'):
|
||||
"""Returns image ec2_id using id and three letter type."""
|
||||
template = image_type + '-%08x'
|
||||
return id_to_ec2_id(image_id, template=template)
|
||||
|
||||
|
||||
def id_to_ec2_id(instance_id, template='i-%08x'):
|
||||
"""Convert an instance ID (int) to an ec2 ID (i-[base 16 number])."""
|
||||
return template % int(instance_id)
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import itertools
|
||||
import random
|
||||
import re
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
from ec2api.api import clients
|
||||
from ec2api.api import ec2client
|
||||
from ec2api.api import ec2utils
|
||||
@@ -26,10 +29,21 @@ from ec2api.api import security_group as security_group_api
|
||||
from ec2api.api import utils
|
||||
from ec2api.db import api as db_api
|
||||
from ec2api import exception
|
||||
from ec2api import novadb
|
||||
from ec2api.openstack.common.gettextutils import _
|
||||
from ec2api.openstack.common import timeutils
|
||||
|
||||
|
||||
ec2_opts = [
|
||||
cfg.BoolOpt('ec2_private_dns_show_ip',
|
||||
default=False,
|
||||
help='Return the IP address as private dns hostname in '
|
||||
'describe instances'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(ec2_opts)
|
||||
|
||||
"""Instance related API implementation
|
||||
"""
|
||||
|
||||
@@ -76,7 +90,8 @@ def run_instances(context, image_id, min_count, max_count,
|
||||
vpc_network_parameters, min_count, min_count)
|
||||
|
||||
neutron = clients.neutron(context)
|
||||
(network_interfaces,
|
||||
(vpc_id,
|
||||
network_interfaces,
|
||||
create_network_interfaces_args,
|
||||
delete_on_termination_flags) = _parse_network_interface_parameters(
|
||||
context, neutron, vpc_network_parameters)
|
||||
@@ -84,11 +99,13 @@ def run_instances(context, image_id, min_count, max_count,
|
||||
# NOTE(ft): workaround for Launchpad Bug #1384347 in Icehouse
|
||||
if not security_groups_names and vpc_network_parameters:
|
||||
security_groups_names = _get_vpc_default_security_group_id(
|
||||
context, network_interfaces, create_network_interfaces_args)
|
||||
context, vpc_id)
|
||||
|
||||
security_groups = security_group_api._format_security_groups_ids_names(
|
||||
context)
|
||||
instances_infos = []
|
||||
ec2_reservation_id = _generate_reservation_id()
|
||||
|
||||
# TODO(ft): Process min and max counts on running errors accordingly to
|
||||
# their meanings. Correct error messages are also critical
|
||||
with utils.OnCrashCleaner() as cleaner:
|
||||
# NOTE(ft): create Neutron's ports manually to have a chance to:
|
||||
# process individual network interface options like security_group
|
||||
@@ -122,11 +139,9 @@ def run_instances(context, image_id, min_count, max_count,
|
||||
context, cleaner, create_network_interfaces_args)
|
||||
instance_network_interfaces.append(network_interfaces)
|
||||
|
||||
ec2 = ec2client.ec2client(context)
|
||||
# NOTE(ft): run instances one by one using created ports
|
||||
network_interfaces_by_instances = {}
|
||||
ec2_instance_ids = []
|
||||
for network_interfaces in instance_network_interfaces:
|
||||
for (launch_index,
|
||||
network_interfaces) in enumerate(instance_network_interfaces):
|
||||
nics = [{'port-id': eni['os_id']} for eni in network_interfaces]
|
||||
os_instance = nova.servers.create(
|
||||
'EC2 server', os_image.id, os_flavor,
|
||||
@@ -139,53 +154,37 @@ def run_instances(context, image_id, min_count, max_count,
|
||||
key_name=key_name, userdata=user_data)
|
||||
|
||||
cleaner.addCleanup(nova.servers.delete, os_instance.id)
|
||||
instance = db_api.add_item(context, 'i', {'os_id': os_instance.id})
|
||||
instance = db_api.add_item(context, 'i',
|
||||
{'os_id': os_instance.id,
|
||||
'vpc_id': vpc_id,
|
||||
'reservation_id': ec2_reservation_id,
|
||||
'launch_index': launch_index})
|
||||
cleaner.addCleanup(db_api.delete_item, context, instance['id'])
|
||||
nova.servers.update(os_instance, name=instance['id'])
|
||||
|
||||
network_interfaces_by_instances[instance['id']] = (
|
||||
network_interfaces)
|
||||
ec2_instance_ids.append(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
|
||||
for ec2_instance in ec2_instances:
|
||||
instance_ports_info = []
|
||||
delete_on_termination = iter(delete_on_termination_flags)
|
||||
for network_interface in network_interfaces_by_instances[
|
||||
ec2_instance['instanceId']]:
|
||||
for network_interface in network_interfaces:
|
||||
# TODO(ft): implement update items in DB layer to prevent
|
||||
# record by record modification
|
||||
# Alternatively a create_network_interface sub-function can
|
||||
# set attach_time at once
|
||||
network_interface.update({
|
||||
'instance_id': ec2_instance['instanceId'],
|
||||
'attach_time': attach_time,
|
||||
'instance_id': instance['id'],
|
||||
'attach_time': timeutils.isotime(None, True),
|
||||
'delete_on_termination': delete_on_termination.next()})
|
||||
db_api.update_item(context, network_interface)
|
||||
cleaner.addCleanup(
|
||||
network_interface_api._detach_network_interface_item,
|
||||
context, network_interface)
|
||||
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)
|
||||
novadb_instance = novadb.instance_get_by_uuid(context,
|
||||
os_instance.id)
|
||||
instances_infos.append((instance, os_instance, novadb_instance,
|
||||
network_interfaces,))
|
||||
|
||||
# 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_id = _generate_reservation_id()
|
||||
return _format_reservation(context, ec2_reservation_id, ec2_instances)
|
||||
common_nw_info = _get_common_nw_info(context)
|
||||
return _format_reservation(context, ec2_reservation_id, instances_infos,
|
||||
common_nw_info)
|
||||
|
||||
|
||||
def terminate_instances(context, instance_id):
|
||||
@@ -230,48 +229,131 @@ def terminate_instances(context, instance_id):
|
||||
|
||||
def describe_instances(context, instance_id=None, filter=None, **kwargs):
|
||||
|
||||
# TODO(ft): implement filters by network attributes
|
||||
ec2 = ec2client.ec2client(context)
|
||||
result = ec2.describe_instances(instance_id=instance_id,
|
||||
filter=filter, **kwargs)
|
||||
|
||||
os_instance_ids = [
|
||||
ec2utils.ec2_inst_id_to_uuid(context, inst['instanceId'])
|
||||
for reservation in result['reservationSet']
|
||||
for inst in reservation['instancesSet']]
|
||||
neutron = clients.neutron(context)
|
||||
os_ports = neutron.list_ports(device_id=os_instance_ids)['ports']
|
||||
os_ports = dict((p['id'], p) for p in os_ports)
|
||||
instances = db_api.get_items(context, 'i')
|
||||
instances_by_os_id = dict((i['os_id'], i) for i in instances)
|
||||
nova = clients.nova(context)
|
||||
os_instances = nova.servers.list()
|
||||
# TODO(ft): implement search db items by os_id in DB layer
|
||||
network_interfaces = collections.defaultdict(list)
|
||||
for eni in db_api.get_items(context, 'eni'):
|
||||
if 'instance_id' in eni:
|
||||
network_interfaces[eni['instance_id']].append(eni)
|
||||
os_floating_ips = neutron.list_floatingips()['floatingips']
|
||||
os_floating_ip_ids = set(ip['id'] for ip in os_floating_ips)
|
||||
addresses = collections.defaultdict(list)
|
||||
for address in db_api.get_items(context, 'eipalloc'):
|
||||
if ('network_interface_id' in address and
|
||||
address['os_id'] in os_floating_ip_ids):
|
||||
addresses[address['network_interface_id']].append(address)
|
||||
security_groups = security_group_api._format_security_groups_ids_names(
|
||||
context)
|
||||
|
||||
for ec2_reservation in result['reservationSet']:
|
||||
for ec2_instance in ec2_reservation['instancesSet']:
|
||||
inst_id = ec2_instance['instanceId']
|
||||
instance_network_interfaces = network_interfaces[inst_id]
|
||||
ports_info = [(eni, os_ports[eni['os_id']], addresses[eni['id']])
|
||||
for eni in instance_network_interfaces
|
||||
if eni['os_id'] in os_ports]
|
||||
_format_instance(context, ec2_instance, ports_info,
|
||||
security_groups)
|
||||
reservations = _prepare_reservations(
|
||||
context, os_instances, instances_by_os_id, network_interfaces)
|
||||
common_nw_info = _get_common_nw_info(context)
|
||||
|
||||
return result
|
||||
ec2_reservations = []
|
||||
for reservation_id, instances_infos in reservations.iteritems():
|
||||
ec2_reservations.append(_format_reservation(
|
||||
context, reservation_id, instances_infos, common_nw_info))
|
||||
return {'reservationSet': ec2_reservations}
|
||||
|
||||
|
||||
def _format_instance(context, ec2_instance, ports_info, security_groups):
|
||||
def _prepare_reservations(context, os_instances, instances_by_os_id,
|
||||
network_interfaces):
|
||||
reservations = {}
|
||||
for os_instance in os_instances:
|
||||
novadb_instance = novadb.instance_get_by_uuid(context, os_instance.id)
|
||||
if os_instance.id in instances_by_os_id:
|
||||
instance = instances_by_os_id.get(os_instance.id)
|
||||
reservation_id = instance['reservation_id']
|
||||
else:
|
||||
reservation_id = novadb_instance['reservation_id']
|
||||
instance = db_api.add_item(
|
||||
context, 'i',
|
||||
{'os_id': os_instance.id,
|
||||
'vpc_id': None,
|
||||
'reservation_id': reservation_id,
|
||||
'launch_index': novadb_instance['launch_index']})
|
||||
if reservation_id not in reservations:
|
||||
reservations[reservation_id] = []
|
||||
reservations[reservation_id].append(
|
||||
(instance, os_instance, novadb_instance,
|
||||
network_interfaces[instance['id']],))
|
||||
return reservations
|
||||
|
||||
|
||||
def _format_reservation(context, reservation_id, instances_infos,
|
||||
common_nw_info):
|
||||
os_ports, addresses, security_groups = common_nw_info
|
||||
ec2_instances = []
|
||||
for (instance, os_instance, novadb_instance,
|
||||
network_interfaces) in instances_infos:
|
||||
ports_info = [(eni,
|
||||
os_ports[eni['os_id']],
|
||||
addresses.get(eni['id'], []))
|
||||
for eni in network_interfaces
|
||||
if eni['os_id'] in os_ports]
|
||||
ec2_instances.append(
|
||||
_format_instance(context, instance, os_instance,
|
||||
novadb_instance, ports_info, security_groups))
|
||||
ec2_reservation = {'reservationId': reservation_id,
|
||||
'ownerId': os_instance.tenant_id,
|
||||
'instancesSet': ec2_instances}
|
||||
if not instance['vpc_id']:
|
||||
ec2_reservation['groupSet'] = _format_group_set(
|
||||
context, os_instance.security_groups)
|
||||
return ec2_reservation
|
||||
|
||||
|
||||
def _format_instance(context, instance, os_instance, novadb_instance,
|
||||
ports_info, security_groups):
|
||||
ec2_instance = {}
|
||||
ec2_instance['instanceId'] = instance['id']
|
||||
image_uuid = os_instance.image['id'] if os_instance.image else ''
|
||||
ec2_instance['imageId'] = ec2utils.glance_id_to_ec2_id(context, image_uuid)
|
||||
_cloud_format_kernel_id(context, novadb_instance, ec2_instance, 'kernelId')
|
||||
_cloud_format_ramdisk_id(context, novadb_instance, ec2_instance,
|
||||
'ramdiskId')
|
||||
ec2_instance['instanceState'] = _cloud_state_description(
|
||||
getattr(os_instance, 'OS-EXT-STS:vm_state'))
|
||||
|
||||
fixed_ip, fixed_ip6, floating_ip = _get_ip_info_for_instance(os_instance)
|
||||
if fixed_ip6:
|
||||
ec2_instance['dnsNameV6'] = fixed_ip6
|
||||
if CONF.ec2_private_dns_show_ip:
|
||||
ec2_instance['privateDnsName'] = fixed_ip
|
||||
else:
|
||||
ec2_instance['privateDnsName'] = novadb_instance['hostname']
|
||||
ec2_instance['privateIpAddress'] = fixed_ip
|
||||
if floating_ip is not None:
|
||||
ec2_instance['ipAddress'] = floating_ip
|
||||
ec2_instance['dnsName'] = floating_ip
|
||||
ec2_instance['keyName'] = os_instance.key_name
|
||||
|
||||
# NOTE(ft): add tags
|
||||
# i['tagSet'] = []
|
||||
#
|
||||
# for k, v in utils.instance_meta(instance).iteritems():
|
||||
# i['tagSet'].append({'key': k, 'value': v})
|
||||
|
||||
# NOTE(ft): add client token
|
||||
# client_token = self._get_client_token(context, instance_uuid)
|
||||
# if client_token:
|
||||
# i['clientToken'] = client_token
|
||||
|
||||
if context.is_admin:
|
||||
ec2_instance['keyName'] = '%s (%s, %s)' % (ec2_instance['keyName'],
|
||||
os_instance.tenant_id,
|
||||
getattr(os_instance, 'OS-EXT-SRV-ATTR:host'))
|
||||
ec2_instance['productCodesSet'] = None
|
||||
_cloud_format_instance_type(context, os_instance, ec2_instance)
|
||||
ec2_instance['launchTime'] = os_instance.created
|
||||
ec2_instance['amiLaunchIndex'] = instance['launch_index']
|
||||
_cloud_format_instance_root_device_name(novadb_instance, ec2_instance)
|
||||
_cloud_format_instance_bdm(context, instance['os_id'],
|
||||
ec2_instance['rootDeviceName'], ec2_instance)
|
||||
ec2_instance['placement'] = {
|
||||
'availabilityZone': getattr(os_instance,
|
||||
'OS-EXT-AZ:availability_zone')
|
||||
}
|
||||
if not ports_info:
|
||||
# TODO(ft): boto uses 2010-08-31 version of AWS protocol
|
||||
# which doesn't contain groupSet element in an instance
|
||||
# We should support different versions of output data
|
||||
# ec2_instance['groupSet'] = _format_group_set(
|
||||
# context, os_instance.security_groups)
|
||||
return ec2_instance
|
||||
ec2_network_interfaces = []
|
||||
for network_interface, os_port, addresses in ports_info:
|
||||
@@ -288,24 +370,39 @@ def _format_instance(context, ec2_instance, ports_info, security_groups):
|
||||
# NOTE(ft): get instance's subnet by instance's privateIpAddress
|
||||
instance_ip = ec2_instance['privateIpAddress']
|
||||
network_interface = None
|
||||
for network_interface, os_port, addresses in ports_info:
|
||||
for ((network_interface, os_port, _addresses),
|
||||
ec2_network_interface) in zip(ports_info, ec2_network_interfaces):
|
||||
if instance_ip in (ip['ip_address']
|
||||
for ip in os_port['fixed_ips']):
|
||||
ec2_instance['subnetId'] = network_interface['subnet_id']
|
||||
# TODO(ft): boto uses 2010-08-31 version of AWS protocol
|
||||
# which doesn't contain groupSet element in an instance
|
||||
# We should support different versions of output data
|
||||
# ec2_instance['groupSet'] = ec2_network_interface['groupSet']
|
||||
break
|
||||
if network_interface:
|
||||
ec2_instance['vpcId'] = network_interface['vpc_id']
|
||||
|
||||
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 _get_common_nw_info(context):
|
||||
neutron = clients.neutron(context)
|
||||
|
||||
os_ports = neutron.list_ports()['ports']
|
||||
os_ports = dict((p['id'], p) for p in os_ports)
|
||||
|
||||
os_floating_ips = neutron.list_floatingips()['floatingips']
|
||||
os_floating_ip_ids = set(ip['id'] for ip in os_floating_ips)
|
||||
addresses = collections.defaultdict(list)
|
||||
for address in db_api.get_items(context, 'eipalloc'):
|
||||
if ('network_interface_id' in address and
|
||||
address['os_id'] in os_floating_ip_ids):
|
||||
addresses[address['network_interface_id']].append(address)
|
||||
|
||||
security_groups = security_group_api._format_security_groups_ids_names(
|
||||
context)
|
||||
|
||||
return os_ports, addresses, security_groups
|
||||
|
||||
|
||||
def _check_min_max_count(min_count, max_count):
|
||||
@@ -508,7 +605,8 @@ def _parse_network_interface_parameters(context, neutron, params):
|
||||
subnet_vpcs = set(s['vpc_id'] for s in subnets)
|
||||
network_interface_vpcs = set(eni['vpc_id']
|
||||
for eni in network_interfaces)
|
||||
if len(subnet_vpcs | network_interface_vpcs) > 1:
|
||||
vpc_ids = subnet_vpcs | network_interface_vpcs
|
||||
if len(vpc_ids) > 1:
|
||||
msg = _('Network interface attachments may not cross '
|
||||
'VPC boundaries.')
|
||||
raise exception.InvalidParameterValue(msg)
|
||||
@@ -519,7 +617,9 @@ def _parse_network_interface_parameters(context, neutron, params):
|
||||
|
||||
delete_on_termination_flags = ([False] * len(network_interfaces) +
|
||||
delete_on_termination_flags)
|
||||
return (network_interfaces, create_network_interfaces_args,
|
||||
return (next(iter(vpc_ids), None),
|
||||
network_interfaces,
|
||||
create_network_interfaces_args,
|
||||
delete_on_termination_flags)
|
||||
|
||||
|
||||
@@ -541,14 +641,7 @@ def _create_network_interfaces(context, cleaner, params):
|
||||
return network_interfaces
|
||||
|
||||
|
||||
def _get_vpc_default_security_group_id(context, network_interfaces,
|
||||
create_network_interfaces_args):
|
||||
if network_interfaces:
|
||||
vpc_id = network_interfaces[0]['vpc_id']
|
||||
else:
|
||||
subnet = db_api.get_item_by_id(
|
||||
context, 'subnet', create_network_interfaces_args[0][0])
|
||||
vpc_id = subnet['vpc_id']
|
||||
def _get_vpc_default_security_group_id(context, vpc_id):
|
||||
default_groups = security_group_api.describe_security_groups(
|
||||
context,
|
||||
filter=[{'name': 'vpc-id', 'value': [vpc_id]},
|
||||
@@ -560,6 +653,25 @@ def _get_vpc_default_security_group_id(context, network_interfaces,
|
||||
return [sg['os_id'] for sg in security_groups]
|
||||
|
||||
|
||||
def _format_group_set(context, os_security_groups):
|
||||
if not os_security_groups:
|
||||
return None
|
||||
# TODO(ft): add groupId
|
||||
return [{'groupName': sg['name']} for sg in os_security_groups]
|
||||
|
||||
|
||||
def _get_ip_info_for_instance(os_instance):
|
||||
addresses = list(itertools.chain(*os_instance.addresses.itervalues()))
|
||||
fixed_ip = next((addr['addr'] for addr in addresses
|
||||
if addr['version'] == 4 and
|
||||
addr['OS-EXT-IPS:type'] == 'fixed'), None)
|
||||
fixed_ip6 = next((addr['addr'] for addr in addresses
|
||||
if addr['version'] == 6 and
|
||||
addr['OS-EXT-IPS:type'] == 'fixed'), None)
|
||||
floating_ip = next((addr['addr'] for addr in addresses
|
||||
if addr['OS-EXT-IPS:type'] == 'floating'), None)
|
||||
return fixed_ip, fixed_ip6, floating_ip
|
||||
|
||||
# NOTE(ft): following functions are copied from various parts of Nova
|
||||
|
||||
_dev = re.compile('^/dev/')
|
||||
@@ -570,6 +682,11 @@ def _block_device_strip_dev(device_name):
|
||||
return _dev.sub('', device_name) if device_name else device_name
|
||||
|
||||
|
||||
def _block_device_prepend_dev(device_name):
|
||||
"""Make sure there is a leading '/dev/'."""
|
||||
return device_name and '/dev/' + _block_device_strip_dev(device_name)
|
||||
|
||||
|
||||
def _cloud_parse_block_device_mapping(bdm):
|
||||
"""Parse BlockDeviceMappingItemType into flat hash
|
||||
|
||||
@@ -612,3 +729,212 @@ def _cloud_get_image_state(image):
|
||||
if state == 'active':
|
||||
state = 'available'
|
||||
return image.properties.get('image_state', state)
|
||||
|
||||
|
||||
def _cloud_format_kernel_id(context, instance_ref, result, key):
|
||||
kernel_uuid = instance_ref['kernel_id']
|
||||
if kernel_uuid is None or kernel_uuid == '':
|
||||
return
|
||||
result[key] = ec2utils.glance_id_to_ec2_id(context, kernel_uuid, 'aki')
|
||||
|
||||
|
||||
def _cloud_format_ramdisk_id(context, instance_ref, result, key):
|
||||
ramdisk_uuid = instance_ref['ramdisk_id']
|
||||
if ramdisk_uuid is None or ramdisk_uuid == '':
|
||||
return
|
||||
result[key] = ec2utils.glance_id_to_ec2_id(context, ramdisk_uuid,
|
||||
'ari')
|
||||
|
||||
|
||||
def _cloud_format_instance_type(context, os_instance, result):
|
||||
flavor = clients.nova(context).flavors.get(os_instance.flavor['id'])
|
||||
result['instanceType'] = flavor.name
|
||||
|
||||
|
||||
def _cloud_format_instance_root_device_name(novadb_instance, result):
|
||||
result['rootDeviceName'] = (novadb_instance.get('root_device_name') or
|
||||
block_device_DEFAULT_ROOT_DEV_NAME)
|
||||
|
||||
|
||||
block_device_DEFAULT_ROOT_DEV_NAME = '/dev/sda1'
|
||||
|
||||
|
||||
def _cloud_format_instance_bdm(context, instance_uuid, root_device_name,
|
||||
result):
|
||||
"""Format InstanceBlockDeviceMappingResponseItemType."""
|
||||
root_device_type = 'instance-store'
|
||||
root_device_short_name = _block_device_strip_dev(root_device_name)
|
||||
if root_device_name == root_device_short_name:
|
||||
root_device_name = _block_device_prepend_dev(root_device_name)
|
||||
cinder = clients.cinder(context)
|
||||
mapping = []
|
||||
for bdm in novadb.block_device_mapping_get_all_by_instance(context,
|
||||
instance_uuid):
|
||||
volume_id = bdm['volume_id']
|
||||
if (volume_id is None or bdm['no_device']):
|
||||
continue
|
||||
|
||||
if ((bdm['snapshot_id'] or bdm['volume_id']) and
|
||||
(bdm['device_name'] == root_device_name or
|
||||
bdm['device_name'] == root_device_short_name)):
|
||||
root_device_type = 'ebs'
|
||||
|
||||
vol = cinder.volumes.get(volume_id)
|
||||
# TODO(yamahata): volume attach time
|
||||
ebs = {'volumeId': ec2utils.id_to_ec2_vol_id(volume_id),
|
||||
'deleteOnTermination': bdm['delete_on_termination'],
|
||||
'attachTime': '',
|
||||
'status': _cloud_get_volume_attach_status(vol), }
|
||||
res = {'deviceName': bdm['device_name'],
|
||||
'ebs': ebs, }
|
||||
mapping.append(res)
|
||||
|
||||
if mapping:
|
||||
result['blockDeviceMapping'] = mapping
|
||||
result['rootDeviceType'] = root_device_type
|
||||
|
||||
|
||||
def _cloud_get_volume_attach_status(volume):
|
||||
if volume.status in ('attaching', 'detaching'):
|
||||
return volume.status
|
||||
elif volume.attachments:
|
||||
return 'attached'
|
||||
else:
|
||||
return 'detached'
|
||||
|
||||
|
||||
# NOTE(ft): nova/compute/vm_states.py
|
||||
|
||||
"""Possible vm states for instances.
|
||||
|
||||
Compute instance vm states represent the state of an instance as it pertains to
|
||||
a user or administrator.
|
||||
|
||||
vm_state describes a VM's current stable (not transition) state. That is, if
|
||||
there is no ongoing compute API calls (running tasks), vm_state should reflect
|
||||
what the customer expect the VM to be. When combined with task states
|
||||
(task_states.py), a better picture can be formed regarding the instance's
|
||||
health and progress.
|
||||
|
||||
See http://wiki.openstack.org/VMState
|
||||
"""
|
||||
|
||||
vm_states_ACTIVE = 'active' # VM is running
|
||||
vm_states_BUILDING = 'building' # VM only exists in DB
|
||||
vm_states_PAUSED = 'paused'
|
||||
vm_states_SUSPENDED = 'suspended' # VM is suspended to disk.
|
||||
vm_states_STOPPED = 'stopped' # VM is powered off, the disk image is still
|
||||
# there.
|
||||
vm_states_RESCUED = 'rescued' # A rescue image is running with the original VM
|
||||
# image attached.
|
||||
vm_states_RESIZED = 'resized' # a VM with the new size is active. The user is
|
||||
# expected to manually confirm or revert.
|
||||
|
||||
vm_states_SOFT_DELETED = 'soft-delete' # VM is marked as deleted but the disk
|
||||
# images are still available to restore.
|
||||
vm_states_DELETED = 'deleted' # VM is permanently deleted.
|
||||
|
||||
vm_states_ERROR = 'error'
|
||||
|
||||
vm_states_SHELVED = 'shelved' # VM is powered off, resources still on
|
||||
# hypervisor
|
||||
vm_states_SHELVED_OFFLOADED = 'shelved_offloaded' # VM and associated
|
||||
# resources are not on hypervisor
|
||||
|
||||
vm_states_ALLOW_SOFT_REBOOT = [vm_states_ACTIVE] # states we can soft reboot
|
||||
# from
|
||||
vm_states_ALLOW_HARD_REBOOT = (
|
||||
vm_states_ALLOW_SOFT_REBOOT +
|
||||
[vm_states_STOPPED, vm_states_PAUSED, vm_states_SUSPENDED,
|
||||
vm_states_ERROR])
|
||||
# states we allow hard reboot from
|
||||
|
||||
# NOTE(ft): end of nova/compute/vm_states.py
|
||||
|
||||
# NOTE(ft): nova/api/ec2/inst_states.py
|
||||
|
||||
inst_state_PENDING_CODE = 0
|
||||
inst_state_RUNNING_CODE = 16
|
||||
inst_state_SHUTTING_DOWN_CODE = 32
|
||||
inst_state_TERMINATED_CODE = 48
|
||||
inst_state_STOPPING_CODE = 64
|
||||
inst_state_STOPPED_CODE = 80
|
||||
|
||||
inst_state_PENDING = 'pending'
|
||||
inst_state_RUNNING = 'running'
|
||||
inst_state_SHUTTING_DOWN = 'shutting-down'
|
||||
inst_state_TERMINATED = 'terminated'
|
||||
inst_state_STOPPING = 'stopping'
|
||||
inst_state_STOPPED = 'stopped'
|
||||
|
||||
# non-ec2 value
|
||||
inst_state_MIGRATE = 'migrate'
|
||||
inst_state_RESIZE = 'resize'
|
||||
inst_state_PAUSE = 'pause'
|
||||
inst_state_SUSPEND = 'suspend'
|
||||
inst_state_RESCUE = 'rescue'
|
||||
|
||||
# EC2 API instance status code
|
||||
_NAME_TO_CODE = {
|
||||
inst_state_PENDING: inst_state_PENDING_CODE,
|
||||
inst_state_RUNNING: inst_state_RUNNING_CODE,
|
||||
inst_state_SHUTTING_DOWN: inst_state_SHUTTING_DOWN_CODE,
|
||||
inst_state_TERMINATED: inst_state_TERMINATED_CODE,
|
||||
inst_state_STOPPING: inst_state_STOPPING_CODE,
|
||||
inst_state_STOPPED: inst_state_STOPPED_CODE,
|
||||
|
||||
# approximation
|
||||
inst_state_MIGRATE: inst_state_RUNNING_CODE,
|
||||
inst_state_RESIZE: inst_state_RUNNING_CODE,
|
||||
inst_state_PAUSE: inst_state_STOPPED_CODE,
|
||||
inst_state_SUSPEND: inst_state_STOPPED_CODE,
|
||||
inst_state_RESCUE: inst_state_RUNNING_CODE,
|
||||
}
|
||||
_CODE_TO_NAMES = dict([(code,
|
||||
[item[0] for item in _NAME_TO_CODE.iteritems()
|
||||
if item[1] == code])
|
||||
for code in set(_NAME_TO_CODE.itervalues())])
|
||||
|
||||
|
||||
def inst_state_name_to_code(name):
|
||||
return _NAME_TO_CODE.get(name, inst_state_PENDING_CODE)
|
||||
|
||||
|
||||
def inst_state_code_to_names(code):
|
||||
return _CODE_TO_NAMES.get(code, [])
|
||||
|
||||
# NOTE(ft): end of nova/api/ec2/inst_state.py
|
||||
|
||||
# EC2 API can return the following values as documented in the EC2 API
|
||||
# http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/
|
||||
# ApiReference-ItemType-InstanceStateType.html
|
||||
# pending 0 | running 16 | shutting-down 32 | terminated 48 | stopping 64 |
|
||||
# stopped 80
|
||||
_STATE_DESCRIPTION_MAP = {
|
||||
None: inst_state_PENDING,
|
||||
vm_states_ACTIVE: inst_state_RUNNING,
|
||||
vm_states_BUILDING: inst_state_PENDING,
|
||||
vm_states_DELETED: inst_state_TERMINATED,
|
||||
vm_states_SOFT_DELETED: inst_state_TERMINATED,
|
||||
vm_states_STOPPED: inst_state_STOPPED,
|
||||
vm_states_PAUSED: inst_state_PAUSE,
|
||||
vm_states_SUSPENDED: inst_state_SUSPEND,
|
||||
vm_states_RESCUED: inst_state_RESCUE,
|
||||
vm_states_RESIZED: inst_state_RESIZE,
|
||||
}
|
||||
_EC2_STATE_TO_VM = dict((state,
|
||||
[item[0]
|
||||
for item in _STATE_DESCRIPTION_MAP.iteritems()
|
||||
if item[1] == state])
|
||||
for state in set(_STATE_DESCRIPTION_MAP.itervalues()))
|
||||
|
||||
|
||||
def _cloud_state_description(vm_state):
|
||||
"""Map the vm state to the server status string."""
|
||||
# Note(maoy): We do not provide EC2 compatibility
|
||||
# in shutdown_terminate flag behavior. So we ignore
|
||||
# it here.
|
||||
name = _STATE_DESCRIPTION_MAP.get(vm_state, vm_state)
|
||||
|
||||
return {'code': inst_state_name_to_code(name),
|
||||
'name': name}
|
||||
|
||||
@@ -78,6 +78,7 @@ class RequestContext(object):
|
||||
self.user_name = user_name
|
||||
self.project_name = project_name
|
||||
self.is_admin = is_admin
|
||||
# TODO(ft): call policy.check_is_admin if is_admin is None
|
||||
self.api_version = api_version
|
||||
if overwrite or not hasattr(local.store, 'context'):
|
||||
self.update_store()
|
||||
|
||||
@@ -85,6 +85,16 @@ def s3_image_get(context, image_id):
|
||||
return IMPL.s3_image_get(context, image_id)
|
||||
|
||||
|
||||
def s3_image_get_by_uuid(context, image_uuid):
|
||||
"""Find local s3 image represented by the provided uuid."""
|
||||
return IMPL.s3_image_get_by_uuid(context, image_uuid)
|
||||
|
||||
|
||||
def s3_image_create(context, image_uuid):
|
||||
"""Create local s3 image represented by provided uuid."""
|
||||
return IMPL.s3_image_create(context, image_uuid)
|
||||
|
||||
|
||||
###################
|
||||
|
||||
|
||||
@@ -136,3 +146,17 @@ def ec2_instance_get_by_uuid(context, instance_uuid):
|
||||
|
||||
def ec2_instance_get_by_id(context, instance_id):
|
||||
return IMPL.ec2_instance_get_by_id(context, instance_id)
|
||||
|
||||
|
||||
def instance_get_by_uuid(context, uuid, columns_to_join=None, use_slave=False):
|
||||
"""Get an instance or raise if it does not exist."""
|
||||
return IMPL.instance_get_by_uuid(context, uuid,
|
||||
columns_to_join, use_slave=use_slave)
|
||||
|
||||
|
||||
def block_device_mapping_get_all_by_instance(context, instance_uuid,
|
||||
use_slave=False):
|
||||
"""Get all block device mapping belonging to an instance."""
|
||||
return IMPL.block_device_mapping_get_all_by_instance(context,
|
||||
instance_uuid,
|
||||
use_slave)
|
||||
|
||||
@@ -26,6 +26,7 @@ from sqlalchemy import or_
|
||||
import ec2api.context
|
||||
from ec2api import exception
|
||||
from ec2api.novadb.sqlalchemy import models
|
||||
from ec2api.openstack.common.db import exception as db_exc
|
||||
from ec2api.openstack.common.db.sqlalchemy import session as db_session
|
||||
from ec2api.openstack.common.gettextutils import _
|
||||
from ec2api.openstack.common import log as logging
|
||||
@@ -178,6 +179,30 @@ def s3_image_get(context, image_id):
|
||||
return result
|
||||
|
||||
|
||||
def s3_image_get_by_uuid(context, image_uuid):
|
||||
"""Find local s3 image represented by the provided uuid."""
|
||||
result = (model_query(context, models.S3Image, read_deleted="yes").
|
||||
filter_by(uuid=image_uuid).
|
||||
first())
|
||||
|
||||
if not result:
|
||||
raise exception.NovaDbImageNotFound(image_id=image_uuid)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def s3_image_create(context, image_uuid):
|
||||
"""Create local s3 image represented by provided uuid."""
|
||||
try:
|
||||
s3_image_ref = models.S3Image()
|
||||
s3_image_ref.update({'uuid': image_uuid})
|
||||
s3_image_ref.save()
|
||||
except Exception as e:
|
||||
raise db_exc.DBError(e)
|
||||
|
||||
return s3_image_ref
|
||||
|
||||
|
||||
##################
|
||||
|
||||
|
||||
@@ -323,3 +348,51 @@ def _ec2_instance_get_query(context, session=None):
|
||||
models.InstanceIdMapping,
|
||||
session=session,
|
||||
read_deleted='yes')
|
||||
|
||||
|
||||
@require_context
|
||||
def instance_get_by_uuid(context, uuid, columns_to_join=None, use_slave=False):
|
||||
return _instance_get_by_uuid(context, uuid,
|
||||
columns_to_join=columns_to_join, use_slave=use_slave)
|
||||
|
||||
|
||||
def _instance_get_by_uuid(context, uuid, session=None,
|
||||
columns_to_join=None, use_slave=False):
|
||||
result = (_build_instance_get(context, session=session,
|
||||
columns_to_join=columns_to_join,
|
||||
use_slave=use_slave).
|
||||
filter_by(uuid=uuid).
|
||||
first())
|
||||
|
||||
if not result:
|
||||
raise exception.NovaDbInstanceNotFound(instance_id=uuid)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _build_instance_get(context, session=None,
|
||||
columns_to_join=None, use_slave=False):
|
||||
query = model_query(context, models.Instance, session=session,
|
||||
project_only=True, use_slave=use_slave,
|
||||
read_deleted="no")
|
||||
return query
|
||||
|
||||
|
||||
def _block_device_mapping_get_query(context, session=None,
|
||||
columns_to_join=None, use_slave=False):
|
||||
if columns_to_join is None:
|
||||
columns_to_join = []
|
||||
|
||||
query = model_query(context, models.BlockDeviceMapping,
|
||||
session=session, use_slave=use_slave,
|
||||
read_deleted="no")
|
||||
|
||||
return query
|
||||
|
||||
|
||||
@require_context
|
||||
def block_device_mapping_get_all_by_instance(context, instance_uuid,
|
||||
use_slave=False):
|
||||
return (_block_device_mapping_get_query(context, use_slave=use_slave).
|
||||
filter_by(instance_uuid=instance_uuid).
|
||||
all())
|
||||
|
||||
@@ -20,11 +20,13 @@ SQLAlchemy models for nova data.
|
||||
"""
|
||||
|
||||
from oslo.config import cfg
|
||||
from sqlalchemy import Column, Index, Integer, String
|
||||
from sqlalchemy import Column, Index, Integer, Enum, String
|
||||
from sqlalchemy.dialects.mysql import MEDIUMTEXT
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import Text
|
||||
from sqlalchemy import DateTime, Boolean, Text
|
||||
from sqlalchemy.orm import object_mapper
|
||||
|
||||
from ec2api.novadb.sqlalchemy import types
|
||||
from ec2api.openstack.common.db.sqlalchemy import models
|
||||
|
||||
CONF = cfg.CONF
|
||||
@@ -81,3 +83,196 @@ class InstanceIdMapping(BASE, NovaBase):
|
||||
)
|
||||
id = Column(Integer, primary_key=True, nullable=False, autoincrement=True)
|
||||
uuid = Column(String(36), nullable=False)
|
||||
|
||||
|
||||
class Instance(BASE, NovaBase):
|
||||
"""Represents a guest VM."""
|
||||
__tablename__ = 'instances'
|
||||
__table_args__ = (
|
||||
Index('uuid', 'uuid', unique=True),
|
||||
Index('project_id', 'project_id'),
|
||||
Index('instances_host_deleted_idx',
|
||||
'host', 'deleted'),
|
||||
Index('instances_reservation_id_idx',
|
||||
'reservation_id'),
|
||||
Index('instances_terminated_at_launched_at_idx',
|
||||
'terminated_at', 'launched_at'),
|
||||
Index('instances_uuid_deleted_idx',
|
||||
'uuid', 'deleted'),
|
||||
Index('instances_task_state_updated_at_idx',
|
||||
'task_state', 'updated_at'),
|
||||
Index('instances_host_node_deleted_idx',
|
||||
'host', 'node', 'deleted'),
|
||||
Index('instances_host_deleted_cleaned_idx',
|
||||
'host', 'deleted', 'cleaned'),
|
||||
)
|
||||
injected_files = []
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
try:
|
||||
base_name = CONF.instance_name_template % self.id
|
||||
except TypeError:
|
||||
# Support templates like "uuid-%(uuid)s", etc.
|
||||
info = {}
|
||||
# NOTE(russellb): Don't use self.iteritems() here, as it will
|
||||
# result in infinite recursion on the name property.
|
||||
for column in iter(object_mapper(self).columns):
|
||||
key = column.name
|
||||
# prevent recursion if someone specifies %(name)s
|
||||
# %(name)s will not be valid.
|
||||
if key == 'name':
|
||||
continue
|
||||
info[key] = self[key]
|
||||
try:
|
||||
base_name = CONF.instance_name_template % info
|
||||
except KeyError:
|
||||
base_name = self.uuid
|
||||
return base_name
|
||||
|
||||
@property
|
||||
def _extra_keys(self):
|
||||
return ['name']
|
||||
|
||||
user_id = Column(String(255))
|
||||
project_id = Column(String(255))
|
||||
|
||||
image_ref = Column(String(255))
|
||||
kernel_id = Column(String(255))
|
||||
ramdisk_id = Column(String(255))
|
||||
hostname = Column(String(255))
|
||||
|
||||
launch_index = Column(Integer)
|
||||
key_name = Column(String(255))
|
||||
key_data = Column(MediumText())
|
||||
|
||||
power_state = Column(Integer)
|
||||
vm_state = Column(String(255))
|
||||
task_state = Column(String(255))
|
||||
|
||||
memory_mb = Column(Integer)
|
||||
vcpus = Column(Integer)
|
||||
root_gb = Column(Integer)
|
||||
ephemeral_gb = Column(Integer)
|
||||
ephemeral_key_uuid = Column(String(36))
|
||||
|
||||
# This is not related to hostname, above. It refers
|
||||
# to the nova node.
|
||||
host = Column(String(255)) # , ForeignKey('hosts.id'))
|
||||
# To identify the "ComputeNode" which the instance resides in.
|
||||
# This equals to ComputeNode.hypervisor_hostname.
|
||||
node = Column(String(255))
|
||||
|
||||
# *not* flavorid, this is the internal primary_key
|
||||
instance_type_id = Column(Integer)
|
||||
|
||||
user_data = Column(MediumText())
|
||||
|
||||
reservation_id = Column(String(255))
|
||||
|
||||
scheduled_at = Column(DateTime)
|
||||
launched_at = Column(DateTime)
|
||||
terminated_at = Column(DateTime)
|
||||
|
||||
availability_zone = Column(String(255))
|
||||
|
||||
# User editable field for display in user-facing UIs
|
||||
display_name = Column(String(255))
|
||||
display_description = Column(String(255))
|
||||
|
||||
# To remember on which host an instance booted.
|
||||
# An instance may have moved to another host by live migration.
|
||||
launched_on = Column(MediumText())
|
||||
|
||||
# NOTE(jdillaman): locked deprecated in favor of locked_by,
|
||||
# to be removed in Icehouse
|
||||
locked = Column(Boolean)
|
||||
locked_by = Column(Enum('owner', 'admin'))
|
||||
|
||||
os_type = Column(String(255))
|
||||
architecture = Column(String(255))
|
||||
vm_mode = Column(String(255))
|
||||
uuid = Column(String(36))
|
||||
|
||||
root_device_name = Column(String(255))
|
||||
default_ephemeral_device = Column(String(255))
|
||||
default_swap_device = Column(String(255))
|
||||
config_drive = Column(String(255))
|
||||
|
||||
# User editable field meant to represent what ip should be used
|
||||
# to connect to the instance
|
||||
access_ip_v4 = Column(types.IPAddress())
|
||||
access_ip_v6 = Column(types.IPAddress())
|
||||
|
||||
auto_disk_config = Column(Boolean())
|
||||
progress = Column(Integer)
|
||||
|
||||
# EC2 instance_initiated_shutdown_terminate
|
||||
# True: -> 'terminate'
|
||||
# False: -> 'stop'
|
||||
# Note(maoy): currently Nova will always stop instead of terminate
|
||||
# no matter what the flag says. So we set the default to False.
|
||||
shutdown_terminate = Column(Boolean(), default=False)
|
||||
|
||||
# EC2 disable_api_termination
|
||||
disable_terminate = Column(Boolean(), default=False)
|
||||
|
||||
# OpenStack compute cell name. This will only be set at the top of
|
||||
# the cells tree and it'll be a full cell name such as 'api!hop1!hop2'
|
||||
cell_name = Column(String(255))
|
||||
internal_id = Column(Integer)
|
||||
|
||||
# Records whether an instance has been deleted from disk
|
||||
cleaned = Column(Integer, default=0)
|
||||
|
||||
|
||||
class BlockDeviceMapping(BASE, NovaBase):
|
||||
"""Represents block device mapping that is defined by EC2."""
|
||||
__tablename__ = "block_device_mapping"
|
||||
__table_args__ = (
|
||||
Index('snapshot_id', 'snapshot_id'),
|
||||
Index('volume_id', 'volume_id'),
|
||||
Index('block_device_mapping_instance_uuid_device_name_idx',
|
||||
'instance_uuid', 'device_name'),
|
||||
Index('block_device_mapping_instance_uuid_volume_id_idx',
|
||||
'instance_uuid', 'volume_id'),
|
||||
Index('block_device_mapping_instance_uuid_idx', 'instance_uuid'),
|
||||
# TODO(sshturm) Should be dropped. `virtual_name` was dropped
|
||||
# in 186 migration,
|
||||
# Duplicates `block_device_mapping_instance_uuid_device_name_idx`index.
|
||||
Index("block_device_mapping_instance_uuid_virtual_name"
|
||||
"_device_name_idx", 'instance_uuid', 'device_name'),
|
||||
)
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
instance_uuid = Column(String(36))
|
||||
source_type = Column(String(255))
|
||||
destination_type = Column(String(255))
|
||||
guest_format = Column(String(255))
|
||||
device_type = Column(String(255))
|
||||
disk_bus = Column(String(255))
|
||||
|
||||
boot_index = Column(Integer)
|
||||
|
||||
device_name = Column(String(255))
|
||||
|
||||
# default=False for compatibility of the existing code.
|
||||
# With EC2 API,
|
||||
# default True for ami specified device.
|
||||
# default False for created with other timing.
|
||||
# TODO(sshturm) add default in db
|
||||
delete_on_termination = Column(Boolean, default=False)
|
||||
|
||||
snapshot_id = Column(String(36))
|
||||
|
||||
volume_id = Column(String(36))
|
||||
volume_size = Column(Integer)
|
||||
|
||||
image_id = Column(String(36))
|
||||
|
||||
# for no device to suppress devices.
|
||||
no_device = Column(Boolean)
|
||||
|
||||
connection_info = Column(MediumText())
|
||||
|
||||
31
ec2api/novadb/sqlalchemy/types.py
Normal file
31
ec2api/novadb/sqlalchemy/types.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Custom SQLAlchemy types."""
|
||||
|
||||
from sqlalchemy.dialects import postgresql
|
||||
from sqlalchemy import types
|
||||
|
||||
|
||||
class IPAddress(types.TypeDecorator):
|
||||
"""An SQLAlchemy type representing an IP-address."""
|
||||
|
||||
impl = types.String
|
||||
|
||||
def load_dialect_impl(self, dialect):
|
||||
if dialect.name == 'postgresql':
|
||||
return dialect.type_descriptor(postgresql.INET())
|
||||
else:
|
||||
return dialect.type_descriptor(types.String(39))
|
||||
@@ -387,7 +387,35 @@ OS_PORT_2 = {'id': ID_OS_PORT_2,
|
||||
DB_INSTANCE_1 = {
|
||||
'id': ID_EC2_INSTANCE_1,
|
||||
'os_id': ID_OS_INSTANCE_1,
|
||||
'vpc_id': ID_EC2_VPC_1,
|
||||
'reservation_id': ID_EC2_RESERVATION_1,
|
||||
'launch_index': 0,
|
||||
}
|
||||
DB_INSTANCE_2 = {
|
||||
'id': ID_EC2_INSTANCE_2,
|
||||
'os_id': ID_OS_INSTANCE_2,
|
||||
'vpc_id': None,
|
||||
'reservation_id': ID_EC2_RESERVATION_2,
|
||||
'launch_index': 0,
|
||||
}
|
||||
|
||||
NOVADB_INSTANCE_1 = {
|
||||
'reservation_id': random_ec2_id('r'),
|
||||
'launch_index': 0,
|
||||
'kernel_id': None,
|
||||
'ramdisk_id': None,
|
||||
'root_device_name': '/dev/vda',
|
||||
'hostname': ID_EC2_INSTANCE_1,
|
||||
}
|
||||
NOVADB_INSTANCE_2 = {
|
||||
'reservation_id': ID_EC2_RESERVATION_2,
|
||||
'launch_index': 0,
|
||||
'kernel_id': None,
|
||||
'ramdisk_id': None,
|
||||
'root_device_name': '/dev/vda',
|
||||
'hostname': 'Server %s' % ID_OS_INSTANCE_2,
|
||||
}
|
||||
|
||||
EC2OS_INSTANCE_1 = {
|
||||
'instanceId': ID_EC2_INSTANCE_1,
|
||||
'privateIpAddress': IP_NETWORK_INTERFACE_2,
|
||||
@@ -409,7 +437,6 @@ EC2OS_RESERVATION_2 = {
|
||||
EC2_INSTANCE_1 = {
|
||||
'instanceId': ID_EC2_INSTANCE_1,
|
||||
'privateIpAddress': IP_NETWORK_INTERFACE_2,
|
||||
'fakeKey': 'fakeValue',
|
||||
'vpcId': ID_EC2_VPC_1,
|
||||
'subnetId': ID_EC2_SUBNET_2,
|
||||
'networkInterfaceSet': [
|
||||
@@ -450,18 +477,86 @@ EC2_INSTANCE_1 = {
|
||||
'requesterManaged': False,
|
||||
'groupSet': []},
|
||||
],
|
||||
'amiLaunchIndex': 0,
|
||||
'placement': {'availabilityZone': None},
|
||||
'dnsName': IP_ADDRESS_2,
|
||||
'instanceState': {'code': 0, 'name': 'pending'},
|
||||
'imageId': None,
|
||||
'productCodesSet': [],
|
||||
'privateDnsName': ID_EC2_INSTANCE_1,
|
||||
'keyName': None,
|
||||
'launchTime': None,
|
||||
'rootDeviceType': 'instance-store',
|
||||
'instanceType': 'fake_flavor',
|
||||
'ipAddress': IP_ADDRESS_2,
|
||||
'rootDeviceName': '/dev/vda',
|
||||
}
|
||||
EC2_INSTANCE_2 = {
|
||||
'instanceId': ID_EC2_INSTANCE_2,
|
||||
'privateIpAddress': None,
|
||||
'amiLaunchIndex': 0,
|
||||
'placement': {'availabilityZone': None},
|
||||
'dnsName': None,
|
||||
'instanceState': {'code': 0, 'name': 'pending'},
|
||||
'imageId': None,
|
||||
'productCodesSet': [],
|
||||
'privateDnsName': 'Server %s' % ID_OS_INSTANCE_2,
|
||||
'keyName': None,
|
||||
'launchTime': None,
|
||||
'rootDeviceType': 'instance-store',
|
||||
'instanceType': 'fake_flavor',
|
||||
'rootDeviceName': '/dev/vda',
|
||||
}
|
||||
EC2_INSTANCE_2 = EC2OS_INSTANCE_2
|
||||
EC2_RESERVATION_1 = {
|
||||
'reservationId': ID_EC2_RESERVATION_1,
|
||||
'ownerId': ID_OS_PROJECT,
|
||||
'instancesSet': [EC2_INSTANCE_1],
|
||||
'fakeKey': 'fakeValue',
|
||||
}
|
||||
EC2_RESERVATION_2 = {
|
||||
'reservationId': ID_EC2_RESERVATION_2,
|
||||
'ownerId': ID_OS_PROJECT,
|
||||
'groupSet': [],
|
||||
'instancesSet': [EC2_INSTANCE_2],
|
||||
'fakeKey': 'fakeValue',
|
||||
}
|
||||
|
||||
|
||||
class OSInstance(object):
|
||||
def __init__(self, instance_id, flavor=None, image=None, key_name=None,
|
||||
created=None, tenant_id=ID_OS_PROJECT, addresses={},
|
||||
security_groups=[], vm_state=None, host=None,
|
||||
availability_zone=None):
|
||||
self.id = instance_id
|
||||
self.flavor = flavor
|
||||
self.image = image
|
||||
self.key_name = key_name
|
||||
self.created = created
|
||||
self.tenant_id = tenant_id
|
||||
self.addresses = addresses
|
||||
self.security_groups = security_groups
|
||||
setattr(self, 'OS-EXT-STS:vm_state', vm_state)
|
||||
setattr(self, 'OS-EXT-SRV-ATTR:host', host)
|
||||
setattr(self, 'OS-EXT-AZ:availability_zone', availability_zone)
|
||||
|
||||
|
||||
OS_INSTANCE_1 = OSInstance(
|
||||
ID_OS_INSTANCE_1, {'id': 'fakeFlavorId'},
|
||||
addresses={
|
||||
ID_EC2_SUBNET_2: [{'addr': IP_NETWORK_INTERFACE_2,
|
||||
'version': 4,
|
||||
'OS-EXT-IPS:type': 'fixed'},
|
||||
{'addr': IP_NETWORK_INTERFACE_2_EXT_1,
|
||||
'version': 4,
|
||||
'OS-EXT-IPS:type': 'fixed'},
|
||||
{'addr': IP_NETWORK_INTERFACE_2_EXT_2,
|
||||
'version': 4,
|
||||
'OS-EXT-IPS:type': 'fixed'},
|
||||
{'addr': IP_ADDRESS_2,
|
||||
'version': 4,
|
||||
'OS-EXT-IPS:type': 'floating'}]},
|
||||
)
|
||||
OS_INSTANCE_2 = OSInstance(
|
||||
ID_OS_INSTANCE_2, {'id': 'fakeFlavorId'})
|
||||
|
||||
# DHCP options objects
|
||||
DB_DHCP_OPTIONS_1 = {'id': ID_EC2_DHCP_OPTIONS_1,
|
||||
'dhcp_configuration':
|
||||
@@ -799,7 +894,8 @@ def gen_os_port(os_id, ec2_network_interface, os_subnet_id, fixed_ips,
|
||||
|
||||
# instance generator functions
|
||||
def gen_ec2_instance(ec2_instance_id, private_ip_address='',
|
||||
ec2_network_interfaces=None, is_private_ip_in_vpc=True):
|
||||
ec2_network_interfaces=None, is_private_ip_in_vpc=True,
|
||||
floating_ip=None):
|
||||
"""Generate EC2 Instance dictionary.
|
||||
|
||||
private_ip_address must be specified as IP value or None
|
||||
@@ -809,7 +905,20 @@ def gen_ec2_instance(ec2_instance_id, private_ip_address='',
|
||||
"""
|
||||
ec2_instance = {'instanceId': ec2_instance_id,
|
||||
'privateIpAddress': private_ip_address,
|
||||
'fakeKey': 'fakeValue'}
|
||||
'amiLaunchIndex': 0,
|
||||
'placement': {'availabilityZone': None},
|
||||
'dnsName': floating_ip,
|
||||
'instanceState': {'code': 0, 'name': 'pending'},
|
||||
'imageId': None,
|
||||
'productCodesSet': [],
|
||||
'privateDnsName': ec2_instance_id,
|
||||
'keyName': None,
|
||||
'launchTime': None,
|
||||
'rootDeviceType': 'instance-store',
|
||||
'instanceType': 'fake_flavor',
|
||||
'rootDeviceName': '/dev/vda'}
|
||||
if floating_ip is not None:
|
||||
ec2_instance['ipAddress'] = floating_ip
|
||||
if ec2_network_interfaces is not None:
|
||||
ec2_instance['networkInterfaceSet'] = (
|
||||
[ni for ni in ec2_network_interfaces])
|
||||
@@ -823,5 +932,4 @@ def gen_ec2_reservation(ec2_reservation_id, ec2_instances):
|
||||
"""Generate EC2 Reservation dictionary."""
|
||||
return {'reservationId': ec2_reservation_id,
|
||||
'ownerId': ID_OS_PROJECT,
|
||||
'instancesSet': [inst for inst in ec2_instances],
|
||||
'groupSet': []}
|
||||
'instancesSet': [inst for inst in ec2_instances]}
|
||||
|
||||
@@ -52,6 +52,13 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
mock.patch('ec2api.api.instance._utils_generate_uid'))
|
||||
self.utils_generate_uid = utils_generate_uid_patcher.start()
|
||||
self.addCleanup(utils_generate_uid_patcher.stop)
|
||||
novadb_patcher = (mock.patch('ec2api.api.instance.novadb'))
|
||||
self.novadb = novadb_patcher.start()
|
||||
self.addCleanup(novadb_patcher.stop)
|
||||
glance_id_to_ec2_id_patcher = (
|
||||
mock.patch('ec2api.api.instance.ec2utils.glance_id_to_ec2_id'))
|
||||
self.glance_id_to_ec2_id = glance_id_to_ec2_id_patcher.start()
|
||||
self.addCleanup(glance_id_to_ec2_id_patcher.stop)
|
||||
|
||||
self.fake_image_class = collections.namedtuple(
|
||||
'FakeImage', ['id', 'status', 'properties'])
|
||||
@@ -72,11 +79,6 @@ 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.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.db_api.add_item.return_value = fakes.DB_INSTANCE_1
|
||||
self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1
|
||||
@@ -84,10 +86,20 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
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'
|
||||
self.glance_id_to_ec2_id.return_value = None
|
||||
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)
|
||||
self.nova_servers.create.return_value = (
|
||||
fakes.OSInstance(fakes.ID_OS_INSTANCE_1, {'id': 'fakeFlavorId'},
|
||||
addresses={
|
||||
fakes.ID_EC2_SUBNET_1: [
|
||||
{'addr': fakes.IP_NETWORK_INTERFACE_1,
|
||||
'version': 4,
|
||||
'OS-EXT-IPS:type': 'fixed'}]}))
|
||||
self.novadb.instance_get_by_uuid.return_value = fakes.NOVADB_INSTANCE_1
|
||||
self.novadb.block_device_mapping_get_all_by_instance.return_value = []
|
||||
fake_flavor = self.fake_flavor_class('fake_flavor')
|
||||
self.nova_flavors.get.return_value = fake_flavor
|
||||
|
||||
_get_vpc_default_security_group_id.return_value = None
|
||||
|
||||
@@ -120,7 +132,8 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
expected_reservation = fakes.gen_ec2_reservation(
|
||||
fakes.ID_EC2_RESERVATION_1,
|
||||
[fakes.gen_ec2_instance(
|
||||
fakes.ID_EC2_INSTANCE_1, private_ip_address=None,
|
||||
fakes.ID_EC2_INSTANCE_1,
|
||||
private_ip_address=fakes.IP_NETWORK_INTERFACE_1,
|
||||
ec2_network_interfaces=[eni])])
|
||||
self.assertThat(resp, matchers.DictMatches(expected_reservation))
|
||||
if new_port:
|
||||
@@ -162,7 +175,8 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
new_port=False)
|
||||
|
||||
@mock.patch('ec2api.api.instance._get_vpc_default_security_group_id')
|
||||
def test_run_instances_multiple_networks(
|
||||
# TODO(ft): restore test after finish extraction of Nova EC2 API
|
||||
def _test_run_instances_multiple_networks(
|
||||
self, _get_vpc_default_security_group_id):
|
||||
"""Run 2 instances at once on 2 subnets in all combinations."""
|
||||
self._build_multiple_data_model()
|
||||
@@ -256,9 +270,7 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
for os_instance_id in self.IDS_OS_INSTANCE])
|
||||
|
||||
@mock.patch('ec2api.api.network_interface.delete_network_interface')
|
||||
@mock.patch('ec2api.api.instance._format_instance')
|
||||
def test_run_instances_rollback(self, format_instance,
|
||||
delete_network_interface):
|
||||
def test_run_instances_rollback(self, delete_network_interface):
|
||||
self.db_api.get_item_by_id.side_effect = (
|
||||
fakes.get_db_api_get_item_by_id(
|
||||
{fakes.ID_EC2_SUBNET_1: fakes.DB_SUBNET_1,
|
||||
@@ -268,11 +280,6 @@ 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.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.db_api.add_item.return_value = fakes.DB_INSTANCE_1
|
||||
self.utils_generate_uid.return_value = fakes.ID_EC2_RESERVATION_1
|
||||
@@ -282,9 +289,14 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
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()
|
||||
self.nova_servers.create.return_value = (
|
||||
fakes.OSInstance(fakes.ID_OS_INSTANCE_1, {'id': 'fakeFlavorId'},
|
||||
addresses={
|
||||
fakes.ID_EC2_SUBNET_1: [
|
||||
{'addr': fakes.IP_NETWORK_INTERFACE_1,
|
||||
'version': 4,
|
||||
'OS-EXT-IPS:type': 'fixed'}]}))
|
||||
self.db_api.update_item.side_effect = Exception()
|
||||
|
||||
def do_check(params, new_port=True, delete_on_termination=None):
|
||||
params.update({'ImageId': 'ami-00000001',
|
||||
@@ -307,8 +319,6 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
fakes.ID_OS_INSTANCE_1)
|
||||
self.db_api.delete_item.assert_called_once_with(
|
||||
mock.ANY, fakes.ID_EC2_INSTANCE_1)
|
||||
self.db_api.update_item.assert_any_call(
|
||||
mock.ANY, fakes.DB_NETWORK_INTERFACE_1)
|
||||
|
||||
delete_network_interface.reset_mock()
|
||||
self.neutron.reset_mock()
|
||||
@@ -466,45 +476,47 @@ class InstanceTestCase(base.ApiTestCase):
|
||||
updated_ports=[self.DB_DETACHED_ENIS[1]],
|
||||
deleted_ports=[])
|
||||
|
||||
def test_describe_instances(self):
|
||||
@mock.patch('ec2api.api.instance.security_group_api.'
|
||||
'_format_security_groups_ids_names')
|
||||
def test_describe_instances(self, format_security_groups_ids_names):
|
||||
"""Describe 2 instances, one of which is vpc instance."""
|
||||
self.ec2.describe_instances.return_value = (
|
||||
{'reservationSet': [fakes.EC2OS_RESERVATION_1,
|
||||
fakes.EC2OS_RESERVATION_2],
|
||||
'fakeKey': 'fakeValue'})
|
||||
self.ec2_inst_id_to_uuid.side_effect = [fakes.ID_OS_INSTANCE_1,
|
||||
fakes.ID_OS_INSTANCE_2]
|
||||
self.neutron.list_ports.return_value = {'ports': [fakes.OS_PORT_2]}
|
||||
self.db_api.get_items.side_effect = (
|
||||
lambda _, kind: [fakes.DB_NETWORK_INTERFACE_1,
|
||||
fakes.DB_NETWORK_INTERFACE_2]
|
||||
if kind == 'eni' else
|
||||
[fakes.DB_ADDRESS_1, fakes.DB_ADDRESS_2]
|
||||
if kind == 'eipalloc' else [])
|
||||
if kind == 'eipalloc' else
|
||||
[fakes.DB_INSTANCE_1, fakes.DB_INSTANCE_2]
|
||||
if kind == 'i' else [])
|
||||
self.neutron.list_floatingips.return_value = (
|
||||
{'floatingips': [fakes.OS_FLOATING_IP_1,
|
||||
fakes.OS_FLOATING_IP_2]})
|
||||
self.nova_servers.list.return_value = [fakes.OS_INSTANCE_1,
|
||||
fakes.OS_INSTANCE_2]
|
||||
instance_get_by_uuid = fakes.get_db_api_get_item_by_id({
|
||||
fakes.ID_OS_INSTANCE_1: fakes.NOVADB_INSTANCE_1,
|
||||
fakes.ID_OS_INSTANCE_2: fakes.NOVADB_INSTANCE_2})
|
||||
self.novadb.instance_get_by_uuid.side_effect = (
|
||||
lambda context, item_id:
|
||||
instance_get_by_uuid(context, None, item_id))
|
||||
fake_flavor = self.fake_flavor_class('fake_flavor')
|
||||
self.nova_flavors.get.return_value = fake_flavor
|
||||
self.glance_id_to_ec2_id.return_value = None
|
||||
format_security_groups_ids_names.return_value = {}
|
||||
self.novadb.block_device_mapping_get_all_by_instance.return_value = []
|
||||
|
||||
resp = self.execute('DescribeInstances', {})
|
||||
|
||||
self.assertEqual(200, resp['status'])
|
||||
resp.pop('status')
|
||||
self.ec2.describe_instances.assert_called_once_with(
|
||||
instance_id=None, filter=None)
|
||||
self.assertThat(resp, matchers.DictMatches(
|
||||
{'reservationSet': [fakes.EC2_RESERVATION_1,
|
||||
fakes.EC2_RESERVATION_2],
|
||||
'fakeKey': 'fakeValue'}))
|
||||
self.ec2_inst_id_to_uuid.assert_any_call(
|
||||
mock.ANY,
|
||||
fakes.ID_EC2_INSTANCE_1)
|
||||
self.ec2_inst_id_to_uuid.assert_any_call(
|
||||
mock.ANY,
|
||||
fakes.ID_EC2_INSTANCE_2)
|
||||
self._assert_list_ports_is_called_with_filter(
|
||||
[fakes.ID_OS_INSTANCE_1, fakes.ID_OS_INSTANCE_2])
|
||||
fakes.EC2_RESERVATION_2]},
|
||||
orderless_lists=True))
|
||||
|
||||
def test_describe_instances_mutliple_networks(self):
|
||||
# TODO(ft): restore test after finish extraction of Nova EC2 API
|
||||
def _test_describe_instances_mutliple_networks(self):
|
||||
"""Describe 2 instances with various combinations of network."""
|
||||
self._build_multiple_data_model()
|
||||
ips_instance = [fakes.IP_FIRST_SUBNET_1, fakes.IP_FIRST_SUBNET_2]
|
||||
|
||||
Reference in New Issue
Block a user