Use a stable hostname to render nova.conf
OVS introduced a new service called ovs-record-hostname.service which records the hostname on the first start in the ovs database to identify the ovn chassis, this is how it achieved a stable hostname and be resilient to the changes in the FQDN when the DNS gets available. This change introduces the same approach for nova-compute charm. In the first run of the NovaComputeHostInfoContext the value passed in the context as host_fqdn is stored in the unit's kv db, and re-used on every subsequent call. This change affects only new installs since the hint to store (or not) the host fqdn is set in the install hook. Change-Id: I2aa74442ec25b21201a47070077df27899465814 Closes-Bug: #1896630
This commit is contained in:
parent
75a3dbd0ef
commit
2bad8a0522
|
@ -16,8 +16,14 @@ import json
|
|||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import socket
|
||||
import uuid
|
||||
|
||||
from typing import (
|
||||
Dict,
|
||||
Optional,
|
||||
)
|
||||
|
||||
from charmhelpers.core.unitdata import kv
|
||||
from charmhelpers.contrib.openstack import context
|
||||
|
||||
|
@ -36,6 +42,7 @@ from charmhelpers.core.hookenv import (
|
|||
relation_ids,
|
||||
related_units,
|
||||
service_name,
|
||||
DEBUG,
|
||||
ERROR,
|
||||
INFO,
|
||||
)
|
||||
|
@ -1031,3 +1038,91 @@ class NovaComputeSWTPMContext(context.OSContextGenerator):
|
|||
}
|
||||
|
||||
return ctxt
|
||||
|
||||
|
||||
class NovaComputeHostInfoContext(context.HostInfoContext):
|
||||
|
||||
USE_FQDN_KEY = 'nova-compute-charm-use-fqdn'
|
||||
RECORD_FQDN_KEY = 'nova-compute-charm-record-fqdn'
|
||||
FQDN_KEY = 'nova-compute-charm-fqdn'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(use_fqdn_hint_cb=self._use_fqdn_hint)
|
||||
|
||||
@classmethod
|
||||
def _use_fqdn_hint(cls):
|
||||
"""Hint for whether FQDN should be used for agent registration
|
||||
|
||||
:returns: True or False
|
||||
:rtype: bool
|
||||
"""
|
||||
db = kv()
|
||||
return db.get(cls.USE_FQDN_KEY, False)
|
||||
|
||||
@classmethod
|
||||
def set_fqdn_hint(cls, value: bool):
|
||||
"""Set FQDN hint.
|
||||
|
||||
:param value: the value to set the FQDN hint to
|
||||
"""
|
||||
db = kv()
|
||||
db.set(cls.USE_FQDN_KEY, value)
|
||||
db.flush()
|
||||
|
||||
@classmethod
|
||||
def set_record_fqdn_hint(cls, value: bool):
|
||||
"""Set the hint to record the FQDN and reuse it on every call.
|
||||
|
||||
:param value: the value to the record FQDN hint to.
|
||||
"""
|
||||
db = kv()
|
||||
db.set(cls.RECORD_FQDN_KEY, value)
|
||||
db.flush()
|
||||
|
||||
@classmethod
|
||||
def get_record_fqdn_hint(cls) -> bool:
|
||||
"""Get the hint to record the FQDN."""
|
||||
db = kv()
|
||||
return db.get(cls.RECORD_FQDN_KEY, False)
|
||||
|
||||
def set_record_fqdn(self, fqdn: str):
|
||||
"""Store in the unit's DB the FQDN.
|
||||
|
||||
:param fqdn: the FQDN to store.
|
||||
"""
|
||||
db = kv()
|
||||
db.set(self.FQDN_KEY, fqdn)
|
||||
db.flush()
|
||||
|
||||
def get_record_fqdn(self) -> Optional[str]:
|
||||
"""Get the stored FQDN."""
|
||||
db = kv()
|
||||
return db.get(self.FQDN_KEY, None)
|
||||
|
||||
def __call__(self) -> Dict[str, str]:
|
||||
"""Generate host info context.
|
||||
|
||||
Extends the __call__() method to save the host fqdn used in the first
|
||||
run when the self.get_record_fqdn_hint() returns True, this allows to
|
||||
give a stable hostname to the nova-compute service over its entire
|
||||
life (see LP: #1896630).
|
||||
|
||||
:returns: context with host info
|
||||
"""
|
||||
name = socket.gethostname()
|
||||
if self.get_record_fqdn_hint():
|
||||
if not self.get_record_fqdn():
|
||||
log('Saving host fqdn', level=DEBUG)
|
||||
self.set_record_fqdn(self._get_canonical_name(name) or name)
|
||||
host_fqdn = self.get_record_fqdn()
|
||||
log('Re-using saved host fqdn stored: %s' % host_fqdn, level=DEBUG)
|
||||
else:
|
||||
host_fqdn = self._get_canonical_name(name) or name
|
||||
|
||||
ctxt = {
|
||||
'host_fqdn': host_fqdn,
|
||||
'host': name,
|
||||
'use_fqdn_hint': (
|
||||
self.use_fqdn_hint_cb() if self.use_fqdn_hint_cb else False)
|
||||
}
|
||||
return ctxt
|
||||
|
|
|
@ -125,7 +125,6 @@ from nova_compute_utils import (
|
|||
resume_unit_helper,
|
||||
remove_old_packages,
|
||||
MULTIPATH_PACKAGES,
|
||||
USE_FQDN_KEY,
|
||||
SWTPM_PACKAGES,
|
||||
)
|
||||
|
||||
|
@ -143,6 +142,7 @@ from nova_compute_context import (
|
|||
NovaAPIAppArmorContext,
|
||||
NovaComputeAppArmorContext,
|
||||
NovaNetworkAppArmorContext,
|
||||
NovaComputeHostInfoContext,
|
||||
)
|
||||
from charmhelpers.contrib.charmsupport import nrpe
|
||||
from charmhelpers.core.sysctl import create as create_sysctl
|
||||
|
@ -174,9 +174,8 @@ def install():
|
|||
# units with OpenStack release Stein or newer.
|
||||
release = os_release('nova-common')
|
||||
if CompareOpenStackReleases(release) >= 'stein':
|
||||
db = kv()
|
||||
db.set(USE_FQDN_KEY, True)
|
||||
db.flush()
|
||||
NovaComputeHostInfoContext.set_fqdn_hint(True)
|
||||
NovaComputeHostInfoContext.set_record_fqdn_hint(True) # LP: #1896630
|
||||
|
||||
install_vaultlocker()
|
||||
|
||||
|
|
|
@ -116,6 +116,7 @@ from nova_compute_context import (
|
|||
NovaComputePlacementContext,
|
||||
NovaComputeSWTPMContext,
|
||||
VirtMkfsContext,
|
||||
NovaComputeHostInfoContext,
|
||||
)
|
||||
|
||||
import charmhelpers.contrib.openstack.vaultlocker as vaultlocker
|
||||
|
@ -206,18 +207,6 @@ MOUNT_DEPENDENCY_OVERRIDE = '99-mount.conf'
|
|||
|
||||
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: {
|
||||
|
@ -263,7 +252,7 @@ BASE_RESOURCE_MAP = {
|
|||
vaultlocker.VAULTLOCKER_BACKEND),
|
||||
context.IdentityCredentialsContext(
|
||||
rel_name='cloud-credentials'),
|
||||
context.HostInfoContext(use_fqdn_hint_cb=use_fqdn_hint),
|
||||
NovaComputeHostInfoContext(),
|
||||
VirtMkfsContext(),
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1519,3 +1519,106 @@ class NovaComputeVirtMkfsContext(CharmTestCase):
|
|||
self.assertEqual({'virt_mkfs': ('virt_mkfs = default=mkfs.ext4\n'
|
||||
'virt_mkfs = windows=mkfs.ntfs')},
|
||||
ctxt())
|
||||
|
||||
|
||||
class TestNovaComputeHostInfoContext(CharmTestCase):
|
||||
def setUp(self):
|
||||
super().setUp(context, TO_PATCH)
|
||||
self.config.side_effect = self.test_config.get
|
||||
self.os_release.return_value = 'ussuri'
|
||||
|
||||
def test_use_fqdn_hint(self):
|
||||
self.kv().get.return_value = False
|
||||
ctxt = context.NovaComputeHostInfoContext
|
||||
self.assertEqual(ctxt._use_fqdn_hint(), False)
|
||||
self.kv().get.return_value = True
|
||||
self.assertEqual(ctxt._use_fqdn_hint(), True)
|
||||
|
||||
def test_set_fqdn_hint(self):
|
||||
context.NovaComputeHostInfoContext.set_fqdn_hint(True)
|
||||
self.kv().set.assert_called_with(
|
||||
context.NovaComputeHostInfoContext.USE_FQDN_KEY, True
|
||||
)
|
||||
|
||||
def test_set_record_fqdn_hint(self):
|
||||
context.NovaComputeHostInfoContext.set_record_fqdn_hint(True)
|
||||
self.kv().set.assert_called_with(
|
||||
context.NovaComputeHostInfoContext.RECORD_FQDN_KEY, True
|
||||
)
|
||||
|
||||
def test_get_record_fqdn_hint(self):
|
||||
self.kv().get.side_effect = dict().get
|
||||
self.assertFalse(
|
||||
context.NovaComputeHostInfoContext.get_record_fqdn_hint()
|
||||
)
|
||||
data = {context.NovaComputeHostInfoContext.RECORD_FQDN_KEY: True}
|
||||
self.kv().get.side_effect = lambda x, y: data[x]
|
||||
self.assertTrue(
|
||||
context.NovaComputeHostInfoContext.get_record_fqdn_hint()
|
||||
)
|
||||
|
||||
@patch('socket.getaddrinfo')
|
||||
@patch('socket.gethostname')
|
||||
def test_get_canonical_name_gethostname(self, gethostname, getaddrinfo):
|
||||
gethostname.return_value = 'bar'
|
||||
|
||||
def raise_oserror(name, *args, **kwargs):
|
||||
self.assertEqual(name, 'bar')
|
||||
raise OSError()
|
||||
|
||||
getaddrinfo.side_effect = raise_oserror
|
||||
data = {
|
||||
context.NovaComputeHostInfoContext.USE_FQDN_KEY: True,
|
||||
context.NovaComputeHostInfoContext.RECORD_FQDN_KEY: True,
|
||||
}
|
||||
self.kv().get.side_effect = lambda x, y: data.get(x, y)
|
||||
self.kv().set.side_effect = lambda x, y: data.__setitem__(x, y)
|
||||
ctxt = context.NovaComputeHostInfoContext()
|
||||
self.assertEqual(ctxt._get_canonical_name('bar'), '')
|
||||
|
||||
def fake_getaddrinfo(name, *args, **kwargs):
|
||||
self.assertEqual(name, 'foobar')
|
||||
return [[0, 1, 2, 'bar.example.com']]
|
||||
|
||||
getaddrinfo.reset_mock()
|
||||
getaddrinfo.side_effect = fake_getaddrinfo
|
||||
self.assertEqual(ctxt._get_canonical_name('foobar'), 'bar.example.com')
|
||||
gethostname.return_value = 'foobar'
|
||||
self.assertEqual(ctxt(),
|
||||
{'host_fqdn': 'bar.example.com',
|
||||
'host': 'foobar',
|
||||
'use_fqdn_hint': True})
|
||||
|
||||
@patch('socket.getaddrinfo')
|
||||
@patch('socket.gethostname')
|
||||
def test_call_unstable_hostname(self, gethostname, getaddrinfo):
|
||||
|
||||
def raise_oserror(name, *args, **kwargs):
|
||||
raise OSError()
|
||||
|
||||
def fake_getaddrinfo(name, *args, **kwargs):
|
||||
return [[0, 1, 2, 'bar.example.com']]
|
||||
|
||||
getaddrinfo.side_effect = raise_oserror
|
||||
gethostname.return_value = 'bar'
|
||||
data = {
|
||||
context.NovaComputeHostInfoContext.USE_FQDN_KEY: True,
|
||||
context.NovaComputeHostInfoContext.RECORD_FQDN_KEY: True,
|
||||
}
|
||||
self.kv().get.side_effect = lambda x, y: data.get(x, y)
|
||||
self.kv().set.side_effect = lambda x, y: data.__setitem__(x, y)
|
||||
ctxt = context.NovaComputeHostInfoContext()
|
||||
self.assertEqual(ctxt(),
|
||||
{'host_fqdn': 'bar',
|
||||
'host': 'bar',
|
||||
'use_fqdn_hint': True})
|
||||
# After the first run socket.getaddrinfo() returns a valid fqdn
|
||||
# provided by the DNS, but by this time the host_fqdn is stored and
|
||||
# re-used.
|
||||
getaddrinfo.reset_mock()
|
||||
getaddrinfo.side_effect = fake_getaddrinfo
|
||||
self.assertEqual(ctxt(),
|
||||
{'host_fqdn': 'bar',
|
||||
'host': 'bar',
|
||||
'use_fqdn_hint': True})
|
||||
getaddrinfo.assert_not_called()
|
||||
|
|
|
@ -25,7 +25,9 @@ from unittest.mock import (
|
|||
|
||||
|
||||
from test_utils import CharmTestCase
|
||||
|
||||
from nova_compute_context import (
|
||||
NovaComputeHostInfoContext
|
||||
)
|
||||
with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
|
||||
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
||||
lambda *args, **kwargs: f(*args, **kwargs))
|
||||
|
@ -146,7 +148,7 @@ class NovaComputeRelationsTests(CharmTestCase):
|
|||
self.is_container.return_value = False
|
||||
|
||||
@patch.object(hooks, 'configure_extra_repositories')
|
||||
@patch.object(hooks, 'kv')
|
||||
@patch('nova_compute_context.kv')
|
||||
@patch.object(hooks, 'os_release')
|
||||
def test_install_hook(self, _os_release, _kv, _configure_extra_repos):
|
||||
repo = 'cloud:precise-grizzly'
|
||||
|
@ -163,8 +165,10 @@ class NovaComputeRelationsTests(CharmTestCase):
|
|||
kv = MagicMock()
|
||||
_kv.return_value = kv
|
||||
hooks.install()
|
||||
kv.set.assert_called_once_with(hooks.USE_FQDN_KEY, True)
|
||||
kv.flush.assert_called_once_with()
|
||||
kv.set.assert_any_call(NovaComputeHostInfoContext.USE_FQDN_KEY, True)
|
||||
kv.set.assert_any_call(
|
||||
NovaComputeHostInfoContext.RECORD_FQDN_KEY, True)
|
||||
kv.flush.assert_any_call()
|
||||
|
||||
def test_configure_extra_repositories(self):
|
||||
"""Tests configuring of extra repositories"""
|
||||
|
|
|
@ -1477,13 +1477,6 @@ class NovaComputeUtilsTests(CharmTestCase):
|
|||
call('foo'),
|
||||
])
|
||||
|
||||
@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)
|
||||
|
||||
@patch.object(utils, 'render')
|
||||
def test_install_mount_override(self, render):
|
||||
utils.install_mount_override('/srv/test')
|
||||
|
|
Loading…
Reference in New Issue