REST API for software config

This REST API creates one new base path to manage config entities:

* /{tenant_id}/software_configs

Implements: blueprint hot-software-config-rest

Change-Id: Ia1f11adbf9377f0a730ca48afcaaaee7bc3258b6
This commit is contained in:
Steve Baker
2013-11-19 16:37:22 +13:00
committed by JUN JIE NAN
parent 764f2706d8
commit 3c01a005cd
4 changed files with 230 additions and 1 deletions

View File

@@ -47,5 +47,9 @@
"stacks:show": "rule:deny_stack_user",
"stacks:template": "rule:deny_stack_user",
"stacks:update": "rule:deny_stack_user",
"stacks:validate_template": "rule:deny_stack_user"
"stacks:validate_template": "rule:deny_stack_user",
"software_configs:create": "rule:deny_stack_user",
"software_configs:show": "rule:deny_stack_user",
"software_configs:delete": "rule:deny_stack_user"
}

View File

@@ -20,6 +20,7 @@ from heat.api.openstack.v1 import resources
from heat.api.openstack.v1 import events
from heat.api.openstack.v1 import actions
from heat.api.openstack.v1 import build_info
from heat.api.openstack.v1 import software_configs
from heat.common import wsgi
from heat.openstack.common import log as logging
@@ -185,4 +186,26 @@ class API(wsgi.Router):
action='build_info',
conditions={'method': 'GET'})
# Software configs
software_config_resource = software_configs.create_resource(conf)
with mapper.submapper(
controller=software_config_resource,
path_prefix="/{tenant_id}/software_configs"
) as sc_mapper:
sc_mapper.connect("software_config_create",
"",
action="create",
conditions={'method': 'POST'})
sc_mapper.connect("software_config_show",
"/{config_id}",
action="show",
conditions={'method': 'GET'})
sc_mapper.connect("software_config_delete",
"/{config_id}",
action="delete",
conditions={'method': 'DELETE'})
super(API, self).__init__(mapper)

View File

@@ -0,0 +1,84 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 webob import exc
from heat.api.openstack.v1 import util
from heat.common import wsgi
from heat.rpc import client as rpc_client
class SoftwareConfigController(object):
"""
WSGI controller for Software config in Heat v1 API
Implements the API actions
"""
REQUEST_SCOPE = 'software_configs'
def __init__(self, options):
self.options = options
self.engine = rpc_client.EngineClient()
def default(self, req, **args):
raise exc.HTTPNotFound()
@util.policy_enforce
def show(self, req, config_id):
"""
Gets detailed information for a software config
"""
sc = self.engine.show_software_config(
req.context, config_id)
return {'software_config': sc}
@util.policy_enforce
def create(self, req, body):
"""
Create a new software config
"""
create_data = {
'name': body.get('name'),
'group': body.get('group'),
'config': body.get('config'),
'inputs': body.get('inputs'),
'outputs': body.get('outputs'),
'options': body.get('options'),
}
sc = self.engine.create_software_config(
req.context, **create_data)
return {'software_config': sc}
@util.policy_enforce
def delete(self, req, config_id):
"""
Delete an existing software config
"""
res = self.engine.delete_software_config(req.context, config_id)
if res is not None:
raise exc.HTTPBadRequest(res['Error'])
raise exc.HTTPNoContent()
def create_resource(options):
"""
Software configs resource factory method.
"""
deserializer = wsgi.JSONRequestDeserializer()
serializer = wsgi.JSONResponseSerializer()
return wsgi.Resource(
SoftwareConfigController(options), deserializer, serializer)

View File

@@ -36,6 +36,7 @@ import heat.api.openstack.v1.resources as resources
import heat.api.openstack.v1.events as events
import heat.api.openstack.v1.actions as actions
import heat.api.openstack.v1.build_info as build_info
import heat.api.openstack.v1.software_configs as software_configs
from heat.tests import utils
import heat.api.middleware.fault as fault
@@ -2958,6 +2959,37 @@ class RoutesTest(HeatTestCase):
'event_id': 'dddd'
})
def test_software_configs(self):
self.assertRoute(
self.m,
'/aaaa/software_configs',
'POST',
'create',
'SoftwareConfigController',
{
'tenant_id': 'aaaa'
})
self.assertRoute(
self.m,
'/aaaa/software_configs/bbbb',
'GET',
'show',
'SoftwareConfigController',
{
'tenant_id': 'aaaa',
'config_id': 'bbbb'
})
self.assertRoute(
self.m,
'/aaaa/software_configs/bbbb',
'DELETE',
'delete',
'SoftwareConfigController',
{
'tenant_id': 'aaaa',
'config_id': 'bbbb'
})
def test_build_info(self):
self.assertRoute(
self.m,
@@ -3202,3 +3234,89 @@ class BuildInfoControllerTest(ControllerTest, HeatTestCase):
req, tenant_id=self.tenant)
self.assertEqual(403, resp.status_int)
self.assertIn('403 Forbidden', str(resp))
class SoftwareConfigControllerTest(ControllerTest, HeatTestCase):
def setUp(self):
super(SoftwareConfigControllerTest, self).setUp()
self.controller = software_configs.SoftwareConfigController({})
def test_default(self):
self.assertRaises(
webob.exc.HTTPNotFound, self.controller.default, None)
@mock.patch.object(policy.Enforcer, 'enforce')
def test_show(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'show')
config_id = 'a45559cd-8736-4375-bc39-d6a7bb62ade2'
req = self._get('/software_configs/%s' % config_id)
return_value = {
'id': config_id,
'name': 'config_mysql',
'group': 'Heat::Shell',
'config': '#!/bin/bash',
'inputs': [],
'ouputs': [],
'options': []}
expected = {'software_config': return_value}
with mock.patch.object(
self.controller.engine,
'show_software_config',
return_value=return_value):
resp = self.controller.show(
req, config_id=config_id, tenant_id=self.tenant)
self.assertEqual(expected, resp)
@mock.patch.object(policy.Enforcer, 'enforce')
def test_create(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'create')
body = {
'name': 'config_mysql',
'group': 'Heat::Shell',
'config': '#!/bin/bash',
'inputs': [],
'ouputs': [],
'options': []}
return_value = body.copy()
config_id = 'a45559cd-8736-4375-bc39-d6a7bb62ade2'
return_value['id'] = config_id
req = self._post('/software_configs', json.dumps(body))
expected = {'software_config': return_value}
with mock.patch.object(
self.controller.engine,
'create_software_config',
return_value=return_value):
resp = self.controller.create(
req, body=body, tenant_id=self.tenant)
self.assertEqual(expected, resp)
@mock.patch.object(policy.Enforcer, 'enforce')
def test_delete(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'delete')
config_id = 'a45559cd-8736-4375-bc39-d6a7bb62ade2'
req = self._delete('/software_configs/%s' % config_id)
return_value = None
with mock.patch.object(
self.controller.engine,
'delete_software_config',
return_value=return_value):
self.assertRaises(
webob.exc.HTTPNoContent, self.controller.delete,
req, config_id=config_id, tenant_id=self.tenant)
@mock.patch.object(policy.Enforcer, 'enforce')
def test_delete_error(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'delete')
config_id = 'a45559cd-8736-4375-bc39-d6a7bb62ade2'
req = self._delete('/software_configs/%s' % config_id)
return_value = {'Error': 'something wrong'}
with mock.patch.object(
self.controller.engine,
'delete_software_config',
return_value=return_value):
self.assertRaises(
webob.exc.HTTPBadRequest, self.controller.delete,
req, config_id=config_id, tenant_id=self.tenant)