ec2-api/ec2api/clients.py
Feodor Tersin 092cc7d494 Switch to Nova v2.32 API
At v2.33 new bdm field 'tag' was introduced. This field is automatically
created by snapshot operation for volume backed instance (ebs). The
result of the operation - bdm - is stored in metadata of the snapshot
image. When ec2api user requires to run an instance with this image
changing some attributes of the attached volume (e.g.
delete_on_termination), this bdm is merged with user defined parameters
and sent to Nova. As the result the new 'tag' field is sent to Nova
since v2.32.

However the used API version is v2.10, which does not support 'tag'
field. This did not lead to faults (until I56348dc2) because Nova
verified the first bdm only, but the first bdm for ebs instances was a
bdm for the image. With the noted fix this no more works.

This patch swithes ec2api to use v2.32 to get 'tag' field supported.

Change-Id: I68329bbffeeff5d460f3fca1a212ba20b35fc284
(cherry picked from commit 480dc02de0)
(cherry picked from commit 4deceadc27)
2017-01-19 15:03:26 +00:00

232 lines
8.5 KiB
Python

# 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.
from cinderclient import client as cinderclient
from glanceclient import client as glanceclient
from keystoneclient.auth.identity.generic import password as keystone_auth
from keystoneclient import client as keystoneclient
from keystoneclient import session as keystone_session
from neutronclient.v2_0 import client as neutronclient
from novaclient import api_versions as nova_api_versions
from novaclient import client as novaclient
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging as messaging
from ec2api.i18n import _, _LI, _LW
logger = logging.getLogger(__name__)
ec2_opts = [
cfg.BoolOpt('ssl_insecure',
default=False,
help="Verify HTTPS connections."),
cfg.StrOpt('ssl_ca_file',
help="CA certificate file to use to verify "
"connecting clients"),
cfg.StrOpt('nova_service_type',
default='compute',
help='Service type of Compute API, registered in Keystone '
'catalog. Should be v2.1 with microversion support. '
'If it is obsolete v2, a lot of useful EC2 compliant '
'instance properties will be unavailable.'),
cfg.StrOpt('cinder_service_type',
default='volume',
help='Service type of Volume API, registered in Keystone '
'catalog.'),
# TODO(andrey-mp): keystone v3 allows to pass domain_name
# or domain_id to auth. This code should support this feature.
cfg.StrOpt('admin_user',
help=_("Admin user to access specific cloud resourses")),
cfg.StrOpt('admin_password',
help=_("Admin password"),
secret=True),
cfg.StrOpt('admin_tenant_name',
help=_("Admin tenant name")),
]
CONF = cfg.CONF
CONF.register_opts(ec2_opts)
# Nova API version with microversions support
REQUIRED_NOVA_API_VERSION = '2.1'
REQUIRED_NOVA_API_VERSION_ID = 'v%s' % REQUIRED_NOVA_API_VERSION
LEGACY_NOVA_API_VERSION = '2'
# Nova API's 2.3 microversion provides additional EC2 compliant instance
# properties
# Nova API's 2.10 microversion provides admin access to users keypairs,
# which allows metadata service to expose openssh part of an instance key
# Nova API's 2.32 microversion allows 'tag' field of bdm v2, which may be
# contained in image bdms, defined by users or autocreated with instance
# snapshot
REQUIRED_NOVA_API_MICROVERSION = '2.32'
_nova_api_version = None
def nova(context):
global _nova_api_version
if not _nova_api_version:
_nova_api_version = _get_nova_api_version(context)
clnt = novaclient.Client(_nova_api_version,
session=context.session,
service_type=CONF.nova_service_type)
# NOTE(ft): workaround for LP #1494116 bug
if not hasattr(clnt.client, 'last_request_id'):
setattr(clnt.client, 'last_request_id', None)
return clnt
def neutron(context):
return neutronclient.Client(session=context.session,
service_type='network')
def glance(context):
return glanceclient.Client('1', service_type='image',
session=context.session)
def cinder(context):
url = context.session.get_endpoint(service_type=CONF.cinder_service_type)
# TODO(jamielennox): This should be using proper version discovery from
# the cinder service rather than just inspecting the URL for certain string
# values.
version = cinderclient.get_volume_api_from_url(url)
return cinderclient.Client(version, session=context.session,
service_type=CONF.cinder_service_type)
def keystone(context):
auth_url = context.session.get_endpoint(service_type='identity')
return keystoneclient.Client(auth_url=auth_url,
session=context.session)
def nova_cert(context):
_cert_api = _rpcapi_CertAPI(context)
return _cert_api
def _get_nova_api_version(context):
client = novaclient.Client(REQUIRED_NOVA_API_VERSION,
session=context.session,
service_type=CONF.nova_service_type)
required = nova_api_versions.APIVersion(REQUIRED_NOVA_API_MICROVERSION)
current = client.versions.get_current()
if not current:
logger.warning(
_LW('Could not check Nova API version because no version '
'was found in Nova version list for url %(url)s of service '
'type "%(service_type)s". '
'Use v%(required_api_version)s Nova API.'),
{'url': client.client.get_endpoint(),
'service_type': CONF.nova_service_type,
'required_api_version': REQUIRED_NOVA_API_MICROVERSION})
return REQUIRED_NOVA_API_MICROVERSION
if current.id != REQUIRED_NOVA_API_VERSION_ID:
logger.warning(
_LW('Specified "%s" Nova service type does not support v2.1 API. '
'A lot of useful EC2 compliant instance properties '
'will be unavailable.'),
CONF.nova_service_type)
return LEGACY_NOVA_API_VERSION
if (nova_api_versions.APIVersion(current.version) < required):
logger.warning(
_LW('Nova support v%(nova_api_version)s, '
'but v%(required_api_version)s is required. '
'A lot of useful EC2 compliant instance properties '
'will be unavailable.'),
{'nova_api_version': current.version,
'required_api_version': REQUIRED_NOVA_API_MICROVERSION})
return current.version
logger.info(_LI('Provided Nova API version is v%(nova_api_version)s, '
'used one is v%(required_api_version)s'),
{'nova_api_version': current.version,
'required_api_version': (
REQUIRED_NOVA_API_MICROVERSION)})
return REQUIRED_NOVA_API_MICROVERSION
class _rpcapi_CertAPI(object):
'''Client side of the cert rpc API.'''
def __init__(self, context):
super(_rpcapi_CertAPI, self).__init__()
target = messaging.Target(topic=CONF.cert_topic, version='2.0')
self.client = _rpc_get_client(target)
self.context = context
def decrypt_text(self, text):
cctxt = self.client.prepare()
return cctxt.call(self.context, 'decrypt_text',
project_id=self.context.project_id,
text=text)
_rpc_TRANSPORT = None
def _rpc_init(conf):
global _rpc_TRANSPORT
# NOTE(ft): set control_exchange parameter to use Nova cert topic
messaging.set_transport_defaults('nova')
_rpc_TRANSPORT = messaging.get_transport(conf)
def _rpc_get_client(target):
if not _rpc_TRANSPORT:
_rpc_init(CONF)
assert _rpc_TRANSPORT is not None
serializer = _rpc_RequestContextSerializer()
return messaging.RPCClient(_rpc_TRANSPORT,
target,
serializer=serializer)
class _rpc_RequestContextSerializer(messaging.NoOpSerializer):
def serialize_context(self, context):
return context.to_dict()
_admin_session = None
def get_os_admin_session():
"""Create a context to interact with OpenStack as an administrator."""
# NOTE(ft): this is a singletone because keystone's session looks thread
# safe for both regular and token renewal requests
global _admin_session
if not _admin_session:
auth = keystone_auth.Password(
username=CONF.admin_user,
password=CONF.admin_password,
project_name=CONF.admin_tenant_name,
tenant_name=CONF.admin_tenant_name,
auth_url=CONF.keystone_url,
)
params = {'auth': auth}
update_request_params_with_ssl(params)
_admin_session = keystone_session.Session(**params)
return _admin_session
def update_request_params_with_ssl(params):
verify = CONF.ssl_ca_file or not CONF.ssl_insecure
if verify is not True:
params['verify'] = verify