Port rackspace clients to client plugins

This change converts the rackspace client code to be contributed
via client plugins. This is the last change required for all in-tree
clients to be contributed via plugins.

A RackspaceTest base class which adds the rackspace directory
to the client plugins path which ensures any rackspace variants
are loaded. Plugin loading precedence is used to ensure rackspace
versions will override core plugins with the same name.

The nova plugin will fallback to returning the core nova client
so that CloudServersTest will run even when pyrax is not installed.

The cloud_backed configuration option will no longer have any effect
on whether rackspace clients will be loaded. Instead they will be loaded
by stevedore via the setup.cfg heat.clients entry point.

Change-Id: I312de40064cc00896dba1835117ff67003e57938
This commit is contained in:
Steve Baker 2014-06-09 13:13:34 +12:00 committed by Randall Burt
parent 0eb93cbf33
commit 1a44187fdf
11 changed files with 246 additions and 219 deletions

View File

@ -18,140 +18,37 @@ import urlparse
from oslo.config import cfg
from heat.common import exception
from heat.engine import clients
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 trove
from heat.openstack.common.gettextutils import _
from heat.openstack.common import log as logging
from glanceclient import client as glanceclient
from glanceclient import client as gc
from troveclient import client as tc
LOG = logging.getLogger(__name__)
try:
import pyrax
except ImportError:
LOG.info(_('pyrax not available'))
pyrax = None
class Clients(clients.OpenStackClients):
class RackspaceClientPlugin(client_plugin.ClientPlugin):
"""Convenience class to create and cache client instances."""
def __init__(self, context):
super(Clients, self).__init__(context)
self.pyrax = None
pyrax = None
def _get_client(self, name):
if not self.pyrax:
self.__authenticate()
if name not in self._clients:
client = self.pyrax.get_client(
name, cfg.CONF.region_name_for_services)
self._clients[name] = client
return self._clients[name]
if self.pyrax is None:
self._authenticate()
return self.pyrax.get_client(
name, cfg.CONF.region_name_for_services)
def auto_scale(self):
"""Rackspace Auto Scale client."""
return self._get_client("autoscale")
def cloud_lb(self):
"""Rackspace cloud loadbalancer client."""
return self._get_client("load_balancer")
def cloud_dns(self):
"""Rackspace cloud dns client."""
return self._get_client("dns")
def nova(self):
"""Rackspace cloudservers client."""
return self._get_client("compute")
def cloud_networks(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 "networks" not in self._clients:
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']
self._clients["networks"] = cls(
self.pyrax,
region_name=cfg.CONF.region_name_for_services,
management_url=ep)
return self._clients["networks"]
def trove(self):
"""
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
management url to be region-aware.
"""
if "trove" not in self._clients:
super(Clients, self).trove(service_type='rax:database')
management_url = self.url_for(
service_type='rax:database',
region_name=cfg.CONF.region_name_for_services)
self._clients['trove'].client.management_url = management_url
return self._clients['trove']
def cinder(self):
"""Override the region for the cinder client."""
if "cinder" not in self._clients:
super(Clients, self).cinder()
management_url = self.url_for(
service_type='volume',
region_name=cfg.CONF.region_name_for_services)
self._clients['cinder'].client.management_url = management_url
return self._clients['cinder']
def swift(self):
# Rackspace doesn't include object-store in the default catalog
# for "reasons". The pyrax client takes care of this, but it
# returns a wrapper over the upstream python-swiftclient so we
# unwrap here and things just work
return self._get_client("object_store").connection
def glance(self):
if "image" not in self._clients:
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 = urlparse.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')
}
client = glanceclient.Client('2', endpoint, **args)
self._clients["image"] = client
return self._clients["image"]
def __authenticate(self):
def _authenticate(self):
"""Create an authenticated client context."""
self.pyrax = pyrax.create_context("rackspace")
self.pyrax.auth_endpoint = self.context.auth_url
@ -167,3 +64,145 @@ class Clients(clients.OpenStackClients):
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(RackspaceClientPlugin):
def _create(self):
# Rackspace doesn't include object-store in the default catalog
# for "reasons". The pyrax client takes care of this, but it
# returns a wrapper over the upstream python-swiftclient so we
# unwrap here and things just work
return self._get_client("object_store").connection
class RackspaceGlanceClient(glance.GlanceClientPlugin):
def _create(self):
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 = urlparse.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)

View File

