Store stack domain credentials for deployments

In order to allow stack domain credentials to be used for
deployment API operations, the stack_user_project_id needs to
be passed when a deployment resource is created and stored in the
database.

stack_user_project_id can then be used to compare to the context
tenant_id, just as for stack_get.

This change is needed since the blueprint hot-software-config and
blueprint instance-users landed at the same time. Some stack
operations can now be performed with credentials which are scoped
to a single resource. It would be very useful to allow these
credentials to be used for that polling for deployment metadata
too.

Closes-Bug: #1293234
Change-Id: Iea9d8bfe216d17fa2d3a9e9251102292be48486d
This commit is contained in:
Steve Baker 2014-03-24 12:49:58 +13:00
parent 66d701564c
commit 323ed2566c
11 changed files with 120 additions and 35 deletions

View File

@ -72,7 +72,7 @@ class SoftwareDeploymentController(object):
"""
create_data = dict((k, body.get(k)) for k in (
'config_id', 'server_id', 'input_values',
'action', 'status', 'status_reason'))
'action', 'status', 'status_reason', 'stack_user_project_id'))
sd = self.rpc_client.create_software_deployment(req.context,
**create_data)

View File

@ -662,7 +662,8 @@ def software_deployment_create(context, values):
def software_deployment_get(context, deployment_id):
result = model_query(context, models.SoftwareDeployment).get(deployment_id)
if (result is not None and context is not None and
result.tenant != context.tenant_id):
context.tenant_id not in (result.tenant,
result.stack_user_project_id)):
result = None
if not result:
@ -672,9 +673,13 @@ def software_deployment_get(context, deployment_id):
def software_deployment_get_all(context, server_id=None):
query = model_query(context, models.SoftwareDeployment).\
filter_by(tenant=context.tenant_id).\
order_by(models.SoftwareDeployment.created_at)
sd = models.SoftwareDeployment
query = model_query(context, sd).\
filter(sqlalchemy.or_(
sd.tenant == context.tenant_id,
sd.stack_user_project_id == context.tenant_id
)).\
order_by(sd.created_at)
if server_id:
query = query.filter_by(server_id=server_id)
return query.all()

View File

@ -0,0 +1,31 @@
# 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.
import sqlalchemy
def upgrade(migrate_engine):
meta = sqlalchemy.MetaData(bind=migrate_engine)
stack = sqlalchemy.Table('software_deployment', meta, autoload=True)
# Align with 64 character length used in keystone project table
stack_user_project_id = sqlalchemy.Column('stack_user_project_id',
sqlalchemy.String(length=64))
stack_user_project_id.create(stack)
def downgrade(migrate_engine):
meta = sqlalchemy.MetaData(bind=migrate_engine)
stack = sqlalchemy.Table('software_deployment', meta, autoload=True)
stack.c.stack_user_project_id.drop()

View File

@ -299,6 +299,8 @@ class SoftwareDeployment(BASE, HeatBase):
output_values = sqlalchemy.Column('output_values', Json)
tenant = sqlalchemy.Column(
'tenant', sqlalchemy.String(256), nullable=False)
stack_user_project_id = sqlalchemy.Column(sqlalchemy.String(64),
nullable=True)
action = sqlalchemy.Column('action', sqlalchemy.String(255))
status = sqlalchemy.Column('status', sqlalchemy.String(255))
status_reason = sqlalchemy.Column('status_reason', sqlalchemy.String(255))

View File

@ -173,7 +173,8 @@ class SoftwareDeployment(signal_responder.SignalResponder):
props = {
'config_id': config_id,
'server_id': properties[SoftwareDeployment.SERVER],
'action': action
'action': action,
'stack_user_project_id': self.stack.stack_user_project_id
}
if self._signal_transport_none():

View File

@ -1130,12 +1130,14 @@ class EngineService(service.Service):
@request_context
def create_software_deployment(self, cnxt, server_id, config_id,
input_values, action, status,
status_reason):
status_reason, stack_user_project_id):
sd = db_api.software_deployment_create(cnxt, {
'config_id': config_id,
'server_id': server_id,
'input_values': input_values,
'tenant': cnxt.tenant_id,
'stack_user_project_id': stack_user_project_id,
'action': action,
'status': status,
'status_reason': status_reason})

View File

@ -405,14 +405,17 @@ class EngineClient(heat.openstack.common.rpc.proxy.RpcProxy):
def create_software_deployment(self, cnxt, server_id, config_id=None,
input_values={}, action='INIT',
status='COMPLETE', status_reason=''):
return self.call(cnxt, self.make_msg('create_software_deployment',
server_id=server_id,
config_id=config_id,
input_values=input_values,
action=action,
status=status,
status_reason=status_reason))
status='COMPLETE', status_reason='',
stack_user_project_id=None):
return self.call(cnxt, self.make_msg(
'create_software_deployment',
server_id=server_id,
config_id=config_id,
input_values=input_values,
action=action,
status=status,
status_reason=status_reason,
stack_user_project_id=stack_user_project_id))
def update_software_deployment(self, cnxt, deployment_id,
config_id=None, input_values=None,

View File

@ -2586,14 +2586,15 @@ class SoftwareConfigServiceTest(HeatTestCase):
status='COMPLETE', status_reason='',
config_group=None,
server_id=str(uuid.uuid4()),
config_name=None):
config_name=None,
stack_user_project_id=None):
if config_id is None:
config = self._create_software_config(group=config_group,
name=config_name)
config_id = config['id']
return self.engine.create_software_deployment(
self.ctx, server_id, config_id, input_values,
action, status, status_reason)
action, status, status_reason, stack_user_project_id)
def test_list_software_deployments(self):
deployment = self._create_software_deployment()
@ -2611,15 +2612,22 @@ class SoftwareConfigServiceTest(HeatTestCase):
def test_metadata_software_deployments(self):
server_id = str(uuid.uuid4())
d1 = self._create_software_deployment(config_group='mygroup',
server_id=server_id,
config_name='02_second')
d2 = self._create_software_deployment(config_group='mygroup',
server_id=server_id,
config_name='01_first')
d3 = self._create_software_deployment(config_group='myothergroup',
server_id=server_id,
config_name='03_third')
stack_user_project_id = str(uuid.uuid4())
d1 = self._create_software_deployment(
config_group='mygroup',
server_id=server_id,
config_name='02_second',
stack_user_project_id=stack_user_project_id)
d2 = self._create_software_deployment(
config_group='mygroup',
server_id=server_id,
config_name='01_first',
stack_user_project_id=stack_user_project_id)
d3 = self._create_software_deployment(
config_group='myothergroup',
server_id=server_id,
config_name='03_third',
stack_user_project_id=stack_user_project_id)
metadata = self.engine.metadata_software_deployments(
self.ctx, server_id=server_id)
self.assertEqual(3, len(metadata))
@ -2637,6 +2645,19 @@ class SoftwareConfigServiceTest(HeatTestCase):
self.ctx, server_id=str(uuid.uuid4()))
self.assertEqual([], deployments)
# assert get results when the context tenant_id matches
# the stored stack_user_project_id
ctx = utils.dummy_context(tenant_id=stack_user_project_id)
metadata = self.engine.metadata_software_deployments(
ctx, server_id=server_id)
self.assertEqual(3, len(metadata))
# assert get no results when the context tenant_id is unknown
ctx = utils.dummy_context(tenant_id=str(uuid.uuid4()))
metadata = self.engine.metadata_software_deployments(
ctx, server_id=server_id)
self.assertEqual(0, len(metadata))
def test_show_software_deployment(self):
deployment_id = str(uuid.uuid4())
self.assertRaises(exception.NotFound,

View File

@ -242,13 +242,15 @@ class EngineRpcAPITestCase(testtools.TestCase):
deployment_id=deployment_id)
def test_create_software_deployment(self):
self._test_engine_api('create_software_deployment', 'call',
server_id='9f1f0e00-05d2-4ca5-8602-95021f19c9d0',
config_id='48e8ade1-9196-42d5-89a2-f709fde42632',
input_values={},
action='INIT',
status='COMPLETE',
status_reason=None)
self._test_engine_api(
'create_software_deployment', 'call',
server_id='9f1f0e00-05d2-4ca5-8602-95021f19c9d0',
config_id='48e8ade1-9196-42d5-89a2-f709fde42632',
stack_user_project_id='65728b74-cfe7-4f17-9c15-11d4f686e591',
input_values={},
action='INIT',
status='COMPLETE',
status_reason=None)
def test_update_software_deployment(self):
deployment_id = '86729f02-4648-44d8-af44-d0ec65b6abc9'

View File

@ -76,7 +76,8 @@ class SoftwareDeploymentTest(HeatTestCase):
self.stack = parser.Stack(
self.ctx, 'software_deployment_test_stack',
template.Template(tmpl),
stack_id='42f6f66b-631a-44e7-8d01-e22fb54574a9'
stack_id='42f6f66b-631a-44e7-8d01-e22fb54574a9',
stack_user_project_id='65728b74-cfe7-4f17-9c15-11d4f686e591'
)
self.patchobject(sd.SoftwareDeployment, '_create_user')
@ -189,6 +190,7 @@ class SoftwareDeploymentTest(HeatTestCase):
{'action': 'CREATE',
'config_id': derived_sc.id,
'server_id': '9f1f0e00-05d2-4ca5-8602-95021f19c9d0',
'stack_user_project_id': '65728b74-cfe7-4f17-9c15-11d4f686e591',
'status': 'COMPLETE',
'status_reason': 'Not waiting for outputs signal'},
self.deployments.create.call_args[1])
@ -207,6 +209,7 @@ class SoftwareDeploymentTest(HeatTestCase):
{'action': 'CREATE',
'config_id': derived_sc.id,
'server_id': '9f1f0e00-05d2-4ca5-8602-95021f19c9d0',
'stack_user_project_id': '65728b74-cfe7-4f17-9c15-11d4f686e591',
'status': 'IN_PROGRESS',
'status_reason': 'Deploy data available'},
args)
@ -281,6 +284,7 @@ class SoftwareDeploymentTest(HeatTestCase):
'action': 'DELETE',
'config_id': derived_sc.id,
'server_id': '9f1f0e00-05d2-4ca5-8602-95021f19c9d0',
'stack_user_project_id': '65728b74-cfe7-4f17-9c15-11d4f686e591',
'status': 'IN_PROGRESS',
'status_reason': 'Deploy data available'
}, args)
@ -365,6 +369,7 @@ class SoftwareDeploymentTest(HeatTestCase):
'action': 'SUSPEND',
'config_id': derived_sc.id,
'server_id': '9f1f0e00-05d2-4ca5-8602-95021f19c9d0',
'stack_user_project_id': '65728b74-cfe7-4f17-9c15-11d4f686e591',
'status': 'IN_PROGRESS',
'status_reason': 'Deploy data available'
}, args)
@ -383,6 +388,7 @@ class SoftwareDeploymentTest(HeatTestCase):
'action': 'RESUME',
'config_id': derived_sc.id,
'server_id': '9f1f0e00-05d2-4ca5-8602-95021f19c9d0',
'stack_user_project_id': '65728b74-cfe7-4f17-9c15-11d4f686e591',
'status': 'IN_PROGRESS',
'status_reason': 'Deploy data available'
}, args)

View File

@ -696,12 +696,14 @@ class SqlAlchemyTest(HeatTestCase):
def _deployment_values(self):
tenant_id = self.ctx.tenant_id
stack_user_project_id = str(uuid.uuid4())
config_id = db_api.software_config_create(
self.ctx, {'name': 'config_mysql', 'tenant': tenant_id}).id
server_id = str(uuid.uuid4())
input_values = {'foo': 'fooooo', 'bar': 'baaaaa'}
values = {
'tenant': tenant_id,
'stack_user_project_id': stack_user_project_id,
'config_id': config_id,
'server_id': server_id,
'input_values': input_values
@ -730,13 +732,23 @@ class SqlAlchemyTest(HeatTestCase):
self.assertEqual(values['config_id'], deployment.config_id)
self.assertEqual(values['server_id'], deployment.server_id)
self.assertEqual(values['input_values'], deployment.input_values)
self.ctx.tenant_id = None
self.assertEqual(
values['stack_user_project_id'], deployment.stack_user_project_id)
# assert not found with invalid context tenant
self.ctx.tenant_id = str(uuid.uuid4())
self.assertRaises(
exception.NotFound,
db_api.software_deployment_get,
self.ctx,
deployment_id)
# assert found with stack_user_project_id context tenant
self.ctx.tenant_id = deployment.stack_user_project_id
deployment = db_api.software_deployment_get(self.ctx, deployment_id)
self.assertIsNotNone(deployment)
self.assertEqual(values['tenant'], deployment.tenant)
def test_software_deployment_get_all(self):
self.assertEqual([], db_api.software_deployment_get_all(self.ctx))
values = self._deployment_values()