Sandro Bonazzola has uploaded a new change for review. Change subject: WIP: require answer file on additional hosts ......................................................................
WIP: 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 <sbona...@redhat.com> --- 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 4 files changed, 270 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-hosted-engine-setup refs/changes/77/19577/1 diff --git a/src/ovirt_hosted_engine_setup/constants.py b/src/ovirt_hosted_engine_setup/constants.py index 694ecde..2d3dc7b 100644 --- a/src/ovirt_hosted_engine_setup/constants.py +++ b/src/ovirt_hosted_engine_setup/constants.py @@ -491,6 +491,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' @@ -563,4 +564,13 @@ SETTINGS = 'SETTINGS_PROCEED' +@util.export +@util.codegen +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..6a9bce2 --- /dev/null +++ b/src/plugins/ovirt-hosted-engine-setup/core/remote_answerfile.py @@ -0,0 +1,257 @@ +# +# 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') + + +@util.export +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)) + except paramiko.SSHException as e: + self.logger.debug('exception', exc_info=True) + self.logger.error( + _('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( + _( + 'Any configuration file was supplied while deploying ' + '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=_( + 'Do you want to scp the answer file from 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 host ' + 'without knowing the configuration used on 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/19577 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: master Gerrit-Owner: Sandro Bonazzola <sbona...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches