Use Zaqar signed URLs in software deployment

Instead of using a token that we need to create every time to poll the
Zaqar queue, create a signature and use it as an authentication
mechanism.

Change-Id: Ibf9f6c334eba024f6faa7d6bb708d6d9f778ee43
This commit is contained in:
Thomas Herve 2016-01-19 19:20:53 +01:00
parent 6499a52e4c
commit 82f3817105
8 changed files with 74 additions and 22 deletions

View File

@ -17,7 +17,7 @@ from heat.common.i18n import _LE
LOG = logging.getLogger(__name__)
from zaqarclient.queues.v1 import client as zaqarclient
from zaqarclient.queues.v2 import client as zaqarclient
from zaqarclient.transport import errors as zaqar_errors
from heat.engine.clients import client_plugin
@ -53,9 +53,22 @@ class ZaqarClientPlugin(client_plugin.ClientPlugin):
conf = {'auth_opts': auth_opts}
endpoint = self.url_for(service_type=self.MESSAGING)
client = zaqarclient.Client(url=endpoint, conf=conf, version=1.1)
return zaqarclient.Client(url=endpoint, conf=conf, version=2)
return client
def create_from_signed_url(self, project_id, paths, expires, methods,
signature):
opts = {
'paths': paths,
'expires': expires,
'methods': methods,
'signature': signature,
'os_project_id': project_id,
}
auth_opts = {'backend': 'signed-url',
'options': opts}
conf = {'auth_opts': auth_opts}
endpoint = self.url_for(service_type=self.MESSAGING)
return zaqarclient.Client(url=endpoint, conf=conf, version=2)
def is_not_found(self, ex):
return isinstance(ex, zaqar_errors.ResourceNotFound)

View File

@ -290,6 +290,14 @@ class SignalResponder(stack_user.StackUser):
self._create_user()
queue_id = self.physical_resource_name()
zaqar_plugin = self.client_plugin('zaqar')
zaqar = zaqar_plugin.create_for_tenant(
self.stack.stack_user_project_id, self._user_token())
queue = zaqar.queue(queue_id)
signed_url_data = queue.signed_url(
['messages'], methods=['GET', 'DELETE'])
self.data_set('zaqar_queue_signed_url_data',
jsonutils.dumps(signed_url_data))
self.data_set('zaqar_signal_queue_id', queue_id)
return queue_id

View File

@ -118,10 +118,8 @@ class SoftwareConfigService(service.Service):
requests.put(metadata_put_url, json_md)
if metadata_queue_id:
project = stack_user_project_id
token = self._get_user_token(cnxt, rs, project)
queue = self._get_zaqar_queue(cnxt, rs, project, metadata_queue_id)
zaqar_plugin = cnxt.clients.client_plugin('zaqar')
zaqar = zaqar_plugin.create_for_tenant(project, token)
queue = zaqar.queue(metadata_queue_id)
queue.post({'body': md, 'ttl': zaqar_plugin.DEFAULT_TTL})
def _refresh_swift_software_deployment(self, cnxt, sd, deploy_signal_id):
@ -170,24 +168,32 @@ class SoftwareConfigService(service.Service):
return software_deployment_object.SoftwareDeployment.get_by_id(
cnxt, sd.id)
def _get_user_token(self, cnxt, rs, project):
user = password = None
def _get_zaqar_queue(self, cnxt, rs, project, queue_name):
user = password = signed_url_data = None
for rd in rs.data:
if rd.key == 'password':
password = crypt.decrypt(rd.decrypt_method, rd.value)
if rd.key == 'user_id':
user = rd.value
keystone = cnxt.clients.client('keystone')
return keystone.stack_domain_user_token(
user_id=user, project_id=project, password=password)
if rd.key == 'zaqar_queue_signed_url_data':
signed_url_data = jsonutils.loads(rd.value)
zaqar_plugin = cnxt.clients.client_plugin('zaqar')
if signed_url_data is None:
keystone = cnxt.clients.client('keystone')
token = keystone.stack_domain_user_token(
user_id=user, project_id=project, password=password)
zaqar = zaqar_plugin.create_for_tenant(project, token)
else:
signed_url_data.pop('project')
zaqar = zaqar_plugin.create_from_signed_url(project,
**signed_url_data)
return zaqar.queue(queue_name)
def _refresh_zaqar_software_deployment(self, cnxt, sd, deploy_queue_id):
rs = db_api.resource_get_by_physical_resource_id(cnxt, sd.server_id)
rs = db_api.resource_get_by_physical_resource_id(cnxt, sd.id)
project = sd.stack_user_project_id
token = self._get_user_token(cnxt, rs, project)
zaqar_plugin = cnxt.clients.client_plugin('zaqar')
zaqar = zaqar_plugin.create_for_tenant(project, token)
queue = zaqar.queue(deploy_queue_id)
queue = self._get_zaqar_queue(cnxt, rs, project, deploy_queue_id)
messages = list(queue.pop())
if messages:

View File

