Move merge_environment to environment_util module

This moves the merge_environment utility function
from service.py to environment_util.py.

Change-Id: Ia005cf47d5e655e60359f8da397a712e749ce13c
Blueprint: environment-merging
This commit is contained in:
rabi 2016-08-04 10:10:51 +05:30
parent 36a0ad29e5
commit 59b2a559ec
7 changed files with 100 additions and 81 deletions

View File

@ -10,6 +10,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import collections
from heat.common import environment_format
ALLOWED_PARAM_MERGE_STRATEGIES = (OVERWRITE, MERGE, DEEP_MERGE) = ( ALLOWED_PARAM_MERGE_STRATEGIES = (OVERWRITE, MERGE, DEEP_MERGE) = (
'overwrite', 'merge', 'deep_merge') 'overwrite', 'merge', 'deep_merge')
@ -27,3 +30,38 @@ def get_param_merge_strategy(merge_strategies, param_key):
return merge_strategy return merge_strategy
return env_default return env_default
def deep_update(old, new):
'''Merge nested dictionaries.'''
for k, v in new.items():
if isinstance(v, collections.Mapping):
r = deep_update(old.get(k, {}), v)
old[k] = r
else:
old[k] = new[k]
return old
def merge_environments(environment_files, files, params):
"""Merges environment files into the stack input parameters.
If a list of environment files have been specified, this call will
pull the contents of each from the files dict, parse them as
environments, and merge them into the stack input params. This
behavior is the same as earlier versions of the Heat client that
performed this params population client-side.
:param environment_files: ordered names of the environment files
found in the files dict
:type environment_files: list or None
:param files: mapping of stack filenames to contents
:type files: dict
:param params: parameters describing the stack
:type dict:
"""
if environment_files:
for filename in environment_files:
raw_env = files[filename]
parsed_env = environment_format.parse(raw_env)
deep_update(params, parsed_env)

View File

@ -33,6 +33,7 @@ import webob
from heat.common import context from heat.common import context
from heat.common import environment_format as env_fmt from heat.common import environment_format as env_fmt
from heat.common import environment_util as env_util
from heat.common import exception from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.common.i18n import _LE from heat.common.i18n import _LE
@ -69,8 +70,6 @@ from heat.objects import watch_data
from heat.objects import watch_rule from heat.objects import watch_rule
from heat.rpc import api as rpc_api from heat.rpc import api as rpc_api
from heat.rpc import worker_api as rpc_worker_api from heat.rpc import worker_api as rpc_worker_api
from heatclient.common import environment_format
from heatclient.common import template_utils
cfg.CONF.import_opt('engine_life_check_timeout', 'heat.common.config') cfg.CONF.import_opt('engine_life_check_timeout', 'heat.common.config')
cfg.CONF.import_opt('max_resources_per_stack', 'heat.common.config') cfg.CONF.import_opt('max_resources_per_stack', 'heat.common.config')
@ -680,7 +679,7 @@ class EngineService(service.Service):
tmpl = templatem.Template.load(cnxt, template_id) tmpl = templatem.Template.load(cnxt, template_id)
env = tmpl.env env = tmpl.env
else: else:
self._merge_environments(environment_files, files, params) env_util.merge_environments(environment_files, files, params)
env = environment.Environment(params) env = environment.Environment(params)
tmpl = templatem.Template(template, files=files, env=env) tmpl = templatem.Template(template, files=files, env=env)
self._validate_new_stack(cnxt, stack_name, tmpl) self._validate_new_stack(cnxt, stack_name, tmpl)
@ -701,30 +700,6 @@ class EngineService(service.Service):
env.registry.log_resource_info(prefix=stack_name) env.registry.log_resource_info(prefix=stack_name)
return stack return stack
@staticmethod
def _merge_environments(environment_files, files, params):
"""Merges environment files into the stack input parameters.
If a list of environment files have been specified, this call will
pull the contents of each from the files dict, parse them as
environments, and merge them into the stack input params. This
behavior is the same as earlier versions of the Heat client that
performed this params population client-side.
:param environment_files: ordered names of the environment files
found in the files dict
:type environment_files: list or None
:param files: mapping of stack filenames to contents
:type files: dict
:param params: parameters describing the stack
:type dict:
"""
if environment_files:
for filename in environment_files:
raw_env = files[filename]
parsed_env = environment_format.parse(raw_env)
template_utils.deep_update(params, parsed_env)
@context.request_context @context.request_context
def preview_stack(self, cnxt, stack_name, template, params, files, def preview_stack(self, cnxt, stack_name, template, params, files,
args, environment_files=None): args, environment_files=None):
@ -969,7 +944,7 @@ class EngineService(service.Service):
:param template_id: the ID of a pre-stored template in the DB :param template_id: the ID of a pre-stored template in the DB
""" """
# Handle server-side environment file resolution # Handle server-side environment file resolution
self._merge_environments(environment_files, files, params) env_util.merge_environments(environment_files, files, params)
# Get the database representation of the existing stack # Get the database representation of the existing stack
db_stack = self._get_stack(cnxt, stack_identity) db_stack = self._get_stack(cnxt, stack_identity)
@ -1026,7 +1001,7 @@ class EngineService(service.Service):
heat-api process if using a template-url. heat-api process if using a template-url.
""" """
# Handle server-side environment file resolution # Handle server-side environment file resolution
self._merge_environments(environment_files, files, params) env_util.merge_environments(environment_files, files, params)
# Get the database representation of the existing stack # Get the database representation of the existing stack
db_stack = self._get_stack(cnxt, stack_identity) db_stack = self._get_stack(cnxt, stack_identity)
@ -1206,7 +1181,7 @@ class EngineService(service.Service):
service_check_defer = True service_check_defer = True
self._merge_environments(environment_files, files, params) env_util.merge_environments(environment_files, files, params)
env = environment.Environment(params) env = environment.Environment(params)
tmpl = templatem.Template(template, files=files, env=env) tmpl = templatem.Template(template, files=files, env=env)
try: try:

