5566e6f2c8
In Python3, dict.keys() returns a view object rather than a list. This
behaves differently in that changes to the dict also modify the view, and
in that the view type interacts with various operators in different ways to
lists.
One universally correct transformation to preserve Python2 behaviour in
Python3 would be to replace all instances of d.keys() with
list(six.iterkeys(d)), and indeed we did. However, like many automatic
transformations the results are usually unsightly, invariably inefficient,
and frequently absurd. Not least because list(d.keys()) and indeed list(d)
are also equivalent.
This patch changes to using the simplest correct method of accessing the
data we want in each case.
This reverts or rewrites most of commit
4ace95ad47
.
Change-Id: Iba3cf48246d8cbc958d8fb577cd700a218b0bebf
218 lines
7.8 KiB
Python
218 lines
7.8 KiB
Python
#
|
|
# 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 os
|
|
|
|
import mock
|
|
import re
|
|
import six
|
|
import yaml
|
|
|
|
from heat.common import config
|
|
from heat.common import exception
|
|
from heat.common import template_format
|
|
from heat.engine.clients.os import neutron
|
|
from heat.tests import common
|
|
from heat.tests import utils
|
|
|
|
|
|
class JsonToYamlTest(common.HeatTestCase):
|
|
|
|
def setUp(self):
|
|
super(JsonToYamlTest, self).setUp()
|
|
self.expected_test_count = 2
|
|
self.longMessage = True
|
|
self.maxDiff = None
|
|
|
|
def test_convert_all_templates(self):
|
|
path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
|
'templates')
|
|
|
|
template_test_count = 0
|
|
for (json_str,
|
|
yml_str) in self.convert_all_json_to_yaml(path):
|
|
|
|
self.compare_json_vs_yaml(json_str, yml_str)
|
|
template_test_count += 1
|
|
if template_test_count >= self.expected_test_count:
|
|
break
|
|
|
|
self.assertTrue(template_test_count >= self.expected_test_count,
|
|
'Expected at least %d templates to be tested, not %d' %
|
|
(self.expected_test_count, template_test_count))
|
|
|
|
def compare_json_vs_yaml(self, json_str, yml_str):
|
|
yml = template_format.parse(yml_str)
|
|
|
|
self.assertEqual(u'2012-12-12', yml[u'HeatTemplateFormatVersion'])
|
|
self.assertNotIn(u'AWSTemplateFormatVersion', yml)
|
|
del(yml[u'HeatTemplateFormatVersion'])
|
|
|
|
jsn = template_format.parse(json_str)
|
|
|
|
if u'AWSTemplateFormatVersion' in jsn:
|
|
del(jsn[u'AWSTemplateFormatVersion'])
|
|
|
|
self.assertEqual(yml, jsn)
|
|
|
|
def convert_all_json_to_yaml(self, dirpath):
|
|
for path in os.listdir(dirpath):
|
|
if not path.endswith('.template') and not path.endswith('.json'):
|
|
continue
|
|
with open(os.path.join(dirpath, path), 'r') as f:
|
|
json_str = f.read()
|
|
|
|
yml_str = template_format.convert_json_to_yaml(json_str)
|
|
yield (json_str, yml_str)
|
|
|
|
def test_integer_only_keys_get_translated_correctly(self):
|
|
path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
|
'templates/WordPress_Single_Instance.template')
|
|
with open(path, 'r') as f:
|
|
json_str = f.read()
|
|
yml_str = template_format.convert_json_to_yaml(json_str)
|
|
match = re.search('[\s,{]\d+\s*:', yml_str)
|
|
# Check that there are no matches of integer-only keys
|
|
# lacking explicit quotes
|
|
self.assertIsNone(match)
|
|
|
|
|
|
class YamlMinimalTest(common.HeatTestCase):
|
|
|
|
def _parse_template(self, tmpl_str, msg_str):
|
|
parse_ex = self.assertRaises(ValueError,
|
|
template_format.parse,
|
|
tmpl_str)
|
|
self.assertIn(msg_str, six.text_type(parse_ex))
|
|
|
|
def test_long_yaml(self):
|
|
template = {'HeatTemplateFormatVersion': '2012-12-12'}
|
|
config.cfg.CONF.set_override('max_template_size', 10,
|
|
enforce_type=True)
|
|
template['Resources'] = ['a'] * int(
|
|
config.cfg.CONF.max_template_size / 3)
|
|
limit = config.cfg.CONF.max_template_size
|
|
long_yaml = yaml.safe_dump(template)
|
|
self.assertTrue(len(long_yaml) > limit)
|
|
ex = self.assertRaises(exception.RequestLimitExceeded,
|
|
template_format.parse, long_yaml)
|
|
msg = ('Request limit exceeded: Template size (%(actual_len)s '
|
|
'bytes) exceeds maximum allowed size (%(limit)s bytes).') % {
|
|
'actual_len': len(str(long_yaml)),
|
|
'limit': config.cfg.CONF.max_template_size}
|
|
self.assertEqual(msg, six.text_type(ex))
|
|
|
|
def test_parse_no_version_format(self):
|
|
yaml = ''
|
|
self._parse_template(yaml, 'Template format version not found')
|
|
yaml2 = '''Parameters: {}
|
|
Mappings: {}
|
|
Resources: {}
|
|
Outputs: {}
|
|
'''
|
|
self._parse_template(yaml2, 'Template format version not found')
|
|
|
|
def test_parse_string_template(self):
|
|
tmpl_str = 'just string'
|
|
msg = 'The template is not a JSON object or YAML mapping.'
|
|
self._parse_template(tmpl_str, msg)
|
|
|
|
def test_parse_invalid_yaml_and_json_template(self):
|
|
tmpl_str = '{test'
|
|
msg = 'line 1, column 1'
|
|
self._parse_template(tmpl_str, msg)
|
|
|
|
def test_parse_json_document(self):
|
|
tmpl_str = '["foo" , "bar"]'
|
|
msg = 'The template is not a JSON object or YAML mapping.'
|
|
self._parse_template(tmpl_str, msg)
|
|
|
|
def test_parse_empty_json_template(self):
|
|
tmpl_str = '{}'
|
|
msg = 'Template format version not found'
|
|
self._parse_template(tmpl_str, msg)
|
|
|
|
def test_parse_yaml_template(self):
|
|
tmpl_str = 'heat_template_version: 2013-05-23'
|
|
expected = {'heat_template_version': '2013-05-23'}
|
|
self.assertEqual(expected, template_format.parse(tmpl_str))
|
|
|
|
|
|
class YamlParseExceptions(common.HeatTestCase):
|
|
|
|
scenarios = [
|
|
('scanner', dict(raised_exception=yaml.scanner.ScannerError())),
|
|
('parser', dict(raised_exception=yaml.parser.ParserError())),
|
|
('reader',
|
|
dict(raised_exception=yaml.reader.ReaderError(
|
|
'', 42, six.b('x'), '', ''))),
|
|
]
|
|
|
|
def test_parse_to_value_exception(self):
|
|
text = 'not important'
|
|
|
|
with mock.patch.object(yaml, 'load') as yaml_loader:
|
|
yaml_loader.side_effect = self.raised_exception
|
|
|
|
err = self.assertRaises(ValueError,
|
|
template_format.parse, text)
|
|
|
|
self.assertIn('Error parsing template: ', six.text_type(err))
|
|
|
|
|
|
class JsonYamlResolvedCompareTest(common.HeatTestCase):
|
|
|
|
def setUp(self):
|
|
super(JsonYamlResolvedCompareTest, self).setUp()
|
|
self.longMessage = True
|
|
self.maxDiff = None
|
|
|
|
def load_template(self, file_name):
|
|
filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
|
'templates', file_name)
|
|
f = open(filepath)
|
|
t = template_format.parse(f.read())
|
|
f.close()
|
|
return t
|
|
|
|
def compare_stacks(self, json_file, yaml_file, parameters):
|
|
t1 = self.load_template(json_file)
|
|
t2 = self.load_template(yaml_file)
|
|
del(t1[u'AWSTemplateFormatVersion'])
|
|
t1[u'HeatTemplateFormatVersion'] = t2[u'HeatTemplateFormatVersion']
|
|
stack1 = utils.parse_stack(t1, parameters)
|
|
stack2 = utils.parse_stack(t2, parameters)
|
|
|
|
# compare resources separately so that resolved static data
|
|
# is compared
|
|
t1nr = dict(stack1.t.t)
|
|
del(t1nr['Resources'])
|
|
|
|
t2nr = dict(stack2.t.t)
|
|
del(t2nr['Resources'])
|
|
self.assertEqual(t1nr, t2nr)
|
|
|
|
self.assertEqual(set(stack1), set(stack2))
|
|
for key in stack1:
|
|
self.assertEqual(stack1[key].t, stack2[key].t)
|
|
|
|
def test_neutron_resolved(self):
|
|
self.patchobject(neutron.NeutronClientPlugin, 'has_extension',
|
|
return_value=True)
|
|
self.compare_stacks('Neutron.template', 'Neutron.yaml', {})
|
|
|
|
def test_wordpress_resolved(self):
|
|
self.compare_stacks('WordPress_Single_Instance.template',
|
|
'WordPress_Single_Instance.yaml',
|
|
{'KeyName': 'test'})
|