Add template validation support to orchestration
This patch adds the support to validate stack templates based on V1 API[1]. Due to the design of the API, it is not like any of a regular resource you will operate in other services. We have to live with it. [1] http://developer.openstack.org/api-ref/orchestration/v1/index.html?expanded=#validate-template Change-Id: I2277943d2413459d84a0041ef6eeb2506b2611a6
This commit is contained in:
parent
49db6006dd
commit
71a0d1615d
|
@ -10,10 +10,12 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack.orchestration.v1 import resource as _resource
|
||||
from openstack.orchestration.v1 import software_config as _sc
|
||||
from openstack.orchestration.v1 import software_deployment as _sd
|
||||
from openstack.orchestration.v1 import stack as _stack
|
||||
from openstack.orchestration.v1 import template as _template
|
||||
from openstack import proxy2
|
||||
|
||||
|
||||
|
@ -249,3 +251,33 @@ class Proxy(proxy2.BaseProxy):
|
|||
"""
|
||||
return self._update(_sd.SoftwareDeployment, software_deployment,
|
||||
**attrs)
|
||||
|
||||
def validate_template(self, template, environment=None, template_url=None,
|
||||
ignore_errors=None):
|
||||
"""Validates a template.
|
||||
|
||||
:param template: The stack template on which the validation is
|
||||
performed.
|
||||
:param environment: A JSON environment for the stack, if provided.
|
||||
:param template_url: A URI to the location containing the stack
|
||||
template for validation. This parameter is only
|
||||
required if the ``template`` parameter is None.
|
||||
This parameter is ignored if ``template`` is
|
||||
specified.
|
||||
:param ignore_errors: A string containing comma separated error codes
|
||||
to ignore. Currently the only valid error code
|
||||
is '99001'.
|
||||
:returns: The result of template validation.
|
||||
:raises: :class:`~openstack.exceptions.InvalidRequest` if neither
|
||||
`template` not `template_url` is provided.
|
||||
:raises: :class:`~openstack.exceptions.HttpException` if the template
|
||||
fails the validation.
|
||||
"""
|
||||
if template is None and template_url is None:
|
||||
raise exceptions.InvalidRequest(
|
||||
"'template_url' must be specified when template is None")
|
||||
|
||||
tmpl = _template.Template.new()
|
||||
return tmpl.validate(self.session, template, environment=environment,
|
||||
template_url=template_url,
|
||||
ignore_errors=ignore_errors)
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# 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 six.moves.urllib import parse
|
||||
|
||||
from openstack.orchestration import orchestration_service
|
||||
from openstack import resource2 as resource
|
||||
|
||||
|
||||
class Template(resource.Resource):
|
||||
service = orchestration_service.OrchestrationService()
|
||||
|
||||
# capabilities
|
||||
allow_create = False
|
||||
allow_list = False
|
||||
allow_retrieve = False
|
||||
allow_delete = False
|
||||
allow_update = False
|
||||
|
||||
# Properties
|
||||
#: The description specified in the template
|
||||
description = resource.Body('Description')
|
||||
#: Key and value pairs that contain template parameters
|
||||
parameters = resource.Body('Parameters', type=dict)
|
||||
#: A list of parameter groups each contains a lsit of parameter names.
|
||||
parameter_groups = resource.Body('ParameterGroups', type=list)
|
||||
|
||||
def validate(self, session, template, environment=None, template_url=None,
|
||||
ignore_errors=None):
|
||||
url = '/validate'
|
||||
|
||||
body = {'template': template}
|
||||
if environment is not None:
|
||||
body['environment'] = environment
|
||||
if template_url is not None:
|
||||
body['template_url'] = template_url
|
||||
if ignore_errors:
|
||||
qry = parse.urlencode({'ignore_errors': ignore_errors})
|
||||
url = '?'.join([url, qry])
|
||||
|
||||
resp = session.post(url, endpoint_filter=self.service, json=body)
|
||||
self._translate_response(resp)
|
||||
return self
|
|
@ -19,6 +19,7 @@ from openstack.orchestration.v1 import resource
|
|||
from openstack.orchestration.v1 import software_config as sc
|
||||
from openstack.orchestration.v1 import software_deployment as sd
|
||||
from openstack.orchestration.v1 import stack
|
||||
from openstack.orchestration.v1 import template
|
||||
from openstack.tests.unit import test_proxy_base2
|
||||
|
||||
|
||||
|
@ -131,3 +132,24 @@ class TestOrchestrationProxy(test_proxy_base2.TestProxyBase):
|
|||
sd.SoftwareDeployment, True)
|
||||
self.verify_delete(self.proxy.delete_software_deployment,
|
||||
sd.SoftwareDeployment, False)
|
||||
|
||||
@mock.patch.object(template.Template, 'validate')
|
||||
def test_validate_template(self, mock_validate):
|
||||
tmpl = mock.Mock()
|
||||
env = mock.Mock()
|
||||
tmpl_url = 'A_URI'
|
||||
ignore_errors = 'a_string'
|
||||
|
||||
res = self.proxy.validate_template(tmpl, env, tmpl_url, ignore_errors)
|
||||
|
||||
mock_validate.assert_called_once_with(
|
||||
self.proxy.session, tmpl, environment=env, template_url=tmpl_url,
|
||||
ignore_errors=ignore_errors)
|
||||
self.assertEqual(mock_validate.return_value, res)
|
||||
|
||||
def test_validate_template_invalid_request(self):
|
||||
err = self.assertRaises(exceptions.InvalidRequest,
|
||||
self.proxy.validate_template,
|
||||
None, template_url=None)
|
||||
self.assertEqual("'template_url' must be specified when template is "
|
||||
"None", six.text_type(err))
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# 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 mock
|
||||
import testtools
|
||||
|
||||
from openstack.orchestration.v1 import template
|
||||
from openstack import resource2 as resource
|
||||
|
||||
|
||||
FAKE = {
|
||||
'Description': 'Blah blah',
|
||||
'Parameters': {
|
||||
'key_name': {
|
||||
'type': 'string'
|
||||
}
|
||||
},
|
||||
'ParameterGroups': [{
|
||||
'label': 'Group 1',
|
||||
'parameters': ['key_name']
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
class TestTemplate(testtools.TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
sot = template.Template()
|
||||
self.assertEqual('orchestration', sot.service.service_type)
|
||||
self.assertFalse(sot.allow_create)
|
||||
self.assertFalse(sot.allow_get)
|
||||
self.assertFalse(sot.allow_update)
|
||||
self.assertFalse(sot.allow_delete)
|
||||
self.assertFalse(sot.allow_list)
|
||||
|
||||
def test_make_it(self):
|
||||
sot = template.Template(**FAKE)
|
||||
self.assertEqual(FAKE['Description'], sot.description)
|
||||
self.assertEqual(FAKE['Parameters'], sot.parameters)
|
||||
self.assertEqual(FAKE['ParameterGroups'], sot.parameter_groups)
|
||||
|
||||
@mock.patch.object(resource.Resource, '_translate_response')
|
||||
def test_validate(self, mock_translate):
|
||||
sess = mock.Mock()
|
||||
sot = template.Template()
|
||||
tmpl = mock.Mock()
|
||||
body = {'template': tmpl}
|
||||
|
||||
sot.validate(sess, tmpl)
|
||||
|
||||
sess.post.assert_called_once_with(
|
||||
'/validate', endpoint_filter=sot.service, json=body)
|
||||
mock_translate.assert_called_once_with(sess.post.return_value)
|
||||
|
||||
@mock.patch.object(resource.Resource, '_translate_response')
|
||||
def test_validate_with_env(self, mock_translate):
|
||||
sess = mock.Mock()
|
||||
sot = template.Template()
|
||||
tmpl = mock.Mock()
|
||||
env = mock.Mock()
|
||||
body = {'template': tmpl, 'environment': env}
|
||||
|
||||
sot.validate(sess, tmpl, environment=env)
|
||||
|
||||
sess.post.assert_called_once_with(
|
||||
'/validate', endpoint_filter=sot.service, json=body)
|
||||
mock_translate.assert_called_once_with(sess.post.return_value)
|
||||
|
||||
@mock.patch.object(resource.Resource, '_translate_response')
|
||||
def test_validate_with_template_url(self, mock_translate):
|
||||
sess = mock.Mock()
|
||||
sot = template.Template()
|
||||
template_url = 'http://host1'
|
||||
body = {'template': None, 'template_url': template_url}
|
||||
|
||||
sot.validate(sess, None, template_url=template_url)
|
||||
|
||||
sess.post.assert_called_once_with(
|
||||
'/validate', endpoint_filter=sot.service, json=body)
|
||||
mock_translate.assert_called_once_with(sess.post.return_value)
|
||||
|
||||
@mock.patch.object(resource.Resource, '_translate_response')
|
||||
def test_validate_with_ignore_errors(self, mock_translate):
|
||||
sess = mock.Mock()
|
||||
sot = template.Template()
|
||||
tmpl = mock.Mock()
|
||||
body = {'template': tmpl}
|
||||
|
||||
sot.validate(sess, tmpl, ignore_errors='123,456')
|
||||
|
||||
sess.post.assert_called_once_with(
|
||||
'/validate?ignore_errors=123%2C456',
|
||||
endpoint_filter=sot.service, json=body)
|
||||
mock_translate.assert_called_once_with(sess.post.return_value)
|
Loading…
Reference in New Issue