# # 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. """Client Libraries for Rackspace Resources.""" import hashlib import random import time from glanceclient import client as gc from oslo_config import cfg from oslo_log import log as logging from six.moves.urllib import parse from swiftclient import utils as swiftclient_utils from troveclient import client as tc from heat.common import exception from heat.common.i18n import _LI from heat.common.i18n import _LW from heat.engine.clients import client_plugin from heat.engine.clients.os import cinder from heat.engine.clients.os import glance from heat.engine.clients.os import nova from heat.engine.clients.os import swift from heat.engine.clients.os import trove LOG = logging.getLogger(__name__) try: import pyrax except ImportError: pyrax = None class RackspaceClientPlugin(client_plugin.ClientPlugin): pyrax = None def _get_client(self, name): if self.pyrax is None: self._authenticate() return self.pyrax.get_client( name, cfg.CONF.region_name_for_services) def _authenticate(self): """Create an authenticated client context.""" self.pyrax = pyrax.create_context("rackspace") self.pyrax.auth_endpoint = self.context.auth_url LOG.info(_LI("Authenticating username: %s"), self.context.username) tenant = self.context.tenant_id tenant_name = self.context.tenant self.pyrax.auth_with_token(self.context.auth_token, tenant_id=tenant, tenant_name=tenant_name) if not self.pyrax.authenticated: LOG.warning(_LW("Pyrax Authentication Failed.")) raise exception.AuthorizationFailure() LOG.info(_LI("User %s authenticated successfully."), self.context.username) class RackspaceAutoScaleClient(RackspaceClientPlugin): def _create(self): """Rackspace Auto Scale client.""" return self._get_client("autoscale") class RackspaceCloudLBClient(RackspaceClientPlugin): def _create(self): """Rackspace cloud loadbalancer client.""" return self._get_client("load_balancer") class RackspaceCloudDNSClient(RackspaceClientPlugin): def _create(self): """Rackspace cloud dns client.""" return self._get_client("dns") class RackspaceNovaClient(nova.NovaClientPlugin, RackspaceClientPlugin): def _create(self): """Rackspace cloudservers client.""" client = self._get_client("compute") if not client: client = super(RackspaceNovaClient, self)._create() return client class RackspaceCloudNetworksClient(RackspaceClientPlugin): def _create(self): """Rackspace cloud networks client. Though pyrax "fixed" the network client bugs that were introduced in 1.8, it still doesn't work for contexts because of caching of the nova client. """ if not self.pyrax: self._authenticate() # need special handling now since the contextual # pyrax doesn't handle "networks" not being in # the catalog ep = pyrax._get_service_endpoint( self.pyrax, "compute", region=cfg.CONF.region_name_for_services) cls = pyrax._client_classes['compute:network'] client = cls(self.pyrax, region_name=cfg.CONF.region_name_for_services, management_url=ep) return client class RackspaceTroveClient(trove.TroveClientPlugin): """Rackspace trove client. Since the pyrax module uses its own client implementation for Cloud Databases, we have to skip pyrax on this one and override the super implementation to account for custom service type and regionalized management url. """ def _create(self): service_type = "rax:database" con = self.context endpoint_type = self._get_client_option('trove', 'endpoint_type') args = { 'service_type': service_type, 'auth_url': con.auth_url, 'proxy_token': con.auth_token, 'username': None, 'password': None, 'cacert': self._get_client_option('trove', 'ca_file'), 'insecure': self._get_client_option('trove', 'insecure'), 'endpoint_type': endpoint_type } client = tc.Client('1.0', **args) region = cfg.CONF.region_name_for_services management_url = self.url_for(service_type=service_type, endpoint_type=endpoint_type, region_name=region) client.client.auth_token = con.auth_token client.client.management_url = management_url return client class RackspaceCinderClient(cinder.CinderClientPlugin): def _create(self): """Override the region for the cinder client.""" client = super(RackspaceCinderClient, self)._create() management_url = self.url_for( service_type='volume', region_name=cfg.CONF.region_name_for_services) client.client.management_url = management_url return client class RackspaceSwiftClient(swift.SwiftClientPlugin): def is_valid_temp_url_path(self, path): """Return True if path is a valid Swift TempURL path, False otherwise. A Swift TempURL path must: - Be five parts, ['', 'v1', 'account', 'container', 'object'] - Be a v1 request - Have account, container, and object values - Have an object value with more than just '/'s :param path: The TempURL path :type path: string """ parts = path.split('/', 4) return bool(len(parts) == 5 and not parts[0] and parts[1] == 'v1' and parts[2] and parts[3] and parts[4].strip('/')) def get_temp_url(self, container_name, obj_name, timeout=None, method='PUT'): """Return a Swift TempURL.""" def tenant_uuid(): access = self.context.auth_token_info['access'] for role in access['user']['roles']: if role['name'] == 'object-store:default': return role['tenantId'] key_header = 'x-account-meta-temp-url-key' if key_header in self.client().head_account(): key = self.client().head_account()[key_header] else: key = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[:32] self.client().post_account({key_header: key}) path = '/v1/%s/%s/%s' % (tenant_uuid(), container_name, obj_name) if timeout is None: timeout = swift.MAX_EPOCH - 60 - time.time() tempurl = swiftclient_utils.generate_temp_url(path, timeout, key, method) sw_url = parse.urlparse(self.client().url) return '%s://%s%s' % (sw_url.scheme, sw_url.netloc, tempurl) class RackspaceGlanceClient(glance.GlanceClientPlugin): def _create(self, version=None): con = self.context endpoint_type = self._get_client_option('glance', 'endpoint_type') endpoint = self.url_for( service_type='image', endpoint_type=endpoint_type, region_name=cfg.CONF.region_name_for_services) # Rackspace service catalog includes a tenant scoped glance # endpoint so we have to munge the url a bit glance_url = parse.urlparse(endpoint) # remove the tenant and following from the url endpoint = "%s://%s" % (glance_url.scheme, glance_url.hostname) args = { 'auth_url': con.auth_url, 'service_type': 'image', 'project_id': con.tenant, 'token': self.auth_token, 'endpoint_type': endpoint_type, 'ca_file': self._get_client_option('glance', 'ca_file'), 'cert_file': self._get_client_option('glance', 'cert_file'), 'key_file': self._get_client_option('glance', 'key_file'), 'insecure': self._get_client_option('glance', 'insecure') } return gc.Client('2', endpoint, **args)