Get partial compatibility with pre Kilo Nova release

Change-Id: I8f1570c3c3fc297f0ffbfc7cacb84d8c53a0e453
This commit is contained in:
Feodor Tersin 2015-03-19 19:04:35 +03:00
parent 8282371da4
commit 070fe23aaf
4 changed files with 207 additions and 14 deletions

View File

@ -189,6 +189,35 @@ Additions to the legacy nova's EC2 API include:
2. Filtering
3. Tags
Legacy OpenStack release notice
===============================
EC2 API supports Havana, Icehouse, Juno with additional limitations:
Instance related:
- rootDeviceName Instance property
- kernelId Instance property
- ramdiskId Instance property
- userData Instance property
- hostName Instance property
- reservationId Reservation property (ec2api own ids are generated for
instances launched not by ec2api)
- launchIndex Instance property (0 for instances launched not by ec2api)
Volume related:
- deleteOnTermination property
Network interface related:
- deleteOnTermination (False value can be assigned but doesn't supported)
All theese properties can be specified in RunInstance command though, they are
not reported in describe operations.
EC2 API supports Nova client (>=2.16.0) with no microversion support.
Additional limitations are the same, except network interfaces's
deleteOnTermination.
References
==========

View File

@ -15,12 +15,13 @@
from keystoneclient.v2_0 import client as kc
from novaclient import client as novaclient
from novaclient import exceptions as nova_exception
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging as messaging
from ec2api import context as ec2_context
from ec2api.i18n import _
from ec2api.i18n import _, _LW
logger = logging.getLogger(__name__)
@ -44,18 +45,46 @@ except ImportError:
logger.info(_('glanceclient not available'))
# Nova API's 2.3 microversion provides additional EC2 compliant instance
# properties
_novaclient_vertion = '2.3'
_nova_service_type = 'computev21'
def nova(context):
args = {
'project_id': context.project_id,
'auth_url': CONF.keystone_url,
'auth_token': context.auth_token,
# NOTE(ft): These parameters are not used for authentification,
# but are required by novaclient < v2.18 which may be installed in
# Icehouse deployment
'username': None,
'api_key': None,
'auth_token': context.auth_token,
'bypass_url': _url_for(context, service_type='computev21'),
'project_id': None,
}
# Nova API's 2.3 microversion provides additional EC2 complient instance
# attributes
return novaclient.Client(2.3, **args)
global _novaclient_vertion, _nova_service_type
bypass_url = _url_for(context, service_type=_nova_service_type)
if not bypass_url and _nova_service_type == 'computev21':
# NOTE(ft): partial compatibility with pre Kilo OS releases:
# if computev21 isn't provided by Nova, use compute instead
logger.warning(_LW("Nova server doesn't support v2.1, use v2 instead. "
"A lot of useful EC2 compliant instance properties "
"will be unavailable."))
_nova_service_type = 'compute'
return nova(context)
try:
return novaclient.Client(_novaclient_vertion, bypass_url=bypass_url,
**args)
except nova_exception.UnsupportedVersion:
if _novaclient_vertion == '2':
raise
# NOTE(ft): partial compatibility with Nova client w/o microversion
# support
logger.warning(_LW("Nova client doesn't support v2.3, use v2 instead. "
"A lot of useful EC2 compliant instance properties "
"will be unavailable."))
_novaclient_vertion = '2'
return nova(context)
def neutron(context):
@ -123,16 +152,16 @@ def _url_for(context, **kwargs):
service_catalog = context.service_catalog
if not service_catalog:
catalog = keystone(context).service_catalog.catalog
service_catalog = catalog["serviceCatalog"]
service_catalog = catalog['serviceCatalog']
context.service_catalog = service_catalog
service_type = kwargs["service_type"]
service_type = kwargs['service_type']
for service in service_catalog:
if service["type"] != service_type:
if service['type'] != service_type:
continue
for endpoint in service["endpoints"]:
if "publicURL" in endpoint:
return endpoint["publicURL"]
for endpoint in service['endpoints']:
if 'publicURL' in endpoint:
return endpoint['publicURL']
else:
return None

View File

@ -1266,7 +1266,7 @@ def _cloud_format_instance_bdm(context, os_instance, result,
'status': _cloud_get_volume_attach_status(os_volume)}
volume_attached = next((va for va in volumes_attached
if va['id'] == os_volume.id), None)
if volume_attached:
if volume_attached and 'delete_on_termination' in volume_attached:
ebs['deleteOnTermination'] = (
volume_attached['delete_on_termination'])
mapping.append({'deviceName': device_name,

View File

@ -0,0 +1,135 @@
# Copyright 2014
# The Cloudscaling Group, Inc.
#
# 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.
import fixtures
import mock
from novaclient import exceptions as nova_exception
from oslo_config import fixture as config_fixture
from oslotest import base as test_base
from ec2api.api import clients
class ClientsTestCase(test_base.BaseTestCase):
def setUp(self):
super(ClientsTestCase, self).setUp()
conf = self.useFixture(config_fixture.Config())
conf.config(keystone_url='keystone_url')
@mock.patch('novaclient.client.Client')
def test_nova(self, nova):
reload(clients)
# test normal flow
context = mock.Mock(
auth_token='fake_token',
service_catalog=[{'type': 'computev21',
'endpoints': [{'publicURL': 'novav21_url'}]}])
with fixtures.LoggerFixture() as logs:
res = clients.nova(context)
self.assertEqual(nova.return_value, res)
nova.assert_called_with(
'2.3', bypass_url='novav21_url',
auth_url='keystone_url', auth_token='fake_token',
username=None, api_key=None, project_id=None)
self.assertEqual(0, len(logs.output))
# test switching to v2 client
nova.side_effect = [nova_exception.UnsupportedVersion(), 'v2_client']
with fixtures.LoggerFixture() as logs:
res = clients.nova(context)
self.assertEqual('v2_client', res)
nova.assert_called_with(
'2', bypass_url='novav21_url',
auth_url='keystone_url', auth_token='fake_token',
username=None, api_key=None, project_id=None)
self.assertNotEqual(0, len(logs.output))
# test raising of an exception if v2 client is not supported as well
nova.side_effect = nova_exception.UnsupportedVersion()
self.assertRaises(nova_exception.UnsupportedVersion,
clients.nova, context)
nova.side_effect = None
reload(clients)
# test switching to 'compute' service type
context.service_catalog = [{'type': 'compute',
'endpoints': [{'publicURL': 'nova_url'}]}]
with fixtures.LoggerFixture() as logs:
res = clients.nova(context)
nova.assert_called_with(
'2.3', bypass_url='nova_url',
auth_url='keystone_url', auth_token='fake_token',
username=None, api_key=None, project_id=None)
self.assertNotEqual(0, len(logs.output))
# test behavior if 'compute' service type is not found as well
context.service_catalog = [{'type': 'fake'}]
clients.nova(context)
nova.assert_called_with(
'2.3', bypass_url=None,
auth_url='keystone_url', auth_token='fake_token',
username=None, api_key=None, project_id=None)
@mock.patch('neutronclient.v2_0.client.Client')
def test_neutron(self, neutron):
context = mock.Mock(
auth_token='fake_token',
service_catalog=[{'type': 'network',
'endpoints': [{'publicURL': 'neutron_url'}]}])
res = clients.neutron(context)
self.assertEqual(neutron.return_value, res)
neutron.assert_called_with(
auth_url='keystone_url', service_type='network',
token='fake_token', endpoint_url='neutron_url')
@mock.patch('glanceclient.client.Client')
def test_glance(self, glance):
context = mock.Mock(
auth_token='fake_token',
service_catalog=[{'type': 'image',
'endpoints': [{'publicURL': 'glance_url'}]}])
res = clients.glance(context)
self.assertEqual(glance.return_value, res)
glance.assert_called_with(
'1', auth_url='keystone_url', service_type='image',
token='fake_token', endpoint='glance_url')
@mock.patch('cinderclient.client.Client')
def test_cinder(self, cinder):
context = mock.Mock(
auth_token='fake_token',
service_catalog=[{'type': 'volume',
'endpoints': [{'publicURL': 'cinder_url'}]}])
res = clients.cinder(context)
self.assertEqual(cinder.return_value, res)
cinder.assert_called_with(
'1', auth_url='keystone_url', service_type='volume',
username=None, api_key=None)
self.assertEqual('fake_token', res.client.auth_token)
self.assertEqual('cinder_url', res.client.management_url)
@mock.patch('keystoneclient.v2_0.client.Client')
def test_keystone(self, keystone):
context = mock.Mock(
auth_token='fake_token',
project_id='fake_project')
res = clients.keystone(context)
self.assertEqual(keystone.return_value, res)
keystone.assert_called_with(
auth_url='keystone_url', token='fake_token',
tenant_id='fake_project')