Yedidyah Bar David has uploaded a new change for review. Change subject: packaging: setup: add getHostname ......................................................................
packaging: setup: add getHostname Move the code asking for the FQDN to a common place, to allow asking for other host names. Copy the FQDN to ENGINE_FQDN to be used by other plugins needing the engine fqdn. Change-Id: I8747002a14de6d96d5a7e185258d8ee0955b8a24 Related-To: https://bugzilla.redhat.com/1118330 Related-To: https://bugzilla.redhat.com/1080992 Signed-off-by: Yedidyah Bar David <d...@redhat.com> (cherry picked from commit 8b84424185bea6dd9f99940b9b4aa2637eb1504f) --- M packaging/setup/ovirt_engine_setup/engine/constants.py M packaging/setup/ovirt_engine_setup/engine_common/constants.py A packaging/setup/ovirt_engine_setup/hostname.py M packaging/setup/plugins/ovirt-engine-setup/base/network/hostname.py M packaging/setup/plugins/ovirt-engine-setup/ovirt-engine-common/dialog/titles.py M packaging/setup/plugins/ovirt-engine-setup/ovirt-engine/config/protocols.py 6 files changed, 390 insertions(+), 281 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/62/33062/1 diff --git a/packaging/setup/ovirt_engine_setup/engine/constants.py b/packaging/setup/ovirt_engine_setup/engine/constants.py index cf4612b..a2ee32e 100644 --- a/packaging/setup/ovirt_engine_setup/engine/constants.py +++ b/packaging/setup/ovirt_engine_setup/engine/constants.py @@ -595,6 +595,8 @@ MAC_RANGE_POOL = 'OVESETUP_CONFIG/macRangePool' + ENGINE_FQDN = 'OVESETUP_ENGINE_CONFIG/fqdn' + @util.export @util.codegen diff --git a/packaging/setup/ovirt_engine_setup/engine_common/constants.py b/packaging/setup/ovirt_engine_setup/engine_common/constants.py index c333c48..8022a79 100644 --- a/packaging/setup/ovirt_engine_setup/engine_common/constants.py +++ b/packaging/setup/ovirt_engine_setup/engine_common/constants.py @@ -166,6 +166,9 @@ CONFIG_DB_ENCRYPTION_AVAILABLE = 'osetup.config.encryption.available' + NETWORK_OWNERS_CONFIG_CUSTOMIZED = \ + 'osetup.network.owners.config.customized' + DIALOG_TITLES_S_ALLINONE = 'osetup.dialog.titles.allinone.start' DIALOG_TITLES_S_APACHE = 'osetup.dialog.titles.apache.start' DIALOG_TITLES_S_DATABASE = 'osetup.dialog.titles.database.start' diff --git a/packaging/setup/ovirt_engine_setup/hostname.py b/packaging/setup/ovirt_engine_setup/hostname.py new file mode 100644 index 0000000..48b95c8 --- /dev/null +++ b/packaging/setup/ovirt_engine_setup/hostname.py @@ -0,0 +1,347 @@ +# +# ovirt-engine-setup -- ovirt engine setup +# Copyright (C) 2013-2014 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +import re +import socket +import gettext +_ = lambda m: gettext.dgettext(message=m, domain='ovirt-engine-setup') + + +from otopi import util +from otopi import base + + +from ovirt_engine_setup import constants as osetupcons + + +@util.export +class Hostname(base.Base): + _IPADDR_RE = re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') + + _DOMAIN_RE = re.compile( + flags=re.VERBOSE, + pattern=r""" + ^ + [A-Za-z0-9\.\-]+ + \w+ + $ + """ + ) + + _INTERFACE_RE = re.compile( + flags=re.VERBOSE, + pattern=r""" + ^ + \d+ + : + \s+ + (?P<interface>\w+(\.\w+)?)(@\w+)? + : + \s+ + <(?P<options>[^>]+) + .* + """ + ) + + _ADDRESS_RE = re.compile( + flags=re.VERBOSE, + pattern=r""" + \s+ + inet + \s + (?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) + .+ + \s+ + (?P<interface>\w+(\.\w+(@\w+)?)?) + $ + """ + ) + + _DIG_LOOKUP_RE = re.compile( + flags=re.VERBOSE, + pattern=r""" + ^ + [\w.-]+ + \s+ + \d+ + \s+ + IN + \s+ + (A|CNAME) + \s+ + [\w.-]+ + """ + ) + + _DIG_REVLOOKUP_RE = re.compile( + flags=re.VERBOSE, + pattern=r""" + ^ + [\w/.-]+\.in-addr\.arpa\. + \s+ + \d+ + \s+ + IN + \s+ + PTR + \s+ + (?P<answer>[\w/.-]+) + \. + $ + """ + ) + + def __init__(self, plugin): + super(Hostname, self).__init__() + self._plugin = plugin + + @property + def plugin(self): + return self._plugin + + @property + def command(self): + return self._plugin.command + + @property + def dialog(self): + return self._plugin.dialog + + @property + def execute(self): + return self._plugin.execute + + @property + def environment(self): + return self._plugin.environment + + @property + def logger(self): + return self._plugin.logger + + def _getNonLoopbackAddresses(self): + interfaces = {} + addresses = {} + rc, stdout, stderr = self.execute( + args=( + self.command.get('ip'), + 'addr', + ), + ) + for line in stdout: + interfacematch = self._INTERFACE_RE.match(line) + addressmatch = self._ADDRESS_RE.match(line) + if interfacematch is not None: + interfaces[ + interfacematch.group('interface') + ] = 'LOOPBACK' in interfacematch.group('options') + elif addressmatch is not None: + addresses.setdefault( + addressmatch.group('interface'), + [] + ).append( + addressmatch.group('address') + ) + iplist = [] + for interface, loopback in interfaces.iteritems(): + if not loopback: + iplist.extend(addresses.get(interface, [])) + + self.logger.debug('addresses: %s' % iplist) + return set(iplist) + + def _resolvedByDNS(self, fqdn): + args = [ + self.command.get('dig'), + fqdn + ] + rc, stdout, stderr = self.execute( + args=args, + raiseOnError=False + ) + resolved = False + if rc == 0: + for line in stdout: + if self._DIG_LOOKUP_RE.search(line): + resolved = True + return resolved + + def _dig_reverse_lookup(self, addr): + names = set() + args = [ + self.command.get('dig'), + '-x', + addr, + ] + rc, stdout, stderr = self.execute( + args=args, + raiseOnError=False + ) + if rc == 0: + for line in stdout: + found = self._DIG_REVLOOKUP_RE.search(line) + if found: + names.add(found.group('answer')) + return names + + def _validateFQDNresolvability(self, fqdn): + + try: + resolvedAddresses = set([ + address[0] for __, __, __, __, address in + socket.getaddrinfo( + fqdn, + None, + # Currently we need an IPv4 address and ignore the rest. + socket.AF_INET, + ) + ]) + self.logger.debug( + '{fqdn} resolves to: {addresses}'.format( + fqdn=fqdn, + addresses=resolvedAddresses, + ) + ) + resolvedAddressesAsString = ' '.join(resolvedAddresses) + except socket.error: + raise RuntimeError( + _('{fqdn} did not resolve into an IP address').format( + fqdn=fqdn, + ) + ) + + resolvedByDNS = self._resolvedByDNS(fqdn) + if not resolvedByDNS: + self.logger.warning( + _( + 'Failed to resolve {fqdn} using DNS, ' + 'it can be resolved only locally' + ).format( + fqdn=fqdn, + ) + ) + elif self.environment[osetupcons.ConfigEnv.FQDN_REVERSE_VALIDATION]: + revResolved = False + for address in resolvedAddresses: + for name in self._dig_reverse_lookup(address): + revResolved = name.lower() == fqdn.lower() + if revResolved: + break + if revResolved: + break + if not revResolved: + raise RuntimeError( + _( + 'The following addresses: {addresses} did not reverse' + 'resolve into {fqdn}' + ).format( + addresses=resolvedAddressesAsString, + fqdn=fqdn + ) + ) + + if self.environment[ + osetupcons.ConfigEnv.FQDN_NON_LOOPBACK_VALIDATION + ]: + if not resolvedAddresses.issubset(self._getNonLoopbackAddresses()): + raise RuntimeError( + _( + 'The following addreses: ' + "{addresses} can't be mapped " + 'to non loopback devices on this host' + ).format( + addresses=resolvedAddressesAsString + ) + ) + + def _validateFQDN(self, fqdn): + if self._IPADDR_RE.match(fqdn): + raise RuntimeError( + _( + '{fqdn} is an IP address and not a FQDN. ' + 'A FQDN is needed to be able to generate ' + 'certificates correctly.' + ).format( + fqdn=fqdn, + ) + ) + + if not fqdn: + raise RuntimeError( + _('Please specify host FQDN') + ) + + if len(fqdn) > 1000: + raise RuntimeError( + _('FQDN has invalid length') + ) + + components = fqdn.split('.', 1) + if len(components) == 1 or not components[0]: + self.logger.warning( + _('Host name {fqdn} has no domain suffix').format( + fqdn=fqdn, + ) + ) + else: + if not self._DOMAIN_RE.match(components[1]): + raise RuntimeError( + _('Host name {fqdn} has invalid domain name').format( + fqdn=fqdn, + ) + ) + + def getHostname(self, envkey, whichhost, supply_default): + interactive = self.environment[envkey] is None + validFQDN = False + while not validFQDN: + if interactive: + fqdn = socket.getfqdn() + self.environment[ + envkey + ] = self.dialog.queryString( + name='OVESETUP_NETWORK_FQDN', + note=_( + 'Host fully qualified DNS name of ' + '{whichhost} server [@DEFAULT@]: ' + ).format( + whichhost=whichhost, + ), + prompt=True, + default=fqdn if supply_default else '', + ) + try: + self._validateFQDN( + self.environment[envkey] + ) + self._validateFQDNresolvability( + self.environment[envkey] + ) + validFQDN = True + except RuntimeError as e: + self.logger.error( + _('Host name is not valid: {error}').format( + error=e, + ), + ) + self.logger.debug('exception', exc_info=True) + if not interactive: + break + + +# vim: expandtab tabstop=4 shiftwidth=4 diff --git a/packaging/setup/plugins/ovirt-engine-setup/base/network/hostname.py b/packaging/setup/plugins/ovirt-engine-setup/base/network/hostname.py index ac432fd..d6c6e21 100644 --- a/packaging/setup/plugins/ovirt-engine-setup/base/network/hostname.py +++ b/packaging/setup/plugins/ovirt-engine-setup/base/network/hostname.py @@ -19,8 +19,6 @@ """Hostname plugin.""" -import re -import socket import gettext _ = lambda m: gettext.dgettext(message=m, domain='ovirt-engine-setup') @@ -30,260 +28,15 @@ from ovirt_engine_setup import constants as osetupcons +from ovirt_engine_setup import hostname as osetuphostname @util.export class Plugin(plugin.PluginBase): """Protocols plugin.""" - _IPADDR_RE = re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}') - - _DOMAIN_RE = re.compile( - flags=re.VERBOSE, - pattern=r""" - ^ - [A-Za-z0-9\.\-]+ - \w+ - $ - """ - ) - - _INTERFACE_RE = re.compile( - flags=re.VERBOSE, - pattern=r""" - ^ - \d+ - : - \s+ - (?P<interface>\w+(\.\w+)?)(@\w+)? - : - \s+ - <(?P<options>[^>]+) - .* - """ - ) - - _ADDRESS_RE = re.compile( - flags=re.VERBOSE, - pattern=r""" - \s+ - inet - \s - (?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - .+ - \s+ - (?P<interface>\w+(\.\w+(@\w+)?)?) - $ - """ - ) - - _DIG_LOOKUP_RE = re.compile( - flags=re.VERBOSE, - pattern=r""" - ^ - [\w.-]+ - \s+ - \d+ - \s+ - IN - \s+ - (A|CNAME) - \s+ - [\w.-]+ - """ - ) - - _DIG_REVLOOKUP_RE = re.compile( - flags=re.VERBOSE, - pattern=r""" - ^ - [\w/.-]+\.in-addr\.arpa\. - \s+ - \d+ - \s+ - IN - \s+ - PTR - \s+ - (?P<answer>[\w/.-]+) - \. - $ - """ - ) def __init__(self, context): super(Plugin, self).__init__(context=context) - - def _getNonLoopbackAddresses(self): - interfaces = {} - addresses = {} - rc, stdout, stderr = self.execute( - args=( - self.command.get('ip'), - 'addr', - ), - ) - for line in stdout: - interfacematch = self._INTERFACE_RE.match(line) - addressmatch = self._ADDRESS_RE.match(line) - if interfacematch is not None: - interfaces[ - interfacematch.group('interface') - ] = 'LOOPBACK' in interfacematch.group('options') - elif addressmatch is not None: - addresses.setdefault( - addressmatch.group('interface'), - [] - ).append( - addressmatch.group('address') - ) - iplist = [] - for interface, loopback in interfaces.iteritems(): - if not loopback: - iplist.extend(addresses.get(interface, [])) - - self.logger.debug('addresses: %s' % iplist) - return set(iplist) - - def _resolvedByDNS(self, fqdn): - args = [ - self.command.get('dig'), - fqdn - ] - rc, stdout, stderr = self.execute( - args=args, - raiseOnError=False - ) - resolved = False - if rc == 0: - for line in stdout: - if self._DIG_LOOKUP_RE.search(line): - resolved = True - return resolved - - def _dig_reverse_lookup(self, addr): - names = set() - args = [ - self.command.get('dig'), - '-x', - addr, - ] - rc, stdout, stderr = self.execute( - args=args, - raiseOnError=False - ) - if rc == 0: - for line in stdout: - found = self._DIG_REVLOOKUP_RE.search(line) - if found: - names.add(found.group('answer')) - return names - - def _validateFQDNresolvability(self, fqdn): - - try: - resolvedAddresses = set([ - address[0] for __, __, __, __, address in - socket.getaddrinfo( - fqdn, - None, - # Currently we need an IPv4 address and ignore the rest. - socket.AF_INET, - ) - ]) - self.logger.debug( - '{fqdn} resolves to: {addresses}'.format( - fqdn=fqdn, - addresses=resolvedAddresses, - ) - ) - resolvedAddressesAsString = ' '.join(resolvedAddresses) - except socket.error: - raise RuntimeError( - _('{fqdn} did not resolve into an IP address').format( - fqdn=fqdn, - ) - ) - - resolvedByDNS = self._resolvedByDNS(fqdn) - if not resolvedByDNS: - self.logger.warning( - _( - 'Failed to resolve {fqdn} using DNS, ' - 'it can be resolved only locally' - ).format( - fqdn=fqdn, - ) - ) - elif self.environment[osetupcons.ConfigEnv.FQDN_REVERSE_VALIDATION]: - revResolved = False - for address in resolvedAddresses: - for name in self._dig_reverse_lookup(address): - revResolved = name.lower() == fqdn.lower() - if revResolved: - break - if revResolved: - break - if not revResolved: - raise RuntimeError( - _( - 'The following addresses: {addresses} did not reverse' - 'resolve into {fqdn}' - ).format( - addresses=resolvedAddressesAsString, - fqdn=fqdn - ) - ) - - if self.environment[ - osetupcons.ConfigEnv.FQDN_NON_LOOPBACK_VALIDATION - ]: - if not resolvedAddresses.issubset(self._getNonLoopbackAddresses()): - raise RuntimeError( - _( - 'The following addreses: ' - "{addresses} can't be mapped " - 'to non loopback devices on this host' - ).format( - addresses=resolvedAddressesAsString - ) - ) - - def _validateFQDN(self, fqdn): - if self._IPADDR_RE.match(fqdn): - raise RuntimeError( - _( - '{fqdn} is an IP address and not a FQDN. ' - 'A FQDN is needed to be able to generate ' - 'certificates correctly.' - ).format( - fqdn=fqdn, - ) - ) - - if not fqdn: - raise RuntimeError( - _('Please specify host FQDN') - ) - - if len(fqdn) > 1000: - raise RuntimeError( - _('FQDN has invalid length') - ) - - components = fqdn.split('.', 1) - if len(components) == 1 or not components[0]: - self.logger.warning( - _('Host name {fqdn} has no domain suffix').format( - fqdn=fqdn, - ) - ) - else: - if not self._DOMAIN_RE.match(components[1]): - raise RuntimeError( - _('Host name {fqdn} has invalid domain name').format( - fqdn=fqdn, - ) - ) @plugin.event( stage=plugin.Stages.STAGE_INIT, @@ -320,39 +73,13 @@ ), ) def _customization(self): - interactive = self.environment[osetupcons.ConfigEnv.FQDN] is None - validFQDN = False - while not validFQDN: - if interactive: - fqdn = socket.getfqdn() - self.environment[ - osetupcons.ConfigEnv.FQDN - ] = self.dialog.queryString( - name='OVESETUP_NETWORK_FQDN', - note=_( - 'Host fully qualified DNS name of ' - 'this server [@DEFAULT@]: ' - ), - prompt=True, - default=fqdn, - ) - try: - self._validateFQDN( - self.environment[osetupcons.ConfigEnv.FQDN] - ) - self._validateFQDNresolvability( - self.environment[osetupcons.ConfigEnv.FQDN] - ) - validFQDN = True - except RuntimeError as e: - self.logger.error( - _('Host name is not valid: {error}').format( - error=e, - ), - ) - self.logger.debug('exception', exc_info=True) - if not interactive: - break + osetuphostname.Hostname( + plugin=self, + ).getHostname( + envkey=osetupcons.ConfigEnv.FQDN, + whichhost=_('this'), + supply_default=True, + ) # vim: expandtab tabstop=4 shiftwidth=4 diff --git a/packaging/setup/plugins/ovirt-engine-setup/ovirt-engine-common/dialog/titles.py b/packaging/setup/plugins/ovirt-engine-setup/ovirt-engine-common/dialog/titles.py index 1afc968..411fab5 100644 --- a/packaging/setup/plugins/ovirt-engine-setup/ovirt-engine-common/dialog/titles.py +++ b/packaging/setup/plugins/ovirt-engine-setup/ovirt-engine-common/dialog/titles.py @@ -109,6 +109,19 @@ @plugin.event( stage=plugin.Stages.STAGE_CUSTOMIZATION, + name=oengcommcons.Stages.NETWORK_OWNERS_CONFIG_CUSTOMIZED, + before=( + osetupcons.Stages.DIALOG_TITLES_E_NETWORK, + ), + after=( + osetupcons.Stages.DIALOG_TITLES_S_NETWORK, + ), + ) + def _network_owners_config_customized(self): + pass + + @plugin.event( + stage=plugin.Stages.STAGE_CUSTOMIZATION, name=osetupcons.Stages.DIALOG_TITLES_E_NETWORK, after=( osetupcons.Stages.DIALOG_TITLES_S_NETWORK, diff --git a/packaging/setup/plugins/ovirt-engine-setup/ovirt-engine/config/protocols.py b/packaging/setup/plugins/ovirt-engine-setup/ovirt-engine/config/protocols.py index c30d4af..4cfaa4e 100644 --- a/packaging/setup/plugins/ovirt-engine-setup/ovirt-engine/config/protocols.py +++ b/packaging/setup/plugins/ovirt-engine-setup/ovirt-engine/config/protocols.py @@ -43,6 +43,23 @@ super(Plugin, self).__init__(context=context) @plugin.event( + stage=plugin.Stages.STAGE_CUSTOMIZATION, + condition=lambda self: self.environment[oenginecons.CoreEnv.ENABLE], + after=( + osetupcons.Stages.CONFIG_PROTOCOLS_CUSTOMIZATION, + ), + before=( + oengcommcons.Stages.NETWORK_OWNERS_CONFIG_CUSTOMIZED, + ), + ) + def _customization(self): + self.environment[ + oenginecons.ConfigEnv.ENGINE_FQDN + ] = self.environment[ + osetupcons.ConfigEnv.FQDN + ] + + @plugin.event( stage=plugin.Stages.STAGE_MISC, condition=lambda self: self.environment[oenginecons.CoreEnv.ENABLE], ) -- To view, visit http://gerrit.ovirt.org/33062 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I8747002a14de6d96d5a7e185258d8ee0955b8a24 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: ovirt-engine-3.5 Gerrit-Owner: Yedidyah Bar David <d...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches