Files
cinder/cinder/compute/nova.py
T
Sean Dague 95550a5095 Change nova_catalog_admin_info to default to publicURL
The only services in OpenStack that have dedicated admin REST APIs
that actually enforce different permissions at the API level are
keystone and swift. Assuming there is a dedicated admin endpoint is an
artifical construct.

Change the default for the admin_info to be the normal public url. For
sites that really want 2 different values here, they can have it, but
default to the simpler thing.

Change-Id: I92efc4639d37cb2736bae7e40707a853f1e72be6
2017-02-14 09:49:29 -05:00

277 lines
11 KiB
Python

# Copyright 2013 IBM Corp.
#
# 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.
"""
Handles all requests to Nova.
"""
from keystoneauth1 import identity
from keystoneauth1 import session as ka_session
from novaclient import api_versions
from novaclient import client as nova_client
from novaclient import exceptions as nova_exceptions
from oslo_config import cfg
from oslo_log import log as logging
from requests import exceptions as request_exceptions
from cinder import context as ctx
from cinder.db import base
from cinder import exception
nova_opts = [
cfg.StrOpt('nova_catalog_info',
default='compute:Compute Service:publicURL',
help='Match this value when searching for nova in the '
'service catalog. Format is: separated values of '
'the form: '
'<service_type>:<service_name>:<endpoint_type>'),
cfg.StrOpt('nova_catalog_admin_info',
default='compute:Compute Service:publicURL',
help='Same as nova_catalog_info, but for admin endpoint.'),
cfg.StrOpt('nova_endpoint_template',
help='Override service catalog lookup with template for nova '
'endpoint e.g. http://localhost:8774/v2/%(project_id)s'),
cfg.StrOpt('nova_endpoint_admin_template',
help='Same as nova_endpoint_template, but for admin endpoint.'),
cfg.StrOpt('os_region_name',
help='Region name of this node'),
cfg.StrOpt('nova_ca_certificates_file',
help='Location of ca certificates file to use for nova client '
'requests.'),
cfg.BoolOpt('nova_api_insecure',
default=False,
help='Allow to perform insecure SSL requests to nova'),
]
CONF = cfg.CONF
CONF.register_opts(nova_opts)
LOG = logging.getLogger(__name__)
# TODO(e0ne): Make Nova version configurable in Mitaka.
NOVA_API_VERSION = "2.1"
nova_extensions = [ext for ext in
nova_client.discover_extensions(NOVA_API_VERSION)
if ext.name in ("assisted_volume_snapshots",
"list_extensions")]
# TODO(dmllr): This is a copy of the ServiceCatalog class in python-novaclient
# that got removed in 7.0.0 release. This needs to be cleaned up once we depend
# on newer novaclient.
class _NovaClientServiceCatalog(object):
"""Helper methods for dealing with a Keystone Service Catalog."""
def __init__(self, resource_dict):
self.catalog = resource_dict
def url_for(self, attr=None, filter_value=None,
service_type=None, endpoint_type='publicURL',
service_name=None, volume_service_name=None):
"""Fetch public URL for a particular endpoint.
If none given, return the first.
See tests for sample service catalog.
"""
matching_endpoints = []
if 'endpoints' in self.catalog:
# We have a bastardized service catalog. Treat it special. :/
for endpoint in self.catalog['endpoints']:
if not filter_value or endpoint[attr] == filter_value:
# Ignore 1.0 compute endpoints
if endpoint.get("type") == 'compute' and \
endpoint.get('versionId') in (None, '1.1', '2'):
matching_endpoints.append(endpoint)
if not matching_endpoints:
raise nova_exceptions.EndpointNotFound()
# We don't always get a service catalog back ...
if 'serviceCatalog' not in self.catalog['access']:
return None
# Full catalog ...
catalog = self.catalog['access']['serviceCatalog']
for service in catalog:
if service.get("type") != service_type:
continue
if (service_name and service_type == 'compute' and
service.get('name') != service_name):
continue
if (volume_service_name and service_type == 'volume' and
service.get('name') != volume_service_name):
continue
endpoints = service['endpoints']
for endpoint in endpoints:
# Ignore 1.0 compute endpoints
if (service.get("type") == 'compute' and
endpoint.get('versionId', '2') not in ('1.1', '2')):
continue
if (not filter_value or
endpoint.get(attr).lower() == filter_value.lower()):
endpoint["serviceName"] = service.get("name")
matching_endpoints.append(endpoint)
if not matching_endpoints:
raise nova_exceptions.EndpointNotFound()
elif len(matching_endpoints) > 1:
raise nova_exceptions.AmbiguousEndpoints(
endpoints=matching_endpoints)
else:
return matching_endpoints[0][endpoint_type]
def novaclient(context, admin_endpoint=False, privileged_user=False,
timeout=None):
"""Returns a Nova client
@param admin_endpoint: If True, use the admin endpoint template from
configuration ('nova_endpoint_admin_template' and 'nova_catalog_info')
@param privileged_user: If True, use the account from configuration
(requires 'os_privileged_user_name', 'os_privileged_user_password' and
'os_privileged_user_tenant' to be set)
@param timeout: Number of seconds to wait for an answer before raising a
Timeout exception (None to disable)
"""
# FIXME: the novaclient ServiceCatalog object is mis-named.
# It actually contains the entire access blob.
# Only needed parts of the service catalog are passed in, see
# nova/context.py.
compat_catalog = {
'access': {'serviceCatalog': context.service_catalog or []}
}
sc = _NovaClientServiceCatalog(compat_catalog)
nova_endpoint_template = CONF.nova_endpoint_template
nova_catalog_info = CONF.nova_catalog_info
if admin_endpoint:
nova_endpoint_template = CONF.nova_endpoint_admin_template
nova_catalog_info = CONF.nova_catalog_admin_info
service_type, service_name, endpoint_type = nova_catalog_info.split(':')
# Extract the region if set in configuration
if CONF.os_region_name:
region_filter = {'attr': 'region', 'filter_value': CONF.os_region_name}
else:
region_filter = {}
if privileged_user and CONF.os_privileged_user_name:
context = ctx.RequestContext(
CONF.os_privileged_user_name, None,
auth_token=CONF.os_privileged_user_password,
project_name=CONF.os_privileged_user_tenant,
service_catalog=context.service_catalog)
# When privileged_user is used, it needs to authenticate to Keystone
# before querying Nova, so we set auth_url to the identity service
# endpoint.
if CONF.os_privileged_user_auth_url:
url = CONF.os_privileged_user_auth_url
else:
# We then pass region_name, endpoint_type, etc. to the
# Client() constructor so that the final endpoint is
# chosen correctly.
url = sc.url_for(service_type='identity',
endpoint_type=endpoint_type,
**region_filter)
LOG.debug('Creating a Nova client using "%s" user',
CONF.os_privileged_user_name)
else:
if nova_endpoint_template:
url = nova_endpoint_template % context.to_dict()
else:
url = sc.url_for(service_type=service_type,
service_name=service_name,
endpoint_type=endpoint_type,
**region_filter)
LOG.debug('Nova client connection created using URL: %s', url)
# Now that we have the correct auth_url, username, password, project_name
# and domain information, i.e. project_domain_id and user_domain_id (if
# using Identity v3 API) let's build a Keystone session.
auth = identity.Password(auth_url=url,
username=context.user_id,
password=context.auth_token,
project_name=context.project_name,
project_domain_id=context.project_domain,
user_domain_id=context.user_domain)
keystone_session = ka_session.Session(auth=auth)
c = nova_client.Client(api_versions.APIVersion(NOVA_API_VERSION),
session=keystone_session,
insecure=CONF.nova_api_insecure,
timeout=timeout,
region_name=CONF.os_region_name,
endpoint_type=endpoint_type,
cacert=CONF.nova_ca_certificates_file,
extensions=nova_extensions)
if not privileged_user:
# noauth extracts user_id:project_id from auth_token
c.client.auth_token = (context.auth_token or '%s:%s'
% (context.user_id, context.project_id))
c.client.management_url = url
return c
class API(base.Base):
"""API for interacting with novaclient."""
def has_extension(self, context, extension, timeout=None):
try:
nova_exts = novaclient(context).list_extensions.show_all()
except request_exceptions.Timeout:
raise exception.APITimeout(service='Nova')
return extension in [e.name for e in nova_exts]
def update_server_volume(self, context, server_id, attachment_id,
new_volume_id):
nova = novaclient(context, admin_endpoint=True, privileged_user=True)
nova.volumes.update_server_volume(server_id,
attachment_id,
new_volume_id)
def create_volume_snapshot(self, context, volume_id, create_info):
nova = novaclient(context, admin_endpoint=True, privileged_user=True)
# pylint: disable=E1101
nova.assisted_volume_snapshots.create(
volume_id,
create_info=create_info)
def delete_volume_snapshot(self, context, snapshot_id, delete_info):
nova = novaclient(context, admin_endpoint=True, privileged_user=True)
# pylint: disable=E1101
nova.assisted_volume_snapshots.delete(
snapshot_id,
delete_info=delete_info)
def get_server(self, context, server_id, privileged_user=False,
timeout=None):
try:
return novaclient(context, privileged_user=privileged_user,
timeout=timeout).servers.get(server_id)
except nova_exceptions.NotFound:
raise exception.ServerNotFound(uuid=server_id)
except request_exceptions.Timeout:
raise exception.APITimeout(service='Nova')