1# Copyright 2013 IBM Corp. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14 15""" 16Handles all requests to Nova. 17""" 18 19from keystoneauth1 import identity 20from keystoneauth1 import loading as ks_loading 21from novaclient import api_versions 22from novaclient import client as nova_client 23from novaclient import exceptions as nova_exceptions 24from oslo_config import cfg 25from oslo_log import log as logging 26from requests import exceptions as request_exceptions 27 28from cinder.db import base 29from cinder import exception 30from cinder.message import api as message_api 31from cinder.message import message_field 32from cinder import service_auth 33 34nova_opts = [ 35 cfg.StrOpt('region_name', 36 help='Name of nova region to use. Useful if keystone manages ' 37 'more than one region.', 38 deprecated_name="os_region_name", 39 deprecated_group="DEFAULT"), 40 cfg.StrOpt('interface', 41 default='public', 42 choices=['public', 'admin', 'internal'], 43 help='Type of the nova endpoint to use. This endpoint will ' 44 'be looked up in the keystone catalog and should be ' 45 'one of public, internal or admin.'), 46 cfg.StrOpt('token_auth_url', 47 help='The authentication URL for the nova connection when ' 48 'using the current user''s token'), 49] 50 51 52NOVA_GROUP = 'nova' 53CONF = cfg.CONF 54 55nova_session_opts = ks_loading.get_session_conf_options() 56nova_auth_opts = ks_loading.get_auth_common_conf_options() 57 58CONF.register_opts(nova_opts, group=NOVA_GROUP) 59CONF.register_opts(nova_session_opts, group=NOVA_GROUP) 60CONF.register_opts(nova_auth_opts, group=NOVA_GROUP) 61 62LOG = logging.getLogger(__name__) 63 64NOVA_API_VERSION = "2.1" 65 66nova_extensions = [ext for ext in 67 nova_client.discover_extensions(NOVA_API_VERSION) 68 if ext.name in ("assisted_volume_snapshots", 69 "list_extensions", 70 "server_external_events")] 71 72 73def _get_identity_endpoint_from_sc(context): 74 # Search for the identity endpoint in the service catalog 75 for service in context.service_catalog: 76 if service.get('type') != 'identity': 77 continue 78 for endpoint in service['endpoints']: 79 if (not CONF[NOVA_GROUP].region_name or 80 endpoint.get('region') == CONF[NOVA_GROUP].region_name): 81 return endpoint.get(CONF[NOVA_GROUP].interface + 'URL') 82 raise nova_exceptions.EndpointNotFound() 83 84 85def novaclient(context, privileged_user=False, timeout=None, api_version=None): 86 """Returns a Nova client 87 88 @param privileged_user: 89 If True, use the account from configuration 90 (requires 'auth_type' and the other usual Keystone authentication 91 options to be set in the [nova] section) 92 @param timeout: 93 Number of seconds to wait for an answer before raising a 94 Timeout exception (None to disable) 95 @param api_version: 96 api version of nova 97 """ 98 99 if privileged_user and CONF[NOVA_GROUP].auth_type: 100 LOG.debug('Creating Keystone auth plugin from conf') 101 n_auth = ks_loading.load_auth_from_conf_options(CONF, NOVA_GROUP) 102 else: 103 if CONF[NOVA_GROUP].token_auth_url: 104 url = CONF[NOVA_GROUP].token_auth_url 105 else: 106 url = _get_identity_endpoint_from_sc(context) 107 LOG.debug('Creating Keystone token plugin using URL: %s', url) 108 n_auth = identity.Token(auth_url=url, 109 token=context.auth_token, 110 project_name=context.project_name, 111 project_domain_id=context.project_domain_id) 112 113 if CONF.auth_strategy == 'keystone': 114 n_auth = service_auth.get_auth_plugin(context, auth=n_auth) 115 116 keystone_session = ks_loading.load_session_from_conf_options( 117 CONF, 118 NOVA_GROUP, 119 auth=n_auth) 120 121 c = nova_client.Client( 122 api_versions.APIVersion(api_version or NOVA_API_VERSION), 123 session=keystone_session, 124 insecure=CONF[NOVA_GROUP].insecure, 125 timeout=timeout, 126 region_name=CONF[NOVA_GROUP].region_name, 127 endpoint_type=CONF[NOVA_GROUP].interface, 128 cacert=CONF[NOVA_GROUP].cafile, 129 global_request_id=context.global_id, 130 extensions=nova_extensions) 131 132 return c 133 134 135class API(base.Base): 136 """API for interacting with novaclient.""" 137 138 def __init__(self): 139 self.message_api = message_api.API() 140 141 def _get_volume_extended_event(self, server_id, volume_id): 142 return {'name': 'volume-extended', 143 'server_uuid': server_id, 144 'tag': volume_id} 145 146 def _send_events(self, context, events, api_version=None): 147 nova = novaclient(context, privileged_user=True, 148 api_version=api_version) 149 try: 150 response = nova.server_external_events.create(events) 151 except nova_exceptions.NotFound: 152 LOG.warning('Nova returned NotFound for events: %s.', events) 153 return False 154 except Exception: 155 LOG.exception('Failed to notify nova on events: %s.', events) 156 return False 157 else: 158 if not isinstance(response, list): 159 LOG.error('Error response returned from nova: %s.', response) 160 return False 161 response_error = False 162 for event in response: 163 code = event.get('code') 164 if code is None: 165 response_error = True 166 continue 167 if code != 200: 168 LOG.warning( 169 'Nova event: %s returned with failed status.', event) 170 else: 171 LOG.info('Nova event response: %s.', event) 172 if response_error: 173 LOG.error('Error response returned from nova: %s.', response) 174 return False 175 return True 176 177 def has_extension(self, context, extension, timeout=None): 178 try: 179 nova_exts = novaclient(context).list_extensions.show_all() 180 except request_exceptions.Timeout: 181 raise exception.APITimeout(service='Nova') 182 return extension in [e.name for e in nova_exts] 183 184 def update_server_volume(self, context, server_id, src_volid, 185 new_volume_id): 186 nova = novaclient(context, privileged_user=True) 187 nova.volumes.update_server_volume(server_id, 188 src_volid, 189 new_volume_id) 190 191 def create_volume_snapshot(self, context, volume_id, create_info): 192 nova = novaclient(context, privileged_user=True) 193 194 # pylint: disable=E1101 195 nova.assisted_volume_snapshots.create( 196 volume_id, 197 create_info=create_info) 198 199 def delete_volume_snapshot(self, context, snapshot_id, delete_info): 200 nova = novaclient(context, privileged_user=True) 201 202 # pylint: disable=E1101 203 nova.assisted_volume_snapshots.delete( 204 snapshot_id, 205 delete_info=delete_info) 206 207 def get_server(self, context, server_id, privileged_user=False, 208 timeout=None): 209 try: 210 return novaclient(context, privileged_user=privileged_user, 211 timeout=timeout).servers.get(server_id) 212 except nova_exceptions.NotFound: 213 raise exception.ServerNotFound(uuid=server_id) 214 except request_exceptions.Timeout: 215 raise exception.APITimeout(service='Nova') 216 217 def extend_volume(self, context, server_ids, volume_id): 218 api_version = '2.51' 219 events = [self._get_volume_extended_event(server_id, volume_id) 220 for server_id in server_ids] 221 result = self._send_events(context, events, api_version=api_version) 222 if not result: 223 self.message_api.create( 224 context, 225 message_field.Action.EXTEND_VOLUME, 226 resource_uuid=volume_id, 227 detail=message_field.Detail.NOTIFY_COMPUTE_SERVICE_FAILED) 228 return result 229