@ -26,7 +26,7 @@ class ZaqarClientPluginTest(common.HeatTestCase):
plugin = context.clients.client_plugin('zaqar')
client = plugin.client()
self.assertEqual('http://server.test:5000/v3', client.api_url)
self.assertEqual(1.1, client.api_version)
self.assertEqual(2.0, client.api_version)
self.assertEqual('test_tenant_id',
client.conf['auth_opts']['options']['os_project_id'])

View File

@ -199,6 +199,7 @@ class SignalResource(signal_responder.SignalResponder):
'signal': attributes.Schema('Get a signal')}
def handle_create(self):
self.password = 'password'
super(SignalResource, self).handle_create()
self.resource_id_set(self._get_user_id())

View File

@ -18,6 +18,8 @@ import uuid
import mock
import six
from oslo_serialization import jsonutils
from heat.common import exception as exc
from heat.common.i18n import _
from heat.engine.clients.os import nova
@ -1169,6 +1171,17 @@ class SoftwareDeploymentTest(common.HeatTestCase):
def test_get_zaqar_queue(self):
dep_data = {}
zc = mock.MagicMock()
zcc = self.patch(
'heat.engine.clients.os.zaqar.ZaqarClientPlugin.create_for_tenant')
zcc.return_value = zc
mock_queue = mock.MagicMock()
zc.queue.return_value = mock_queue
signed_data = {"signature": "hi", "expires": "later"}
mock_queue.signed_url.return_value = signed_data
self._create_stack(self.template_zaqar_signal)
def data_set(key, value, redact=False):
@ -1183,6 +1196,8 @@ class SoftwareDeploymentTest(common.HeatTestCase):
queue_id = self.deployment._get_zaqar_signal_queue_id()
self.assertEqual(queue_id, dep_data['zaqar_signal_queue_id'])
self.assertEqual(jsonutils.dumps(signed_data),
dep_data['zaqar_queue_signed_url_data'])
self.assertEqual(queue_id,
self.deployment._get_zaqar_signal_queue_id())

View File

@ -14,6 +14,7 @@
import datetime
import uuid
import mock
import mox
from oslo_serialization import jsonutils as json
from oslo_utils import timeutils
@ -480,7 +481,8 @@ class HeatWaitConditionTest(common.HeatTestCase):
signal = json.loads(handle.FnGetAtt('signal'))
self.assertIn('alarm_url', signal)
def test_getatt_signal_zaqar(self):
@mock.patch('zaqarclient.queues.v2.queues.Queue.signed_url')
def test_getatt_signal_zaqar(self, mock_signed_url):
handle = self._create_heat_handle(
template=test_template_heat_waithandle_zaqar)
self.assertIsNone(handle.FnGetAtt('token'))

View File

@ -120,8 +120,9 @@ class SignalTest(common.HeatTestCase):
self.assertEqual('anaccesskey', rs_data.get('access_key'))
self.assertEqual('verysecret', rs_data.get('secret_key'))
self.assertEqual('1234', rs_data.get('user_id'))
self.assertEqual('password', rs_data.get('password'))
self.assertEqual(rsrc.resource_id, rs_data.get('user_id'))
self.assertEqual(4, len(rs_data))
self.assertEqual(5, len(rs_data))
def test_get_user_id(self):
# Setup
@ -259,7 +260,8 @@ class SignalTest(common.HeatTestCase):
mock_has.assert_called_once_with('signal_handler')
self.assertEqual(second_url, 'cached')
def test_FnGetAtt_zaqar_signal(self):
@mock.patch('zaqarclient.queues.v2.queues.Queue.signed_url')
def test_FnGetAtt_zaqar_signal(self, mock_signed_url):
# Setup
stack = self._create_stack(TEMPLATE_ZAQAR_SIGNAL)
rsrc = stack['signal_handler']
@ -276,10 +278,14 @@ class SignalTest(common.HeatTestCase):
self.assertIn('username', signal)
self.assertIn('password', signal)
self.assertIn('queue_id', signal)
mock_signed_url.assert_called_once_with(
['messages'], methods=['GET', 'DELETE'])
@mock.patch.object(stk.Stack, 'cache_data_resource_attribute')
@mock.patch.object(stk.Stack, 'has_cache_data')
def test_FnGetAtt_zaqar_signal_is_cached(self, mock_has, mock_get):
@mock.patch('zaqarclient.queues.v2.queues.Queue.signed_url')
def test_FnGetAtt_zaqar_signal_is_cached(self, mock_signed_url, mock_has,
mock_get):
# Setup
mock_has.return_value = False
stack = self._create_stack(TEMPLATE_ZAQAR_SIGNAL)
@ -454,7 +460,8 @@ class SignalTest(common.HeatTestCase):
self.assertEqual(1, mock_delete_container.call_count)
self.assertEqual(1, mock_head.call_count)
def test_FnGetAtt_zaqar_signal_delete(self):
@mock.patch('zaqarclient.queues.v2.queues.Queue.signed_url')
def test_FnGetAtt_zaqar_signal_delete(self, mock_signed_url):
# Setup
stack = self._create_stack(TEMPLATE_ZAQAR_SIGNAL)