diff --git a/unit_tests/test_zaza_charm_lifecycle_prepare.py b/unit_tests/test_zaza_charm_lifecycle_prepare.py index 8f817f4..bb304e4 100644 --- a/unit_tests/test_zaza_charm_lifecycle_prepare.py +++ b/unit_tests/test_zaza_charm_lifecycle_prepare.py @@ -9,54 +9,54 @@ class TestCharmLifecyclePrepare(ut_utils.BaseTestCase): MODEL_CONFIG_DEFAULTS = lc_prepare.MODEL_DEFAULTS - def base_get_model_settings(self, env, expect): + def base_parse_option_list_string(self, env, expect): with mock.patch.dict(lc_prepare.os.environ, env): self.assertEqual(lc_prepare.get_model_settings(), expect) + def test_parse_option_list_string_empty_config(self): + self.assertEqual( + lc_prepare.parse_option_list_string(option_list=""), + {}) + + def test_parse_option_list_string_single_value(self): + self.assertEqual( + lc_prepare.parse_option_list_string( + option_list='image-stream=released'), + {'image-stream': 'released'}) + + def test_parse_option_list_string_multiple_values(self): + self.assertEqual( + lc_prepare.parse_option_list_string( + option_list='image-stream=released;no-proxy=jujucharms.com'), + { + 'image-stream': 'released', + 'no-proxy': 'jujucharms.com'}) + + def test_parse_option_list_string_whitespace(self): + self.assertEqual( + lc_prepare.parse_option_list_string( + option_list=' test-mode= false ; image-stream= released'), + { + 'test-mode': 'false', + 'image-stream': 'released'}) + def test_get_model_settings_no_config(self): - expect_config = copy.deepcopy(self.MODEL_CONFIG_DEFAULTS) - self.base_get_model_settings({}, expect_config) - - def test_get_model_settings_empty_config(self): - expect_config = copy.deepcopy(self.MODEL_CONFIG_DEFAULTS) - self.base_get_model_settings({'MODEL_SETTINGS': ''}, expect_config) - - def test_get_model_settings_single_value(self): - expect_config = copy.deepcopy(self.MODEL_CONFIG_DEFAULTS) - expect_config.update({'virt-type': 'kvm'}) - self.base_get_model_settings( - {'MODEL_SETTINGS': 'virt-type=kvm'}, - expect_config) - - def test_get_model_settings_multiple_values(self): - expect_config = copy.deepcopy(self.MODEL_CONFIG_DEFAULTS) - expect_config.update({ - 'virt-type': 'kvm', - 'no-proxy': 'jujucharms.com'}) - self.base_get_model_settings( - {'MODEL_SETTINGS': 'virt-type=kvm;no-proxy=jujucharms.com'}, - expect_config) + self.base_parse_option_list_string({}, self.MODEL_CONFIG_DEFAULTS) def test_get_model_settings_multiple_values_override(self): expect_config = copy.deepcopy(self.MODEL_CONFIG_DEFAULTS) expect_config.update({'test-mode': 'false'}) - self.base_get_model_settings( + self.base_parse_option_list_string( {'MODEL_SETTINGS': 'test-mode=false'}, expect_config) - def test_get_model_settings_whitespace(self): - expect_config = copy.deepcopy(self.MODEL_CONFIG_DEFAULTS) - expect_config.update({ - 'test-mode': 'false', - 'virt-type': 'kvm'}) - self.base_get_model_settings( - {'MODEL_SETTINGS': ' test-mode= false ; virt-type= kvm'}, - expect_config) - def test_prepare(self): self.patch_object(lc_prepare.zaza.controller, 'add_model') self.patch_object(lc_prepare, 'get_model_settings') + self.patch_object(lc_prepare, 'get_model_constraints') + self.patch_object(lc_prepare.zaza.model, 'set_model_constraints') self.get_model_settings.return_value = lc_prepare.MODEL_DEFAULTS + self.get_model_constraints.return_value = {'image-stream': 'released'} lc_prepare.prepare('newmodel') self.add_model.assert_called_once_with( 'newmodel', @@ -69,6 +69,9 @@ class TestCharmLifecyclePrepare(ut_utils.BaseTestCase): 'enable-os-upgrade': 'false', 'automatically-retry-hooks': 'false', 'use-default-secgroup': 'true'}) + self.set_model_constraints.assert_called_once_with( + constraints={'image-stream': 'released'}, + model_name='newmodel') def test_parser(self): args = lc_prepare.parse_args(['-m', 'newmodel']) diff --git a/zaza/charm_lifecycle/prepare.py b/zaza/charm_lifecycle/prepare.py index 8734d8b..96d6600 100644 --- a/zaza/charm_lifecycle/prepare.py +++ b/zaza/charm_lifecycle/prepare.py @@ -6,6 +6,7 @@ import os import sys import zaza.controller +import zaza.model MODEL_DEFAULTS = { # Model defaults from charm-test-infra @@ -23,21 +24,51 @@ MODEL_DEFAULTS = { } -def get_model_settings(): - """Construct settings for model from defaults and env variables. +def parse_option_list_string(option_list, delimiter=None): + """Convert the given string to a dictionary of options. - :returns: Settings to usee for model + Each pair must be of the form 'k=v', the delimiter seperates the + pairs from each other not the key from the value. + + :param option_list: A string representation of key value pairs. + :type option_list: str + :param delimiter: Delimiter to use to seperate each pair. + :type delimiter: str + :returns: A dictionary of settings. :rtype: Dict """ - model_settings = copy.deepcopy(MODEL_DEFAULTS) - for setting in os.environ.get('MODEL_SETTINGS', '').split(';'): + settings = {} + if delimiter is None: + delimiter = ';' + for setting in option_list.split(delimiter): if not setting: continue key, value = setting.split('=') - model_settings[key.strip()] = value.strip() + settings[key.strip()] = value.strip() + return settings + + +def get_model_settings(): + """Construct settings for model from defaults and env variables. + + :returns: Settings to use for model + :rtype: Dict + """ + model_settings = copy.deepcopy(MODEL_DEFAULTS) + model_settings.update( + parse_option_list_string(os.environ.get('MODEL_SETTINGS', ''))) return model_settings +def get_model_constraints(): + """Construct constraints for model. + + :returns: Constraints to apply to model + :rtype: Dict + """ + return parse_option_list_string(os.environ.get('MODEL_CONSTRAINTS', '')) + + def prepare(model_name): """Run all steps to prepare the environment before a functional test run. @@ -45,6 +76,9 @@ def prepare(model_name): :type bundle: str """ zaza.controller.add_model(model_name, config=get_model_settings()) + zaza.model.set_model_constraints( + model_name=model_name, + constraints=get_model_constraints()) def parse_args(args): diff --git a/zaza/model.py b/zaza/model.py index 28f2de9..944a74b 100644 --- a/zaza/model.py +++ b/zaza/model.py @@ -1008,3 +1008,25 @@ async def async_get_relation_id(application_name, remote_application_name, return(rel.id) get_relation_id = sync_wrapper(async_get_relation_id) + + +def set_model_constraints(constraints, model_name=None): + """ + Set constraints on a model. + + Note: Subprocessing out to 'juju' is a temporary solution until + https://bit.ly/2ujbVPA lands in libjuju. + + :param model_name: Name of model to operate on + :type model_name: str + :param constraints: Constraints to be applied to model + :type constraints: dict + + """ + if not constraints: + return + if not model_name: + model_name = get_juju_model() + cmd = ['juju', 'set-model-constraints', '-m', model_name] + cmd.extend(['{}={}'.format(k, v) for k, v in constraints.items()]) + subprocess.check_call(cmd)