092cc7d494
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 commit480dc02de0
) (cherry picked from commit4deceadc27
)
232 lines
8.5 KiB
Python
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
|