@ -306,13 +306,13 @@ class Group(resource.Resource):
The resource_id is set to the resulting group's ID.
"""
asclient = self.stack.clients.auto_scale()
asclient = self.auto_scale()
group = asclient.create(**self._get_create_args())
self.resource_id_set(str(group.id))
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
"""Update the group configuration and the launch configuration."""
asclient = self.stack.clients.auto_scale()
asclient = self.auto_scale()
if self.GROUP_CONFIGURATION in prop_diff:
args = self._get_group_config_args(
prop_diff[self.GROUP_CONFIGURATION])
@ -332,7 +332,7 @@ class Group(resource.Resource):
"""
if self.resource_id is None:
return
asclient = self.stack.clients.auto_scale()
asclient = self.auto_scale()
args = self._get_group_config_args(
self.properties[self.GROUP_CONFIGURATION])
args['min_entities'] = 0
@ -347,7 +347,7 @@ class Group(resource.Resource):
if self.resource_id is None:
return True
try:
self.stack.clients.auto_scale().delete(self.resource_id)
self.auto_scale().delete(self.resource_id)
except Forbidden:
return False
except NotFound:
@ -355,6 +355,9 @@ class Group(resource.Resource):
else:
return True
def auto_scale(self):
return self.client('auto_scale')
class ScalingPolicy(resource.Resource):
@ -447,7 +450,7 @@ class ScalingPolicy(resource.Resource):
The resource ID is initialized to {group_id}:{policy_id}.
"""
asclient = self.stack.clients.auto_scale()
asclient = self.auto_scale()
args = self._get_args(self.properties)
policy = asclient.add_policy(**args)
resource_id = '%s:%s' % (self.properties[self.GROUP], policy.id)
@ -457,14 +460,14 @@ class ScalingPolicy(resource.Resource):
return self.resource_id.split(':', 1)[1]
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
asclient = self.stack.clients.auto_scale()
asclient = self.auto_scale()
args = self._get_args(tmpl_diff['Properties'])
args['policy'] = self._get_policy_id()
asclient.replace_policy(**args)
def handle_delete(self):
"""Delete the policy if it exists."""
asclient = self.stack.clients.auto_scale()
asclient = self.auto_scale()
if self.resource_id is None:
return
policy_id = self._get_policy_id()
@ -473,6 +476,9 @@ class ScalingPolicy(resource.Resource):
except NotFound:
pass
def auto_scale(self):
return self.client('auto_scale')
class WebHook(resource.Resource):
@ -534,7 +540,7 @@ class WebHook(resource.Resource):
metadata=props.get(self.METADATA))
def handle_create(self):
asclient = self.stack.clients.auto_scale()
asclient = self.auto_scale()
args = self._get_args(self.properties)
webhook = asclient.add_webhook(**args)
self.resource_id_set(webhook.id)
@ -548,7 +554,7 @@ class WebHook(resource.Resource):
self.data_set(key, url)
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
asclient = self.stack.clients.auto_scale()
asclient = self.auto_scale()
args = self._get_args(json_snippet['Properties'])
args['webhook'] = self.resource_id
asclient.replace_webhook(**args)
@ -563,13 +569,16 @@ class WebHook(resource.Resource):
def handle_delete(self):
if self.resource_id is None:
return
asclient = self.stack.clients.auto_scale()
asclient = self.auto_scale()
group_id, policy_id = self.properties[self.POLICY].split(':', 1)
try:
asclient.delete_webhook(group_id, policy_id, self.resource_id)
except NotFound:
pass
def auto_scale(self):
return self.client('auto_scale')
def resource_mapping():
return {

View File

@ -147,7 +147,7 @@ class CloudDns(resource.Resource):
}
def cloud_dns(self):
return self.stack.clients.cloud_dns()
return self.client('cloud_dns')
def handle_create(self):
"""Create a Rackspace CloudDns Instance."""

View File

@ -387,7 +387,7 @@ class CloudLoadBalancer(resource.Resource):
self.clb = self.cloud_lb()
def cloud_lb(self):
return self.stack.clients.cloud_lb()
return self.client('cloud_lb')
def _setup_properties(self, properties, function):
"""Use defined schema properties as kwargs for loadbalancer objects."""

View File

@ -98,7 +98,7 @@ class CloudNetwork(resource.Resource):
return self._network
def cloud_networks(self):
return self.stack.clients.cloud_networks()
return self.client('cloud_networks')
def handle_create(self):
cnw = self.cloud_networks().create(label=self.properties[self.LABEL],

View File

@ -16,11 +16,10 @@ import itertools
from heat.common import exception
from heat.common import template_format
from heat.engine import clients
from heat.engine import resource
from heat.engine import rsrc_defn
from heat.engine import scheduler
from heat.tests.common import HeatTestCase
from heat.tests import common
from heat.tests import utils
from ..resources import auto_scale # noqa
@ -165,7 +164,7 @@ class FakeAutoScale(object):
webhook.kwargs['metadata'] = metadata
class ScalingGroupTest(HeatTestCase):
class ScalingGroupTest(common.HeatTestCase):
group_template = template_format.parse('''
HeatTemplateFormatVersion: "2012-12-12"
@ -204,8 +203,8 @@ class ScalingGroupTest(HeatTestCase):
for res_name, res_class in auto_scale.resource_mapping().items():
resource._register_class(res_name, res_class)
self.fake_auto_scale = FakeAutoScale()
self.patchobject(clients.OpenStackClients, 'auto_scale',
return_value=self.fake_auto_scale, create=True)
self.patchobject(auto_scale.Group, 'auto_scale',
return_value=self.fake_auto_scale)
def _setup_test_stack(self):
self.stack = utils.parse_stack(self.group_template)
@ -382,7 +381,7 @@ Resources:
if count < 3:
raise auto_scale.Forbidden("Not empty!")
self.patchobject(self.fake_auto_scale, 'delete', new=delete)
self.patchobject(self.fake_auto_scale, 'delete', side_effect=delete)
resource = self.stack['my_group']
scheduler.TaskRunner(resource.delete)()
# It really called delete until it succeeded:
@ -398,14 +397,14 @@ Resources:
def delete(group_id):
1 / 0
self.patchobject(self.fake_auto_scale, 'delete', new=delete)
self.patchobject(self.fake_auto_scale, 'delete', side_effect=delete)
resource = self.stack['my_group']
err = self.assertRaises(
exception.ResourceFailure, scheduler.TaskRunner(resource.delete))
self.assertIsInstance(err.exc, ZeroDivisionError)
class PolicyTest(HeatTestCase):
class PolicyTest(common.HeatTestCase):
policy_template = template_format.parse('''
HeatTemplateFormatVersion: "2012-12-12"
Description: "Rackspace Auto Scale"
@ -426,8 +425,8 @@ class PolicyTest(HeatTestCase):
for res_name, res_class in auto_scale.resource_mapping().items():
resource._register_class(res_name, res_class)
self.fake_auto_scale = FakeAutoScale()
self.patchobject(clients.OpenStackClients, 'auto_scale',
return_value=self.fake_auto_scale, create=True)
self.patchobject(auto_scale.ScalingPolicy, 'auto_scale',
return_value=self.fake_auto_scale)
def _setup_test_stack(self, template):
self.stack = utils.parse_stack(template)
@ -550,7 +549,7 @@ class PolicyTest(HeatTestCase):
self.assertEqual({}, self.fake_auto_scale.policies)
class WebHookTest(HeatTestCase):
class WebHookTest(common.HeatTestCase):
webhook_template = template_format.parse('''
HeatTemplateFormatVersion: "2012-12-12"
Description: "Rackspace Auto Scale"
@ -570,8 +569,8 @@ class WebHookTest(HeatTestCase):
for res_name, res_class in auto_scale.resource_mapping().items():
resource._register_class(res_name, res_class)
self.fake_auto_scale = FakeAutoScale()
self.patchobject(clients.OpenStackClients, 'auto_scale',
return_value=self.fake_auto_scale, create=True)
self.patchobject(auto_scale.WebHook, 'auto_scale',
return_value=self.fake_auto_scale)
def _setup_test_stack(self, template):
self.stack = utils.parse_stack(template)

View File

@ -23,7 +23,7 @@ from heat.common import template_format
from heat.engine import resource
from heat.engine import rsrc_defn
from heat.engine import scheduler
from heat.tests.common import HeatTestCase
from heat.tests import common
from heat.tests import utils
from ..resources import cloud_loadbalancer as lb # noqa
@ -155,7 +155,7 @@ def override_resource():
}
class LoadBalancerTest(HeatTestCase):
class LoadBalancerTest(common.HeatTestCase):
def setUp(self):
super(LoadBalancerTest, self).setUp()

View File

@ -20,7 +20,7 @@ from heat.common import exception
from heat.common import template_format
from heat.engine import resource
from heat.engine import scheduler
from heat.tests.common import HeatTestCase
from heat.tests import common
from heat.tests import utils
from ..resources import cloudnetworks # noqa
@ -75,7 +75,7 @@ class FakeClient(object):
@mock.patch.object(cloudnetworks.CloudNetwork, "cloud_networks")
class CloudNetworkTest(HeatTestCase):
class CloudNetworkTest(common.HeatTestCase):
_template = template_format.parse("""
heat_template_version: 2013-05-23