View File

@ -411,50 +411,3 @@ class ServiceEngineTest(common.HeatTestCase):
self.eng.start() self.eng.start()
self.assertEqual(cfg.CONF.executor_thread_pool_size, self.assertEqual(cfg.CONF.executor_thread_pool_size,
cfg.CONF.database.max_overflow) cfg.CONF.database.max_overflow)
def test_merge_environments(self):
# Setup
params = {'parameters': {
'p0': 'CORRECT',
'p1': 'INCORRECT',
'p2': 'INCORRECT'}
}
env_1 = '''
{'parameters' : {
'p1': 'CORRECT',
'p2': 'INCORRECT-ENV-1',
}}'''
env_2 = '''
{'parameters': {
'p2': 'CORRECT'
}}'''
files = {'env_1': env_1, 'env_2': env_2}
environment_files = ['env_1', 'env_2']
# Test
self.eng._merge_environments(environment_files, files, params)
# Verify
expected = {'parameters': {
'p0': 'CORRECT',
'p1': 'CORRECT',
'p2': 'CORRECT',
}}
self.assertEqual(expected, params)
def test_merge_environments_no_env_files(self):
params = {'parameters': {'p0': 'CORRECT'}}
env_1 = '''
{'parameters' : {
'p0': 'INCORRECT',
}}'''
files = {'env_1': env_1}
# Test - Should ignore env_1 in files
self.eng._merge_environments(None, files, params)
# Verify
expected = {'parameters': {'p0': 'CORRECT'}}
self.assertEqual(expected, params)

View File

@ -16,6 +16,7 @@ from oslo_messaging.rpc import dispatcher
from oslo_service import threadgroup from oslo_service import threadgroup
import six import six
from heat.common import environment_util as env_util
from heat.common import exception from heat.common import exception
from heat.engine.clients.os import glance from heat.engine.clients.os import glance
from heat.engine.clients.os import nova from heat.engine.clients.os import nova
@ -54,7 +55,7 @@ class StackCreateTest(common.HeatTestCase):
mock_env = self.patchobject(environment, 'Environment', mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env) return_value=stk.env)
mock_stack = self.patchobject(stack, 'Stack', return_value=stk) mock_stack = self.patchobject(stack, 'Stack', return_value=stk)
mock_merge = self.patchobject(self.man, '_merge_environments') mock_merge = self.patchobject(env_util, 'merge_environments')
result = self.man.create_stack(self.ctx, stack_name, result = self.man.create_stack(self.ctx, stack_name,
template, params, None, {}, template, params, None, {},
environment_files=environment_files) environment_files=environment_files)

