diff --git a/tripleoclient/tests/v1/tripleo/test_tripleo_deploy.py b/tripleoclient/tests/v1/tripleo/test_tripleo_deploy.py index 9a8b2b61b..67fdc1518 100644 --- a/tripleoclient/tests/v1/tripleo/test_tripleo_deploy.py +++ b/tripleoclient/tests/v1/tripleo/test_tripleo_deploy.py @@ -21,6 +21,7 @@ import yaml from heatclient import exc as hc_exc from tripleo_common.image import kolla_builder +from tripleoclient import constants from tripleoclient import exceptions from tripleoclient.tests.v1.test_plugin import TestPluginV1 @@ -58,6 +59,74 @@ class TestDeployUndercloud(TestPluginV1): self.orc.stacks.create = mock.MagicMock( return_value={'stack': {'id': 'foo'}}) + @mock.patch('os.path.exists', return_value=True) + def test_set_roles_file(self, mock_exists): + self.cmd._set_roles_file('/foobar.yaml') + self.assertEqual(self.cmd.roles_file, '/foobar.yaml') + + @mock.patch('os.path.exists', return_value=False) + def test_set_roles_file_relative(self, mock_exists): + self.cmd._set_roles_file('foobar.yaml', + constants.TRIPLEO_HEAT_TEMPLATES) + self.assertEqual(self.cmd.roles_file, + '/usr/share/openstack-tripleo-heat-templates/' + 'foobar.yaml') + + def test_get_roles_data(self): + self.cmd.roles_data = [{'name': 'Foobar'}] + self.assertEqual(self.cmd._get_roles_data(), + [{'name': 'Foobar'}]) + + def test_get_roles_data_from_file(self): + with tempfile.NamedTemporaryFile(mode='w') as roles_file: + yaml.dump([{'name': 'Foobar'}], roles_file) + self.cmd.roles_file = roles_file.name + self.assertEqual(self.cmd._get_roles_data(), + [{'name': 'Foobar'}]) + + @mock.patch('os.path.exists', return_value=False) + def test_get_roles_data_missing(self, mock_exists): + self.cmd.roles_file = '/tmp/foo.yaml' + self.cmd.roles_data = None + self.assertEqual(self.cmd._get_roles_data(), None) + + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_get_roles_data') + def test_get_primary_role_name(self, mock_data): + mock_data.return_value = [ + {'name': 'Bar'}, {'name': 'Foo', 'tags': ['primary']} + ] + self.assertEqual(self.cmd._get_primary_role_name(), 'Foo') + + def test_get_primary_role_name_none_defined(self): + self.assertEqual(self.cmd._get_primary_role_name(), 'Controller') + + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_get_roles_data') + def test_get_primary_role_name_no_primary(self, mock_data): + mock_data.return_value = [{'name': 'Bar'}, {'name': 'Foo'}] + self.assertEqual(self.cmd._get_primary_role_name(), 'Bar') + + @mock.patch('os.path.exists', side_effect=[True, False]) + @mock.patch('shutil.copytree') + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_create_working_dirs') + def test_populate_templates_dir(self, mock_workingdirs, mock_copy, + mock_exists): + self.cmd.tht_render = '/foo' + self.cmd._populate_templates_dir('/bar') + mock_workingdirs.assert_called_once() + mock_copy.assert_called_once_with('/bar', '/foo', symlinks=True) + + @mock.patch('os.path.exists', return_value=False) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_create_working_dirs') + def test_populate_templates_dir_bad_source(self, mock_workingdirs, + mock_exists): + self.cmd.tht_render = '/foo' + self.assertRaises(exceptions.NotFound, + self.cmd._populate_templates_dir, '/foo') + @mock.patch('os.chmod') @mock.patch('os.path.exists') @mock.patch('tripleo_common.utils.passwords.generate_passwords') @@ -240,6 +309,8 @@ class TestDeployUndercloud(TestPluginV1): mock_yaml_dump.assert_has_calls([mock.call(rewritten_env, default_flow_style=False)]) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_create_working_dirs') @mock.patch('heatclient.common.template_utils.' 'process_environment_and_files', return_value=({}, {}), autospec=True) @@ -255,21 +326,22 @@ class TestDeployUndercloud(TestPluginV1): '_update_passwords_env', autospec=True) @mock.patch('tripleoclient.utils.' 'run_command_and_log', autospec=True) - @mock.patch('tempfile.mkdtemp', autospec=True, return_value='/twd') - @mock.patch('shutil.copytree', autospec=True) @mock.patch('shutil.copy', autospec=True) def test_setup_heat_environments(self, mock_cp, - mock_copy, - mock_mktemp, mock_run, mock_update_pass_env, mock_process_hiera, mock_process_multiple_environments, mock_hc_get_templ_cont, - mock_hc_process): + mock_hc_process, + mock_createdirs): mock_run.return_value = 0 + # logic handled in _standalone_deploy and _create_working_dirs + self.cmd.output_dir = '/my' + self.cmd.tht_render = '/my/tripleo-heat-installer-templates' + parsed_args = self.check_parser(self.cmd, ['--local-ip', '127.0.0.1/8', '--templates', '/tmp/thtroot', @@ -344,15 +416,16 @@ class TestDeployUndercloud(TestPluginV1): 'ansible-playbook', '-i', '/tmp/inventory.yaml', 'deploy_steps_playbook.yaml']) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_get_roles_data') @mock.patch('tripleo_common.image.kolla_builder.' 'container_images_prepare_multi') - def test_prepare_container_images(self, mock_cipm): + def test_prepare_container_images(self, mock_cipm, rolesdata_mock): env = {'parameter_defaults': {}} mock_cipm.return_value = {'FooImage': 'foo/bar:baz'} + rolesdata_mock.return_value = [{'name': 'Compute'}] - with tempfile.NamedTemporaryFile(mode='w') as roles_file: - yaml.dump([{'name': 'Compute'}], roles_file) - self.cmd._prepare_container_images(env, roles_file.name) + self.cmd._prepare_container_images(env) mock_cipm.assert_called_once_with( env, @@ -367,6 +440,8 @@ class TestDeployUndercloud(TestPluginV1): env ) + @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' + '_populate_templates_dir') @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' '_create_install_artifact', return_value='/tmp/foo.tar.bzip2') @mock.patch('tripleoclient.v1.tripleo_deploy.Deploy.' @@ -399,7 +474,7 @@ class TestDeployUndercloud(TestPluginV1): mock_launchheat, mock_download, mock_tht, mock_wait_for_port, mock_createdirs, mock_cleanupdirs, mock_launchansible, - mock_tarball): + mock_tarball, mock_templates_dir): parsed_args = self.check_parser(self.cmd, ['--local-ip', '127.0.0.1', diff --git a/tripleoclient/v1/tripleo_deploy.py b/tripleoclient/v1/tripleo_deploy.py index 3edc856e4..84e7a2055 100644 --- a/tripleoclient/v1/tripleo_deploy.py +++ b/tripleoclient/v1/tripleo_deploy.py @@ -83,6 +83,52 @@ class Deploy(command.Command): output_dir = None tmp_env_file_name = None tmp_ansible_dir = None + roles_file = None + roles_data = None + + def _set_roles_file(self, file_name=None, templates_dir=None): + """Set the roles file for the deployment + + If the file_name is a full path, it will be used. If the file name + passed in is not a full path, we will join it with the templates + dir and use that instead. + + :param file_name: (String) role file name to use, can be relative to + templates directory + :param templates_dir: + """ + if os.path.exists(file_name): + self.roles_file = file_name + else: + self.roles_file = os.path.join(templates_dir, file_name) + + def _get_roles_data(self): + """Load the roles data for deployment""" + # only load once + if self.roles_data: + return self.roles_data + + if self.roles_file and os.path.exists(self.roles_file): + with open(self.roles_file) as f: + self.roles_data = yaml.safe_load(f) + elif self.roles_file: + self.log.warning("roles_data '%s' is not found" % self.roles_file) + + return self.roles_data + + def _get_primary_role_name(self): + """Return the primary role name""" + roles_data = self._get_roles_data() + if not roles_data: + # TODO(aschultz): should this be Undercloud instead? + return 'Controller' + + for r in roles_data: + if 'tags' in r and 'primary' in r['tags']: + return r['name'] + self.log.warning('No primary role found in roles_data, using ' + 'first defined role') + return roles_data[0]['name'] def _get_tar_filename(self): """Return tarball name for the install artifacts""" @@ -122,12 +168,35 @@ class Deploy(command.Command): """Creates temporary working directories""" if self.output_dir and not os.path.exists(self.output_dir): os.mkdir(self.output_dir) - if self.tht_render and not os.path.exists(self.tht_render): - os.mkdir(self.tht_render) + if not self.tht_render: + self.tht_render = os.path.join(self.output_dir, + 'tripleo-heat-installer-templates') + # Clear dir since we're using a static name and shutils.copytree + # needs the fodler to not exist. We'll generate the + # contents each time. This should clear the folder on the first + # run of this function. + shutil.rmtree(self.tht_render, ignore_errors=True) if not self.tmp_ansible_dir: self.tmp_ansible_dir = tempfile.mkdtemp( prefix='undercloud-ansible-', dir=self.output_dir) + def _populate_templates_dir(self, source_templates_dir): + """Creates template dir with templates + + * Copy --templates content into a working dir + created as 'output_dir/tripleo-heat-installer-templates'. + + :param source_templates_dir: string to a directory containing our + source templates + """ + self._create_working_dirs() + if not os.path.exists(source_templates_dir): + raise exceptions.NotFound("%s template director does not exists" % + source_templates_dir) + if not os.path.exists(self.tht_render): + shutil.copytree(source_templates_dir, self.tht_render, + symlinks=True) + def _cleanup_working_dirs(self, cleanup=False): """Cleanup temporary working directories @@ -242,13 +311,18 @@ class Deploy(command.Command): return data def _generate_portmap_parameters(self, ip_addr, cidr_prefixlen, - ctlplane_vip_addr, public_vip_addr): + ctlplane_vip_addr, public_vip_addr, + stack_name='Undercloud', + role_name='Undercloud'): hostname = utils.get_short_hostname() + # in order for deployed server network information to match correctly, + # we need to ensure the HostnameMap matches our hostname + hostname_map_name = "%s-%s-0" % (stack_name.lower(), role_name.lower()) data = { 'ControlPlaneSubnetCidr': '%s' % cidr_prefixlen, 'HostnameMap': { - 'undercloud-undercloud-0': '%s' % hostname + hostname_map_name: '%s' % hostname }, # The settings below allow us to inject a custom public # VIP. This requires use of the generated @@ -340,8 +414,6 @@ class Deploy(command.Command): def _setup_heat_environments(self, parsed_args): """Process tripleo heat templates with jinja and deploy into work dir - * Copy --templates content into a working dir - created as 'output_dir/tripleo-heat-installer-templates'. * Process j2/install additional templates there * Return the environments list for futher processing as a new base. @@ -349,24 +421,22 @@ class Deploy(command.Command): overcloud-resource-registry-puppet.yaml and passwords files. """ - self.tht_render = os.path.join(os.path.abspath(parsed_args.output_dir), - 'tripleo-heat-installer-templates') - # The target should not exist, bear in mind consequent deploys. - # But we need to copy in the env files generated by undercloud_config - # in the current deployment executed, like undercloud_parameters.yaml. - shutil.rmtree(self.tht_render, ignore_errors=True) - shutil.copytree(parsed_args.templates, self.tht_render, symlinks=True) + # TODO(aschultz): This probably needs to get thought about because + # we likely need to do this for any environment file that gets passed + # in the deploy. This file breaks the seperation between standalone + # and undercloud deployment so we need to not hardcode + # undercloud_parameters.yaml shutil.copy(os.path.join(os.path.abspath(parsed_args.output_dir), 'tripleo-config-generated-env-files', 'undercloud_parameters.yaml'), self.tht_render) # generate jinja templates by its work dir location - self.log.debug(_("Using roles file %s") % parsed_args.roles_file) + self.log.debug(_("Using roles file %s") % self.roles_file) process_templates = os.path.join(parsed_args.templates, 'tools/process-templates.py') args = ['python', process_templates, '--roles-data', - parsed_args.roles_file, '--output-dir', self.tht_render] + self.roles_file, '--output-dir', self.tht_render] if utils.run_command_and_log(self.log, args, cwd=self.tht_render) != 0: # TODO(aschultz): improve error messaging msg = _("Problems generating templates.") @@ -425,7 +495,8 @@ class Deploy(command.Command): tmp_env = self._generate_hosts_parameters(parsed_args, p_ip) tmp_env.update(self._generate_portmap_parameters( - ip, cidr_prefixlen, c_ip, p_ip)) + ip, cidr_prefixlen, c_ip, p_ip, stack_name=parsed_args.stack, + role_name=self._get_primary_role_name())) with open(self.tmp_env_file_name, 'w') as env_file: yaml.safe_dump({'parameter_defaults': tmp_env}, env_file, @@ -438,12 +509,8 @@ class Deploy(command.Command): return environments - def _prepare_container_images(self, env, roles_file): - if roles_file: - with open(roles_file) as f: - roles_data = yaml.safe_load(f) - else: - roles_data = None + def _prepare_container_images(self, env): + roles_data = self._get_roles_data() image_params = kolla_builder.container_images_prepare_multi( env, roles_data) @@ -467,9 +534,7 @@ class Deploy(command.Command): environments, self.tht_render, parsed_args.templates, cleanup=parsed_args.cleanup) - roles_file = os.path.join( - self.tht_render, parsed_args.roles_file) - self._prepare_container_images(env, roles_file) + self._prepare_container_images(env) self.log.debug(_("Getting template contents")) template_path = os.path.join(self.tht_render, 'overcloud.yaml') @@ -735,6 +800,13 @@ class Deploy(command.Command): # configure puppet self._configure_puppet() + # copy the templates dir in place + self._populate_templates_dir(parsed_args.templates) + + # configure our roles data + self._set_roles_file(parsed_args.roles_file, self.tht_render) + self._get_roles_data() + rc = 1 try: # Launch heat.