View File

@ -1,29 +0,0 @@
#
# 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 oslo.config import cfg
from heat.engine import clients
from heat.tests.common import HeatTestCase
from .. import clients as rackspace_clients # noqa
class ClientsTest(HeatTestCase):
def setUp(self):
super(ClientsTest, self).setUp()
cfg.CONF.set_override('cloud_backend', 'rackspace.clients.Clients')
self.backend = clients.ClientBackend('fake_context')
def test_client_plugin_loads(self):
self.assertIsInstance(self.backend, rackspace_clients.Clients)

View File

@ -13,18 +13,17 @@
import mock
import mox
from oslo.config import cfg
import six
from heat.common import exception
from heat.common import template_format
from heat.engine.clients.os import glance
from heat.engine.clients.os import nova
from heat.engine import environment
from heat.engine import parser
from heat.engine import resource
from heat.engine import scheduler
from heat.openstack.common import uuidutils
from heat.tests.common import HeatTestCase
from heat.tests import common
from heat.tests import utils
from heat.tests.v1_1 import fakes
@ -56,34 +55,42 @@ wp_template = '''
}
'''
cfg.CONF.import_opt('region_name_for_services', 'heat.common.config')
class CloudServersTest(HeatTestCase):
class CloudServersTest(common.HeatTestCase):
def setUp(self):
super(CloudServersTest, self).setUp()
cfg.CONF.set_override('region_name_for_services', 'RegionOne')
self.ctx = utils.dummy_context()
self.fc = fakes.FakeClient()
mock_nova_create = mock.Mock()
self.ctx.clients.client_plugin(
'nova')._create = mock_nova_create
mock_nova_create.return_value = self.fc
self.stub_keystoneclient()
# Test environment may not have pyrax client library installed and if
# pyrax is not installed resource class would not be registered.
# So register resource provider class explicitly for unit testing.
resource._register_class("Rackspace::Cloud::Server",
cloud_server.CloudServer)
def _mock_get_image_id_success(self, imageId_input, imageId):
self.m.StubOutWithMock(glance.GlanceClientPlugin, 'get_image_id')
glance.GlanceClientPlugin.get_image_id(imageId_input).MultipleTimes().\
AndReturn(imageId)
def _mock_get_image_id_success(self, imageId):
self.mock_get_image = mock.Mock()
self.ctx.clients.client_plugin(
'glance').get_image_id = self.mock_get_image
self.mock_get_image.return_value = imageId
def _stub_server_validate(self, server, imageId_input, image_id):
# stub nova validate
self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
nova.NovaClientPlugin._create().MultipleTimes().AndReturn(self.fc)
# stub glance image validate
self._mock_get_image_id_success(imageId_input, image_id)
self._mock_get_image_id_success(image_id)
def _setup_test_stack(self, stack_name):
t = template_format.parse(wp_template)
template = parser.Template(t)
stack = parser.Stack(utils.dummy_context(), stack_name, template,
stack = parser.Stack(self.ctx, stack_name, template,
environment.Environment({'key_name': 'test'}),
stack_id=uuidutils.generate_uuid())
return (template, stack)
@ -317,11 +324,9 @@ class CloudServersTest(HeatTestCase):
self.assertEqual('Error: Unknown Managed Cloud automation status: FOO',
six.text_type(exc))
@mock.patch.object(nova.NovaClientPlugin, '_create')
@mock.patch.object(resource.Resource, 'data_set')
def test_create_store_admin_pass_resource_data(self,
mock_data_set,
mock_create):
mock_data_set):
self._mock_metadata_os_distro()
return_server = self.fc.servers.list()[1]
return_server.adminPass = 'autogenerated'
@ -333,19 +338,15 @@ class CloudServersTest(HeatTestCase):
server = cloud_server.CloudServer('WebServer',
resource_defns['WebServer'], stack)
mock_create.return_value = self.fc
self.fc.servers.create = mock.Mock(return_value=return_server)
self._mock_get_image_id_success('CentOS 5.2', 'image_id')
self._mock_get_image_id_success('image_id')
scheduler.TaskRunner(server.create)()
expected_call = mock.call(server.ADMIN_PASS,
'autogenerated', redact=True)
self.assertIn(expected_call, mock_data_set.call_args_list)
@mock.patch.object(nova.NovaClientPlugin, '_create')
@mock.patch.object(resource.Resource, 'data_set')
def test_create_save_admin_pass_is_false(self,
mock_data_set,
mock_create):
def test_create_save_admin_pass_is_false(self, mock_data_set):
self._mock_metadata_os_distro()
return_server = self.fc.servers.list()[1]
return_server.adminPass = 'autogenerated'
@ -357,19 +358,16 @@ class CloudServersTest(HeatTestCase):
server = cloud_server.CloudServer('WebServer',
resource_defns['WebServer'], stack)
mock_create.return_value = self.fc
self.fc.servers.create = mock.Mock(return_value=return_server)
self._mock_get_image_id_success('CentOS 5.2', 'image_id')
self._mock_get_image_id_success('image_id')
scheduler.TaskRunner(server.create)()
expected_call = mock.call(mock.ANY, server.ADMIN_PASS,
mock.ANY, mock.ANY)
self.assertNotIn(expected_call, mock_data_set.call_args_list)
@mock.patch.object(nova.NovaClientPlugin, '_create')
@mock.patch.object(resource.Resource, 'data_set')
def test_create_save_admin_pass_defaults_to_false(self,
mock_data_set,
mock_create):
mock_data_set):
self._mock_metadata_os_distro()
return_server = self.fc.servers.list()[1]
return_server.adminPass = 'autogenerated'
@ -381,19 +379,17 @@ class CloudServersTest(HeatTestCase):
server = cloud_server.CloudServer('WebServer',
resource_defns['WebServer'], stack)
mock_create.return_value = self.fc
self.fc.servers.create = mock.Mock(return_value=return_server)
self._mock_get_image_id_success('CentOS 5.2', 'image_id')
self._mock_get_image_id_success('image_id')
scheduler.TaskRunner(server.create)()
expected_call = mock.call(mock.ANY, server.ADMIN_PASS,
mock.ANY, mock.ANY)
self.assertNotIn(expected_call, mock_data_set.call_args_list)
@mock.patch.object(nova.NovaClientPlugin, '_create')
@mock.patch.object(resource.Resource, 'data_set')
def test_create_without_adminPass_attribute(self,
mock_data_set,
mock_create):
mock_data_set):
self._mock_metadata_os_distro()
return_server = self.fc.servers.list()[1]
stack_name = 'admin_pass_s'
@ -403,9 +399,9 @@ class CloudServersTest(HeatTestCase):
server = cloud_server.CloudServer('WebServer',
resource_defns['WebServer'], stack)
mock_create.return_value = self.fc
self.fc.servers.create = mock.Mock(return_value=return_server)
self._mock_get_image_id_success('CentOS 5.2', 'image_id')
self._mock_get_image_id_success('image_id')
scheduler.TaskRunner(server.create)()
expected_call = mock.call(mock.ANY, server.ADMIN_PASS,
mock.ANY, redact=mock.ANY)
@ -433,9 +429,7 @@ class CloudServersTest(HeatTestCase):
resource_defns['WebServer'], stack)
self.assertEqual('foo', server.FnGetAtt('admin_pass'))
@mock.patch.object(nova.NovaClientPlugin, '_create')
def _test_server_config_drive(self, user_data, config_drive, result,
mock_create):
def _test_server_config_drive(self, user_data, config_drive, result):
return_server = self.fc.servers.list()[1]
stack_name = 'no_user_data'
(tmpl, stack) = self._setup_test_stack(stack_name)
@ -445,11 +439,10 @@ class CloudServersTest(HeatTestCase):
resource_defns = tmpl.resource_definitions(stack)
server = cloud_server.CloudServer('WebServer',
resource_defns['WebServer'], stack)
mock_create.return_value = self.fc
mock_servers_create = mock.Mock(return_value=return_server)
self.fc.servers.create = mock_servers_create
image_id = mock.ANY
self._mock_get_image_id_success('CentOS 5.2', image_id)
self._mock_get_image_id_success(image_id)
scheduler.TaskRunner(server.create)()
mock_servers_create.assert_called_with(
image=image_id,

View File

@ -18,10 +18,26 @@ classifier =
Programming Language :: Python :: 2.6
[files]
# Copy to /usr/lib/heat for plugin loading
packages =
rackspace
# Copy to /usr/lib/heat for non-stevedore plugin loading
data_files =
lib/heat/rackspace = rackspace/resources/*
[entry_points]
heat.clients =
auto_scale = rackspace.clients:RackspaceAutoScaleClient
cinder = rackspace.clients:RackspaceCinderClient
cloud_dns = rackspace.clients:RackspaceCloudDNSClient
cloud_lb = rackspace.clients:RackspaceCloudLBClient
cloud_networks = rackspace.clients:RackspaceCloudNetworksClient
glance = rackspace.clients:RackspaceGlanceClient
nova = rackspace.clients:RackspaceNovaClient
trove = rackspace.clients:RackspaceTroveClient
swift = rackspace.clients:RackspaceSwiftClient
[global]
setup-hooks =
pbr.hooks.setup_hook