View File

@ -18,6 +18,7 @@ from oslo_config import cfg
from oslo_messaging.rpc import dispatcher from oslo_messaging.rpc import dispatcher
import six import six
from heat.common import environment_util as env_util
from heat.common import exception from heat.common import exception
from heat.common import messaging from heat.common import messaging
from heat.common import service_utils from heat.common import service_utils
@ -121,7 +122,7 @@ class ServiceStackUpdateTest(common.HeatTestCase):
self.patchobject(eventlet.queue, 'LightQueue', self.patchobject(eventlet.queue, 'LightQueue',
return_value=mock.Mock()) return_value=mock.Mock())
mock_merge = self.patchobject(self.man, '_merge_environments') mock_merge = self.patchobject(env_util, 'merge_environments')
# Test # Test
environment_files = ['env_1'] environment_files = ['env_1']
@ -991,7 +992,7 @@ resources:
mock_env = self.patchobject(environment, 'Environment', mock_env = self.patchobject(environment, 'Environment',
return_value=stk.env) return_value=stk.env)
mock_validate = self.patchobject(stk, 'validate', return_value=None) mock_validate = self.patchobject(stk, 'validate', return_value=None)
mock_merge = self.patchobject(self.man, '_merge_environments') mock_merge = self.patchobject(env_util, 'merge_environments')
# Patch _resolve_all_attributes or it tries to call novaclient # Patch _resolve_all_attributes or it tries to call novaclient
self.patchobject(resource.Resource, '_resolve_all_attributes', self.patchobject(resource.Resource, '_resolve_all_attributes',

View File

@ -48,3 +48,53 @@ class TestEnvironmentUtil(common.HeatTestCase):
param_strategy = env_util.get_param_merge_strategy(merge_strategies, param_strategy = env_util.get_param_merge_strategy(merge_strategies,
'param1') 'param1')
self.assertEqual(env_util.OVERWRITE, param_strategy) self.assertEqual(env_util.OVERWRITE, param_strategy)
class TestMergeEnvironments(common.HeatTestCase):
def test_merge_environments(self):
# Setup
params = {'parameters': {
'p0': 'CORRECT',
'p1': 'INCORRECT',
'p2': 'INCORRECT'}
}
env_1 = '''
{'parameters' : {
'p1': 'CORRECT',
'p2': 'INCORRECT-ENV-1',
}}'''
env_2 = '''
{'parameters': {
'p2': 'CORRECT'
}}'''
files = {'env_1': env_1, 'env_2': env_2}
environment_files = ['env_1', 'env_2']
# Test
env_util.merge_environments(environment_files, files, params)
# Verify
expected = {'parameters': {
'p0': 'CORRECT',
'p1': 'CORRECT',
'p2': 'CORRECT',
}}
self.assertEqual(expected, params)
def test_merge_environments_no_env_files(self):
params = {'parameters': {'p0': 'CORRECT'}}
env_1 = '''
{'parameters' : {
'p0': 'INCORRECT',
}}'''
files = {'env_1': env_1}
# Test - Should ignore env_1 in files
env_util.merge_environments(None, files, params)
# Verify
expected = {'parameters': {'p0': 'CORRECT'}}
self.assertEqual(expected, params)

View File

@ -21,6 +21,7 @@ from oslo_serialization import jsonutils as json
import six import six
from heat.common import context from heat.common import context
from heat.common import environment_util as env_util
from heat.common import exception from heat.common import exception
from heat.common import identifier from heat.common import identifier
from heat.common import template_format from heat.common import template_format
@ -1165,7 +1166,7 @@ class StackServiceTest(common.HeatTestCase):
self._preview_stack) self._preview_stack)
self.assertEqual(exception.StackValidationFailed, ex.exc_info[0]) self.assertEqual(exception.StackValidationFailed, ex.exc_info[0])
@mock.patch.object(service.EngineService, '_merge_environments') @mock.patch.object(env_util, 'merge_environments')
def test_preview_environment_files(self, mock_merge): def test_preview_environment_files(self, mock_merge):
# Setup # Setup
environment_files = ['env_1'] environment_files = ['env_1']