heat/contrib/rackspace/rackspace/clients.py

247 lines
8.6 KiB
Python

#
# 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.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("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("Pyrax Authentication Failed.")
raise exception.AuthorizationFailure()
LOG.info("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)