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 os
|
||||||
import platform
|
import platform
|
||||||
import shutil
|
import shutil
|
||||||
|
import socket
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from typing import (
|
||||||
|
Dict,
|
||||||
|
Optional,
|
||||||
|
)
|
||||||
|
|
||||||
from charmhelpers.core.unitdata import kv
|
from charmhelpers.core.unitdata import kv
|
||||||
from charmhelpers.contrib.openstack import context
|
from charmhelpers.contrib.openstack import context
|
||||||
|
|
||||||
|
@ -36,6 +42,7 @@ from charmhelpers.core.hookenv import (
|
||||||
relation_ids,
|
relation_ids,
|
||||||
related_units,
|
related_units,
|
||||||
service_name,
|
service_name,
|
||||||
|
DEBUG,
|
||||||
ERROR,
|
ERROR,
|
||||||
INFO,
|
INFO,
|
||||||
)
|
)
|
||||||
|
@ -1031,3 +1038,91 @@ class NovaComputeSWTPMContext(context.OSContextGenerator):
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctxt
|
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,
|
resume_unit_helper,
|
||||||
remove_old_packages,
|
remove_old_packages,
|
||||||
MULTIPATH_PACKAGES,
|
MULTIPATH_PACKAGES,
|
||||||
USE_FQDN_KEY,
|
|
||||||
SWTPM_PACKAGES,
|
SWTPM_PACKAGES,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -143,6 +142,7 @@ from nova_compute_context import (
|
||||||
NovaAPIAppArmorContext,
|
NovaAPIAppArmorContext,
|
||||||
NovaComputeAppArmorContext,
|
NovaComputeAppArmorContext,
|
||||||
NovaNetworkAppArmorContext,
|
NovaNetworkAppArmorContext,
|
||||||
|
NovaComputeHostInfoContext,
|
||||||
)
|
)
|
||||||
from charmhelpers.contrib.charmsupport import nrpe
|
from charmhelpers.contrib.charmsupport import nrpe
|
||||||
from charmhelpers.core.sysctl import create as create_sysctl
|
from charmhelpers.core.sysctl import create as create_sysctl
|
||||||
|
@ -174,9 +174,8 @@ def install():
|
||||||
# units with OpenStack release Stein or newer.
|
# units with OpenStack release Stein or newer.
|
||||||
release = os_release('nova-common')
|
release = os_release('nova-common')
|
||||||
if CompareOpenStackReleases(release) >= 'stein':
|
if CompareOpenStackReleases(release) >= 'stein':
|
||||||
db = kv()
|
NovaComputeHostInfoContext.set_fqdn_hint(True)
|
||||||
db.set(USE_FQDN_KEY, True)
|
NovaComputeHostInfoContext.set_record_fqdn_hint(True) # LP: #1896630
|
||||||
db.flush()
|
|
||||||
|
|
||||||
install_vaultlocker()
|
install_vaultlocker()
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,7 @@ from nova_compute_context import (
|
||||||
NovaComputePlacementContext,
|
NovaComputePlacementContext,
|
||||||
NovaComputeSWTPMContext,
|
NovaComputeSWTPMContext,
|
||||||
VirtMkfsContext,
|
VirtMkfsContext,
|
||||||
|
NovaComputeHostInfoContext,
|
||||||
)
|
)
|
||||||
|
|
||||||
import charmhelpers.contrib.openstack.vaultlocker as vaultlocker
|
import charmhelpers.contrib.openstack.vaultlocker as vaultlocker
|
||||||
|
@ -206,18 +207,6 @@ MOUNT_DEPENDENCY_OVERRIDE = '99-mount.conf'
|
||||||
|
|
||||||
LIBVIRT_TYPES = ['kvm', 'qemu', 'lxc']
|
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 = {
|
BASE_RESOURCE_MAP = {
|
||||||
NOVA_CONF: {
|
NOVA_CONF: {
|
||||||
|
@ -263,7 +252,7 @@ BASE_RESOURCE_MAP = {
|
||||||
vaultlocker.VAULTLOCKER_BACKEND),
|
vaultlocker.VAULTLOCKER_BACKEND),
|
||||||
context.IdentityCredentialsContext(
|
context.IdentityCredentialsContext(
|
||||||
rel_name='cloud-credentials'),
|
rel_name='cloud-credentials'),
|
||||||
context.HostInfoContext(use_fqdn_hint_cb=use_fqdn_hint),
|
NovaComputeHostInfoContext(),
|
||||||
VirtMkfsContext(),
|
VirtMkfsContext(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1519,3 +1519,106 @@ class NovaComputeVirtMkfsContext(CharmTestCase):
|
||||||
self.assertEqual({'virt_mkfs': ('virt_mkfs = default=mkfs.ext4\n'
|
self.assertEqual({'virt_mkfs': ('virt_mkfs = default=mkfs.ext4\n'
|
||||||
'virt_mkfs = windows=mkfs.ntfs')},
|
'virt_mkfs = windows=mkfs.ntfs')},
|
||||||
ctxt())
|
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 test_utils import CharmTestCase
|
||||||
|
from nova_compute_context import (
|
||||||
|
NovaComputeHostInfoContext
|
||||||
|
)
|
||||||
with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
|
with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
|
||||||
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
|
||||||
lambda *args, **kwargs: f(*args, **kwargs))
|
lambda *args, **kwargs: f(*args, **kwargs))
|
||||||
|
@ -146,7 +148,7 @@ class NovaComputeRelationsTests(CharmTestCase):
|
||||||
self.is_container.return_value = False
|
self.is_container.return_value = False
|
||||||
|
|
||||||
@patch.object(hooks, 'configure_extra_repositories')
|
@patch.object(hooks, 'configure_extra_repositories')
|
||||||
@patch.object(hooks, 'kv')
|
@patch('nova_compute_context.kv')
|
||||||
@patch.object(hooks, 'os_release')
|
@patch.object(hooks, 'os_release')
|
||||||
def test_install_hook(self, _os_release, _kv, _configure_extra_repos):
|
def test_install_hook(self, _os_release, _kv, _configure_extra_repos):
|
||||||
repo = 'cloud:precise-grizzly'
|
repo = 'cloud:precise-grizzly'
|
||||||
|
@ -163,8 +165,10 @@ class NovaComputeRelationsTests(CharmTestCase):
|
||||||
kv = MagicMock()
|
kv = MagicMock()
|
||||||
_kv.return_value = kv
|
_kv.return_value = kv
|
||||||
hooks.install()
|
hooks.install()
|
||||||
kv.set.assert_called_once_with(hooks.USE_FQDN_KEY, True)
|
kv.set.assert_any_call(NovaComputeHostInfoContext.USE_FQDN_KEY, True)
|
||||||
kv.flush.assert_called_once_with()
|
kv.set.assert_any_call(
|
||||||
|
NovaComputeHostInfoContext.RECORD_FQDN_KEY, True)
|
||||||
|
kv.flush.assert_any_call()
|
||||||
|
|
||||||
def test_configure_extra_repositories(self):
|
def test_configure_extra_repositories(self):
|
||||||
"""Tests configuring of extra repositories"""
|
"""Tests configuring of extra repositories"""
|
||||||
|
|
|
@ -1477,13 +1477,6 @@ class NovaComputeUtilsTests(CharmTestCase):
|
||||||
call('foo'),
|
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')
|
@patch.object(utils, 'render')
|
||||||
def test_install_mount_override(self, render):
|
def test_install_mount_override(self, render):
|
||||||
utils.install_mount_override('/srv/test')
|
utils.install_mount_override('/srv/test')
|
||||||
|
|
Loading…
Reference in New Issue