Use hosts official name for FQDN

The current implementations use of a specific interface to build
FQDN from has the undesired side effect of the ``nova-compute`` and
``neutron-openvswitch`` charms ending up with using different
hostnames in some situations.  It may also lead to use of a
identifier that is mutable throughout the lifetime of a deployment.

Use of a specific interface was chosen due to ``socket.getfqdn()``
not giving reliable results (https://bugs.python.org/issue5004).

This patch gets the FQDN by mimicking the behaviour of a call to
``hostname -f`` with fallback to shortname on failure.

Add relevant update from c-h.

Change-Id: I82db81937e5a46dc6bd222b7160ca1fa5b190c10
Closes-Bug: #1839300
(cherry-picked from 1869bfbc97)
This commit is contained in:
Frode Nordahl 2020-01-10 09:13:34 +01:00
parent 1530383a79
commit 66405e2092
9 changed files with 90 additions and 38 deletions

View File

@ -2177,9 +2177,66 @@ class LogrotateContext(OSContextGenerator):
class HostInfoContext(OSContextGenerator):
"""Context to provide host information."""
def __init__(self, use_fqdn_hint_cb=None):
"""Initialize HostInfoContext
:param use_fqdn_hint_cb: Callback whose return value used to populate
`use_fqdn_hint`
:type use_fqdn_hint_cb: Callable[[], bool]
"""
# Store callback used to get hint for whether FQDN should be used
# Depending on the workload a charm manages, the use of FQDN vs.
# shortname may be a deploy-time decision, i.e. behaviour can not
# change on charm upgrade or post-deployment configuration change.
# The hint is passed on as a flag in the context to allow the decision
# to be made in the Jinja2 configuration template.
self.use_fqdn_hint_cb = use_fqdn_hint_cb
def _get_canonical_name(self, name=None):
"""Get the official FQDN of the host
The implementation of ``socket.getfqdn()`` in the standard Python
library does not exhaust all methods of getting the official name
of a host ref Python issue https://bugs.python.org/issue5004
This function mimics the behaviour of a call to ``hostname -f`` to
get the official FQDN but returns an empty string if it is
unsuccessful.
:param name: Shortname to get FQDN on
:type name: Optional[str]
:returns: The official FQDN for host or empty string ('')
:rtype: str
"""
name = name or socket.gethostname()
fqdn = ''
if six.PY2:
exc = socket.error
else:
exc = OSError
try:
addrs = socket.getaddrinfo(
name, None, 0, socket.SOCK_DGRAM, 0, socket.AI_CANONNAME)
except exc:
pass
else:
for addr in addrs:
if addr[3]:
if '.' in addr[3]:
fqdn = addr[3]
break
return fqdn
def __call__(self):
name = socket.gethostname()
ctxt = {
'host_fqdn': socket.getfqdn(),
'host': socket.gethostname(),
'host_fqdn': self._get_canonical_name(name) or name,
'host': name,
'use_fqdn_hint': (
self.use_fqdn_hint_cb() if self.use_fqdn_hint_cb else False)
}
return ctxt

View File

@ -16,7 +16,6 @@ import json
import os
import platform
import shutil
import socket
import uuid
from charmhelpers.core.unitdata import kv
@ -784,23 +783,6 @@ class HostIPContext(context.OSContextGenerator):
# NOTE: do not format this even for ipv6 (see bug 1499656)
ctxt['host_ip'] = host_ip
# the contents of the Nova ``host`` configuration option is
# referenced throughout a OpenStack deployment, an example being
# Neutron port bindings. It's value should not change after a
# individual units initial deployment.
#
# We do want to migrate to using FQDNs so we enable this for new
# installations.
db = kv()
if db.get('nova-compute-charm-use-fqdn', False):
fqdn = socket.getfqdn(host_ip)
if '.' in fqdn:
# only populate the value if getfqdn() is able to find an
# actual FQDN for this host. If not, we revert back to
# not setting the configuration option and use Nova's
# default behaviour.
ctxt['host'] = fqdn
return ctxt

View File

@ -117,6 +117,7 @@ from nova_compute_utils import (
get_availability_zone,
remove_old_packages,
MULTIPATH_PACKAGES,
USE_FQDN_KEY,
)
from charmhelpers.contrib.network.ip import (
@ -163,7 +164,7 @@ def install():
release = os_release('nova-common')
if CompareOpenStackReleases(release) >= 'stein':
db = kv()
db.set('nova-compute-charm-use-fqdn', True)
db.set(USE_FQDN_KEY, True)
db.flush()

View File

@ -179,6 +179,19 @@ NOVA_NETWORK_AA_PROFILE_PATH = ('/etc/apparmor.d/{}'
LIBVIRT_TYPES = ['kvm', 'qemu', 'lxc']
USE_FQDN_KEY = 'nova-compute-charm-use-fqdn'
def use_fqdn_hint():
"""Hint for whether FQDN should be used for agent registration
:returns: True or False
:rtype: bool
"""
db = kv()
return db.get(USE_FQDN_KEY, False)
BASE_RESOURCE_MAP = {
NOVA_CONF: {
'services': ['nova-compute'],
@ -212,7 +225,9 @@ BASE_RESOURCE_MAP = {
vaultlocker.VaultKVContext(
vaultlocker.VAULTLOCKER_BACKEND),
context.IdentityCredentialsContext(
rel_name='cloud-credentials')],
rel_name='cloud-credentials'),
context.HostInfoContext(use_fqdn_hint_cb=use_fqdn_hint),
],
},
VENDORDATA_FILE: {
'services': [],

View File

@ -20,8 +20,8 @@ enabled_apis=osapi_compute,metadata
my_ip = {{ host_ip }}
force_raw_images = {{ force_raw_images }}
{% if host -%}
host = {{ host }}
{% if use_fqdn_hint and host_fqdn -%}
host = {{ host_fqdn }}
{% endif -%}
{% if debug -%}

View File

@ -19,7 +19,8 @@ os.environ['JUJU_UNIT_NAME'] = 'nova_compute'
with patch('charmhelpers.core.hookenv.config') as config:
config.return_value = 'nova'
import nova_compute_utils as utils # noqa
with patch('charmhelpers.contrib.openstack.context.HostInfoContext'):
import nova_compute_utils as utils # noqa
with patch('nova_compute_utils.restart_map'):
with patch('nova_compute_utils.register_configs'):

View File

@ -635,25 +635,14 @@ class NovaComputeContextTests(CharmTestCase):
libvirt = context.NovaComputeLibvirtContext()
self.assertFalse('cpu-mode' in libvirt())
@patch.object(context.socket, 'getfqdn')
@patch('subprocess.call')
def test_host_IP_context(self, _call, _getfqdn):
def test_host_IP_context(self, _call):
self.log = fake_log
self.get_relation_ip.return_value = '172.24.0.79'
self.kv.return_value = FakeUnitdata()
host_ip = context.HostIPContext()
self.assertEqual({'host_ip': '172.24.0.79'}, host_ip())
self.get_relation_ip.assert_called_with('cloud-compute',
cidr_network=None)
self.kv.return_value = FakeUnitdata(
**{'nova-compute-charm-use-fqdn': True})
_getfqdn.return_value = 'some'
host_ip = context.HostIPContext()
self.assertEqual({'host_ip': '172.24.0.79'}, host_ip())
_getfqdn.return_value = 'some.hostname'
host_ip = context.HostIPContext()
self.assertDictEqual({'host': 'some.hostname',
'host_ip': '172.24.0.79'}, host_ip())
@patch('subprocess.call')
def test_host_IP_context_ipv6(self, _call):

View File

@ -129,7 +129,7 @@ class NovaComputeRelationsTests(CharmTestCase):
kv = MagicMock()
_kv.return_value = kv
hooks.install()
kv.set.assert_called_once_with('nova-compute-charm-use-fqdn', True)
kv.set.assert_called_once_with(hooks.USE_FQDN_KEY, True)
kv.flush.assert_called_once_with()
@patch.object(hooks, 'ceph_changed')

View File

@ -1128,3 +1128,10 @@ class NovaComputeUtilsTests(CharmTestCase):
_hook_name.return_value = "post-series-upgrade"
self.assertEqual(_full,
utils.services_to_pause_or_resume())
@patch.object(utils, 'kv')
def test_use_fqdn_hint(self, _kv):
_kv().get.return_value = False
self.assertEquals(utils.use_fqdn_hint(), False)
_kv().get.return_value = True
self.assertEquals(utils.use_fqdn_hint(), True)