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:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
84
heat/api/openstack/v1/software_configs.py
Normal file
84
heat/api/openstack/v1/software_configs.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user