Sandro Bonazzola has uploaded a new change for review. Change subject: packaging: setup: require answer file on additional hosts ......................................................................
packaging: setup: require answer file on additional hosts Require the use of an answer file on additional hosts deployment. Allows to fetch the answer file from the first host providing its address and root password. Change-Id: Ie8fd38828422c52c416ad3125c2e39193956d74f Bug-Url: https://bugzilla.redhat.com/1007422 Signed-off-by: Sandro Bonazzola <[email protected]> --- M ovirt-hosted-engine-setup.spec.in M src/ovirt_hosted_engine_setup/constants.py M src/plugins/ovirt-hosted-engine-setup/core/Makefile.am M src/plugins/ovirt-hosted-engine-setup/core/__init__.py A src/plugins/ovirt-hosted-engine-setup/core/remote_answerfile.py 5 files changed, 292 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-hosted-engine-setup refs/changes/23/19623/1 diff --git a/ovirt-hosted-engine-setup.spec.in b/ovirt-hosted-engine-setup.spec.in index 30b4ed2..f1c53b3 100644 --- a/ovirt-hosted-engine-setup.spec.in +++ b/ovirt-hosted-engine-setup.spec.in @@ -45,6 +45,7 @@ Requires: vdsm-python >= 4.12.0 Requires: ovirt-host-deploy >= 1.1.0 Requires: openssh-server +Requires: python-paramiko Requires: virt-viewer Requires: openssl Requires: sudo diff --git a/src/ovirt_hosted_engine_setup/constants.py b/src/ovirt_hosted_engine_setup/constants.py index 9b224f1..9124929 100644 --- a/src/ovirt_hosted_engine_setup/constants.py +++ b/src/ovirt_hosted_engine_setup/constants.py @@ -492,6 +492,7 @@ CONFIG_BOOT_DEVICE = 'ohosted.boot.configuration.available' CONFIG_STORAGE = 'ohosted.storage.configuration.available' CONFIG_ADDITIONAL_HOST = 'ohosted.core.additional.host' + REQUIRE_ANSWER_FILE = 'ohosted.core.require.answerfile' CONFIG_OVF_IMPORT = 'ohosted.configuration.ovf' VDSMD_START = 'ohosted.vdsm.started' VDSMD_PKI = 'ohosted.vdsm.pki.available' @@ -564,4 +565,13 @@ SETTINGS = 'SETTINGS_PROCEED' [email protected] [email protected] +class FirstHostEnv(object): + FQDN = 'OVEHOSTED_FIRST_HOST/fqdn' + ROOT_PASSWORD = 'OVEHOSTED_FIRST_HOST/rootPassword' + FETCH_ANSWER = 'OVEHOSTED_FIRST_HOST/fetchAnswer' + SSHD_PORT = 'OVEHOSTED_FIRST_HOST/sshdPort' + + # vim: expandtab tabstop=4 shiftwidth=4 diff --git a/src/plugins/ovirt-hosted-engine-setup/core/Makefile.am b/src/plugins/ovirt-hosted-engine-setup/core/Makefile.am index 8f314b8..78a2f3c 100644 --- a/src/plugins/ovirt-hosted-engine-setup/core/Makefile.am +++ b/src/plugins/ovirt-hosted-engine-setup/core/Makefile.am @@ -31,6 +31,7 @@ answerfile.py \ offlinepackager.py \ preview.py \ + remote_answerfile.py \ shell.py \ titles.py \ $(NULL) diff --git a/src/plugins/ovirt-hosted-engine-setup/core/__init__.py b/src/plugins/ovirt-hosted-engine-setup/core/__init__.py index 8efd871..7eb1551 100644 --- a/src/plugins/ovirt-hosted-engine-setup/core/__init__.py +++ b/src/plugins/ovirt-hosted-engine-setup/core/__init__.py @@ -29,6 +29,7 @@ from . import answerfile from . import offlinepackager from . import preview +from . import remote_answerfile from . import shell from . import titles @@ -40,6 +41,7 @@ answerfile.Plugin(context=context) offlinepackager.Plugin(context=context) preview.Plugin(context=context) + remote_answerfile.Plugin(context=context) shell.Plugin(context=context) titles.Plugin(context=context) diff --git a/src/plugins/ovirt-hosted-engine-setup/core/remote_answerfile.py b/src/plugins/ovirt-hosted-engine-setup/core/remote_answerfile.py new file mode 100644 index 0000000..0bb5598 --- /dev/null +++ b/src/plugins/ovirt-hosted-engine-setup/core/remote_answerfile.py @@ -0,0 +1,278 @@ +# +# ovirt-hosted-engine-setup -- ovirt hosted engine setup +# Copyright (C) 2013 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + + +"""Answer file fetcher plugin""" + + +import configparser +import gettext +import os +import paramiko +import tempfile + + +from otopi import constants as otopicons +from otopi import util +from otopi import common +from otopi import plugin + + +from ovirt_hosted_engine_setup import constants as ohostedcons + + +_ = lambda m: gettext.dgettext(message=m, domain='ovirt-hosted-engine-setup') + + [email protected] +class Plugin(plugin.PluginBase): + """Answer file fetcher plugin""" + + def __init__(self, context): + super(Plugin, self).__init__(context=context) + self._config = configparser.ConfigParser() + self._config.optionxform = str + self._tmp_ans = None + + def _get_fqdn(self): + fqdn_interactive = self.environment[ + ohostedcons.FirstHostEnv.FQDN + ] is None + valid = False + while not valid: + if fqdn_interactive: + self.environment[ + ohostedcons.FirstHostEnv.FQDN + ] = self.dialog.queryString( + name='OVEHOSTED_NET_FIRST_HOST_FQDN', + note=_( + 'Please provide the FQDN or IP of the first host: ' + ), + prompt=True, + caseSensitive=True, + ) + try: + transport = paramiko.Transport((self.environment[ + ohostedcons.FirstHostEnv.FQDN + ], 22)) + valid = True + except paramiko.SSHException as e: + self.logger.debug('exception', exc_info=True) + if fqdn_interactive: + self.logger.error( + _( + 'Unable to connect to {fqdn}. Error: {error}' + ).format( + fqdn=self.environment[ + ohostedcons.FirstHostEnv.FQDN + ], + error=e, + ) + ) + else: + raise RuntimeError( + _( + 'Unable to connect to {fqdn}. Error: {error}' + ).format( + fqdn=self.environment[ + ohostedcons.FirstHostEnv.FQDN + ], + error=e, + ) + ) + finally: + transport.close() + + def _fetch_answer_file(self): + fqdn = self.environment[ohostedcons.FirstHostEnv.FQDN] + interactive = ( + self.environment[ohostedcons.FirstHostEnv.ROOT_PASSWORD] is None + ) + while self.environment[ohostedcons.FirstHostEnv.ROOT_PASSWORD] is None: + if interactive: + password = self.dialog.queryString( + name='HOST_FIRST_HOST_ROOT_PASSWORD', + note=_( + "Enter 'root' user password for host {fqdn}: " + ).format( + fqdn=fqdn, + ), + prompt=True, + hidden=True, + ) + + try: + transport = paramiko.Transport( + ( + fqdn, + self.environment[ohostedcons.FirstHostEnv.SSHD_PORT], + ) + ) + transport.connect(username='root', password=password) + self.environment[ + ohostedcons.FirstHostEnv.ROOT_PASSWORD + ] = password + self.environment[otopicons.CoreEnv.LOG_FILTER].append( + password + ) + try: + fd, self._tmp_ans = tempfile.mkstemp( + dir=self.environment[ohostedcons.CoreEnv.TEMPDIR], + ) + os.close(fd) + sftp = paramiko.SFTPClient.from_transport(transport) + sftp.get( + '/etc/ovirt-hosted-engine/answers.conf', + self._tmp_ans + ) + finally: + sftp.close() + except paramiko.AuthenticationException: + self.logger.error( + _('Invalid password for host {fqdn}').format( + fqdn=fqdn, + ) + ) + except paramiko.SSHException as e: + self.logger.debug('exception', exc_info=True) + self.logger.error( + _('Unable to connect to {fqdn}. Error:{error}').format( + fqdn=fqdn, + error=e, + ) + ) + finally: + transport.close() + if not interactive: + raise RuntimeError( + _( + 'Cannot deploy Hosted Engine on additional host: ' + 'unable to fetch the configuration used on first host' + ) + ) + + def _parse_answer_file(self): + self._config.read(self._tmp_ans) + for name, value in self._config.items( + otopicons.Const.CONFIG_SECTION_DEFAULT + ): + try: + value = common.parseTypedValue(value) + except Exception as e: + raise RuntimeError( + _( + "Cannot parse configuration file key " + "{key} at section {section}: {exception}" + ).format( + key=name, + section=otopicons.Const.CONFIG_SECTION_DEFAULT, + exception=e, + ) + ) + self.environment.setdefault(name, value) + + @plugin.event( + stage=plugin.Stages.STAGE_INIT, + ) + def _init(self): + self.environment.setdefault( + ohostedcons.FirstHostEnv.FQDN, + None + ) + self.environment.setdefault( + ohostedcons.FirstHostEnv.ROOT_PASSWORD, + None + ) + self.environment.setdefault( + ohostedcons.FirstHostEnv.FETCH_ANSWER, + None + ) + self.environment.setdefault( + ohostedcons.FirstHostEnv.SSHD_PORT, + ohostedcons.Defaults.DEFAULT_SSHD_PORT + ) + + @plugin.event( + name=ohostedcons.Stages.REQUIRE_ANSWER_FILE, + stage=plugin.Stages.STAGE_CUSTOMIZATION, + after=( + ohostedcons.Stages.CONFIG_STORAGE, + ohostedcons.Stages.DIALOG_TITLES_S_SYSTEM, + ), + before=( + ohostedcons.Stages.DIALOG_TITLES_E_SYSTEM, + ), + condition=lambda self: ( + self.environment[ohostedcons.CoreEnv.IS_ADDITIONAL_HOST] and + self.environment[otopicons.CoreEnv.CONFIG_FILE_APPEND] is None + ), + ) + def _customization(self): + self.logger.warning( + _( + 'A configuration file must be supplied to deploy ' + 'Hosted Engine on an additional host.' + ) + ) + + interactive = self.environment[ + ohostedcons.FirstHostEnv.FETCH_ANSWER + ] is None + if interactive: + self.environment[ + ohostedcons.FirstHostEnv.FETCH_ANSWER + ] = self.dialog.queryString( + name='OVEHOSTED_CORE_FETCH_ANSWER', + note=_( + 'The answer file may be fetched from the first host ' + 'using scp.\n' + 'If you do not want to download it ' + 'automatically you can abort the setup answering no ' + 'to the following question.\n' + 'Do you want to scp the answer file from the first host? ' + '(@VALUES@)[@DEFAULT@]: ' + ), + prompt=True, + validValues=(_('Yes'), _('No')), + caseSensitive=False, + default=_('Yes') + ) == _('Yes').lower() + + if not self.environment[ohostedcons.FirstHostEnv.FETCH_ANSWER]: + raise RuntimeError( + _( + 'Cannot deploy Hosted Engine on additional hosts ' + 'without access to the configuration used on ' + 'the first host' + ) + ) + + self._get_fqdn() + self._fetch_answer_file() + self._parse_answer_file() + + @plugin.event( + stage=plugin.Stages.STAGE_CLEANUP, + ) + def _cleanup(self): + if self._tmp_ans and os.path.exists(self._tmp_ans): + os.unlink(self._tmp_ans) + + +# vim: expandtab tabstop=4 shiftwidth=4 -- To view, visit http://gerrit.ovirt.org/19623 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ie8fd38828422c52c416ad3125c2e39193956d74f Gerrit-PatchSet: 1 Gerrit-Project: ovirt-hosted-engine-setup Gerrit-Branch: ovirt-hosted-engine-setup-1.0 Gerrit-Owner: Sandro Bonazzola <[email protected]> _______________________________________________ Engine-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/engine-patches
