Get partial compatibility with pre Kilo Nova release
Change-Id: I8f1570c3c3fc297f0ffbfc7cacb84d8c53a0e453
This commit is contained in:
parent
8282371da4
commit
070fe23aaf
29
README.rst
29
README.rst
|
@ -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
|
||||
==========
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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')
|
Loading…
Reference in New Issue