Merge "Run hostname check for standalone"
This commit is contained in:
commit
828cfe02bb
@ -1219,3 +1219,43 @@ class TestWaitApiPortReady(TestCase):
|
||||
utils.wait_api_port_ready(8080)
|
||||
self.assertEqual(urlopen_mock.call_count, 1)
|
||||
self.assertEqual(sleep_mock.call_count, 1)
|
||||
|
||||
|
||||
class TestCheckHostname(TestCase):
|
||||
@mock.patch('tripleoclient.utils.run_command')
|
||||
def test_hostname_ok(self, mock_run):
|
||||
mock_run.side_effect = ['host.domain', 'host.domain']
|
||||
mock_open_ctx = mock.mock_open(read_data='127.0.0.1 host.domain')
|
||||
with mock.patch('tripleoclient.utils.open', mock_open_ctx):
|
||||
utils.check_hostname(False)
|
||||
run_calls = [
|
||||
mock.call(['hostnamectl', '--static'], name='hostnamectl'),
|
||||
mock.call(['hostnamectl', '--transient'], name='hostnamectl')]
|
||||
self.assertEqual(mock_run.mock_calls, run_calls)
|
||||
|
||||
@mock.patch('tripleoclient.utils.run_command')
|
||||
def test_hostname_fix_hosts_ok(self, mock_run):
|
||||
mock_run.side_effect = ['host.domain', 'host.domain', '']
|
||||
mock_open_ctx = mock.mock_open(read_data='')
|
||||
with mock.patch('tripleoclient.utils.open', mock_open_ctx):
|
||||
utils.check_hostname(True)
|
||||
sed_cmd = 'sed -i "s/127.0.0.1\\(\\s*\\)/127.0.0.1\\\\1host.domain ' \
|
||||
'host /" /etc/hosts'
|
||||
run_calls = [
|
||||
mock.call(['hostnamectl', '--static'], name='hostnamectl'),
|
||||
mock.call(['hostnamectl', '--transient'], name='hostnamectl'),
|
||||
mock.call(['sudo', '/bin/bash', '-c', sed_cmd],
|
||||
name='hostname-to-etc-hosts')]
|
||||
import pprint
|
||||
pprint.pprint(mock_run.mock_calls)
|
||||
self.assertEqual(mock_run.mock_calls, run_calls)
|
||||
|
||||
@mock.patch('tripleoclient.utils.run_command')
|
||||
def test_hostname_mismatch_fail(self, mock_run):
|
||||
mock_run.side_effect = ['host.domain', '']
|
||||
self.assertRaises(RuntimeError, utils.check_hostname)
|
||||
|
||||
@mock.patch('tripleoclient.utils.run_command')
|
||||
def test_hostname_short_fail(self, mock_run):
|
||||
mock_run.side_effect = ['host', 'host']
|
||||
self.assertRaises(RuntimeError, utils.check_hostname)
|
||||
|
@ -62,6 +62,39 @@ class TestDeployUndercloud(TestPluginV1):
|
||||
python_version = sys.version_info[0]
|
||||
self.ansible_playbook_cmd = "ansible-playbook-%s" % (python_version)
|
||||
|
||||
@mock.patch('tripleoclient.v1.tripleo_deploy.Deploy._is_undercloud_deploy')
|
||||
@mock.patch('tripleoclient.utils.check_hostname')
|
||||
def test_run_preflight_checks(self, mock_check_hostname, mock_uc):
|
||||
parsed_args = self.check_parser(self.cmd,
|
||||
['--local-ip', '127.0.0.1/8'], [])
|
||||
|
||||
mock_uc.return_value = False
|
||||
self.cmd._run_preflight_checks(parsed_args)
|
||||
mock_check_hostname.called_one_with(False)
|
||||
|
||||
@mock.patch('tripleoclient.v1.tripleo_deploy.Deploy._is_undercloud_deploy')
|
||||
@mock.patch('tripleoclient.utils.check_hostname')
|
||||
def test_run_preflight_checks_output_only(self, mock_check_hostname,
|
||||
mock_uc):
|
||||
parsed_args = self.check_parser(self.cmd,
|
||||
['--local-ip', '127.0.0.1/8',
|
||||
'--output-only'], [])
|
||||
|
||||
mock_uc.return_value = False
|
||||
self.cmd._run_preflight_checks(parsed_args)
|
||||
mock_check_hostname.assert_not_called()
|
||||
|
||||
@mock.patch('tripleoclient.v1.tripleo_deploy.Deploy._is_undercloud_deploy')
|
||||
@mock.patch('tripleoclient.utils.check_hostname')
|
||||
def test_run_preflight_checks_undercloud(self, mock_check_hostname,
|
||||
mock_uc):
|
||||
parsed_args = self.check_parser(self.cmd,
|
||||
['--local-ip', '127.0.0.1/8'], [])
|
||||
|
||||
mock_uc.return_value = True
|
||||
self.cmd._run_preflight_checks(parsed_args)
|
||||
mock_check_hostname.assert_not_called()
|
||||
|
||||
def test_get_roles_file_path(self):
|
||||
parsed_args = self.check_parser(self.cmd,
|
||||
['--local-ip', '127.0.0.1/8'], [])
|
||||
|
@ -48,6 +48,8 @@ from six.moves.urllib import request
|
||||
from tripleoclient import constants
|
||||
from tripleoclient import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__ + ".utils")
|
||||
|
||||
|
||||
def run_ansible_playbook(logger,
|
||||
workdir,
|
||||
@ -1346,3 +1348,89 @@ def get_deployment_python_interpreter(parsed_args):
|
||||
if parsed_args.deployment_python_interpreter:
|
||||
return parsed_args.deployment_python_interpreter
|
||||
return sys.executable
|
||||
|
||||
|
||||
def run_command(args, env=None, name=None, logger=None):
|
||||
"""Run the command defined by args and return its output
|
||||
|
||||
:param args: List of arguments for the command to be run.
|
||||
:param env: Dict defining the environment variables. Pass None to use
|
||||
the current environment.
|
||||
:param name: User-friendly name for the command being run. A value of
|
||||
None will cause args[0] to be used.
|
||||
"""
|
||||
if logger is None:
|
||||
logger = LOG
|
||||
if name is None:
|
||||
name = args[0]
|
||||
try:
|
||||
output = subprocess.check_output(args,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env)
|
||||
if isinstance(output, bytes):
|
||||
output = output.decode('utf-8')
|
||||
return output
|
||||
except subprocess.CalledProcessError as e:
|
||||
message = '%s failed: %s' % (name, e.output)
|
||||
logger.error(message)
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def set_hostname(hostname):
|
||||
"""Set system hostname to provided hostname
|
||||
|
||||
:param hostname: The hostname to set
|
||||
"""
|
||||
args = ['sudo', 'hostnamectl', 'set-hostname', hostname]
|
||||
return run_command(args, name='hostnamectl')
|
||||
|
||||
|
||||
def check_hostname(fix_etc_hosts=True, logger=None):
|
||||
"""Check system hostname configuration
|
||||
|
||||
Rabbit and Puppet require pretty specific hostname configuration. This
|
||||
function ensures that the system hostname settings are valid before
|
||||
continuing with the installation.
|
||||
|
||||
:param fix_etc_hosts: Boolean to to enable adding hostname to /etc/hosts
|
||||
if not found.
|
||||
"""
|
||||
if logger is None:
|
||||
logger = LOG
|
||||
logger.info('Checking for a FQDN hostname...')
|
||||
args = ['hostnamectl', '--static']
|
||||
detected_static_hostname = run_command(args, name='hostnamectl').rstrip()
|
||||
logger.info('Static hostname detected as %s', detected_static_hostname)
|
||||
args = ['hostnamectl', '--transient']
|
||||
detected_transient_hostname = run_command(args,
|
||||
name='hostnamectl').rstrip()
|
||||
logger.info('Transient hostname detected as %s',
|
||||
detected_transient_hostname)
|
||||
if detected_static_hostname != detected_transient_hostname:
|
||||
logger.error('Static hostname "%s" does not match transient hostname '
|
||||
'"%s".', detected_static_hostname,
|
||||
detected_transient_hostname)
|
||||
logger.error('Use hostnamectl to set matching hostnames.')
|
||||
raise RuntimeError('Static and transient hostnames do not match')
|
||||
short_hostname = detected_static_hostname.split('.')[0]
|
||||
if short_hostname == detected_static_hostname:
|
||||
message = _('Configured hostname is not fully qualified.')
|
||||
logger.error(message)
|
||||
raise RuntimeError(message)
|
||||
with open('/etc/hosts') as hosts_file:
|
||||
for line in hosts_file:
|
||||
# check if hostname is in /etc/hosts
|
||||
if (not line.lstrip().startswith('#') and
|
||||
detected_static_hostname in line.split()):
|
||||
break
|
||||
else:
|
||||
# hostname not found, add it to /etc/hosts
|
||||
if not fix_etc_hosts:
|
||||
return
|
||||
sed_cmd = (r'sed -i "s/127.0.0.1\(\s*\)/127.0.0.1\\1%s %s /" '
|
||||
'/etc/hosts' %
|
||||
(detected_static_hostname, short_hostname))
|
||||
args = ['sudo', '/bin/bash', '-c', sed_cmd]
|
||||
run_command(args, name='hostname-to-etc-hosts')
|
||||
logger.info('Added hostname %s to /etc/hosts',
|
||||
detected_static_hostname)
|
||||
|
@ -122,6 +122,24 @@ class Deploy(command.Command):
|
||||
return parsed_args.standalone_role == 'Undercloud' and \
|
||||
parsed_args.stack == 'undercloud'
|
||||
|
||||
def _run_preflight_checks(self, parsed_args):
|
||||
"""Run preflight deployment checks
|
||||
|
||||
Perform any pre-deployment checks that we want to run when deploying
|
||||
standalone deployments. This is skipped when in output only mode or
|
||||
when used with an undercloud. The undercloud has it's own set of
|
||||
deployment preflight requirements.
|
||||
|
||||
:param parsed_args: parsed arguments from the cli
|
||||
"""
|
||||
# we skip preflight checks for output only and undercloud
|
||||
if parsed_args.output_only or self._is_undercloud_deploy(parsed_args):
|
||||
return
|
||||
|
||||
# in standalone we don't want to fixup the /etc/hosts as we'll be
|
||||
# managing that elsewhere during the deployment
|
||||
utils.check_hostname(fix_etc_hosts=False, logger=self.log)
|
||||
|
||||
# NOTE(cjeanner) Quick'n'dirty way before we have proper
|
||||
# escalation support through oslo.privsep
|
||||
def _set_data_rights(self, file_name, user=None,
|
||||
@ -1178,6 +1196,8 @@ class Deploy(command.Command):
|
||||
self.log.error(msg)
|
||||
raise exceptions.DeploymentError(msg)
|
||||
|
||||
self._run_preflight_checks(parsed_args)
|
||||
|
||||
# prepare working spaces
|
||||
self.output_dir = os.path.abspath(parsed_args.output_dir)
|
||||
self._create_working_dirs()
|
||||
|
@ -674,6 +674,9 @@ def prepare_undercloud_deploy(upgrade=False, no_validations=False,
|
||||
|
||||
deploy_args += ['--hieradata-override=%s' % data_file]
|
||||
|
||||
if CONF.get('undercloud_hostname'):
|
||||
utils.set_hostname(CONF.get('undercloud_hostname'))
|
||||
|
||||
if CONF.get('enable_validations') and not no_validations:
|
||||
undercloud_preflight.check(verbose_level, upgrade)
|
||||
deploy_args += ['-e', os.path.join(
|
||||
|
@ -45,30 +45,6 @@ PASSWORD_PATH = '%s/%s' % (constants.UNDERCLOUD_OUTPUT_DIR,
|
||||
LOG = logging.getLogger(__name__ + ".UndercloudSetup")
|
||||
|
||||
|
||||
def _run_command(args, env=None, name=None):
|
||||
"""Run the command defined by args and return its output
|
||||
|
||||
:param args: List of arguments for the command to be run.
|
||||
:param env: Dict defining the environment variables. Pass None to use
|
||||
the current environment.
|
||||
:param name: User-friendly name for the command being run. A value of
|
||||
None will cause args[0] to be used.
|
||||
"""
|
||||
if name is None:
|
||||
name = args[0]
|
||||
try:
|
||||
output = subprocess.check_output(args,
|
||||
stderr=subprocess.STDOUT,
|
||||
env=env)
|
||||
if isinstance(output, bytes):
|
||||
output = output.decode('utf-8')
|
||||
return output
|
||||
except subprocess.CalledProcessError as e:
|
||||
message = '%s failed: %s' % (name, e.output)
|
||||
LOG.error(message)
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def _run_live_command(args, env=None, name=None, cwd=None, wait=True):
|
||||
"""Run the command defined by args, env and cwd
|
||||
|
||||
@ -123,52 +99,6 @@ def _check_diskspace(upgrade=False):
|
||||
python_interpreter=python_interpreter)
|
||||
|
||||
|
||||
def _check_hostname():
|
||||
"""Check system hostname configuration
|
||||
|
||||
Rabbit and Puppet require pretty specific hostname configuration. This
|
||||
function ensures that the system hostname settings are valid before
|
||||
continuing with the installation.
|
||||
"""
|
||||
if CONF.undercloud_hostname is not None:
|
||||
args = ['sudo', 'hostnamectl', 'set-hostname',
|
||||
CONF.undercloud_hostname]
|
||||
_run_command(args, name='hostnamectl')
|
||||
|
||||
LOG.info('Checking for a FQDN hostname...')
|
||||
args = ['sudo', 'hostnamectl', '--static']
|
||||
detected_static_hostname = _run_command(args, name='hostnamectl').rstrip()
|
||||
LOG.info('Static hostname detected as %s', detected_static_hostname)
|
||||
args = ['sudo', 'hostnamectl', '--transient']
|
||||
detected_transient_hostname = _run_command(args,
|
||||
name='hostnamectl').rstrip()
|
||||
LOG.info('Transient hostname detected as %s', detected_transient_hostname)
|
||||
if detected_static_hostname != detected_transient_hostname:
|
||||
LOG.error('Static hostname "%s" does not match transient hostname '
|
||||
'"%s".', detected_static_hostname,
|
||||
detected_transient_hostname)
|
||||
LOG.error('Use hostnamectl to set matching hostnames.')
|
||||
raise RuntimeError('Static and transient hostnames do not match')
|
||||
with open('/etc/hosts') as hosts_file:
|
||||
for line in hosts_file:
|
||||
if (not line.lstrip().startswith('#') and
|
||||
detected_static_hostname in line.split()):
|
||||
break
|
||||
else:
|
||||
short_hostname = detected_static_hostname.split('.')[0]
|
||||
if short_hostname == detected_static_hostname:
|
||||
message = _('Configured hostname is not fully qualified.')
|
||||
LOG.error(message)
|
||||
raise RuntimeError(message)
|
||||
sed_cmd = (r'sed -i "s/127.0.0.1\(\s*\)/127.0.0.1\\1%s %s /" '
|
||||
'/etc/hosts' %
|
||||
(detected_static_hostname, short_hostname))
|
||||
args = ['sudo', '/bin/bash', '-c', sed_cmd]
|
||||
_run_command(args, name='hostname-to-etc-hosts')
|
||||
LOG.info('Added hostname %s to /etc/hosts',
|
||||
detected_static_hostname)
|
||||
|
||||
|
||||
def _check_memory():
|
||||
"""Check system memory
|
||||
|
||||
@ -529,7 +459,7 @@ def check(verbose_level, upgrade=False):
|
||||
try:
|
||||
# Other validations
|
||||
_checking_status('Hostname')
|
||||
_check_hostname()
|
||||
utils.check_hostname()
|
||||
_checking_status('Memory')
|
||||
_check_memory()
|
||||
_checking_status('Disk space')
|
||||
|
Loading…
Reference in New Issue
Block a user