diff --git a/hooks/charmhelpers/contrib/openstack/context.py b/hooks/charmhelpers/contrib/openstack/context.py index 9b80b6d6..a68fd23d 100644 --- a/hooks/charmhelpers/contrib/openstack/context.py +++ b/hooks/charmhelpers/contrib/openstack/context.py @@ -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 diff --git a/hooks/nova_compute_context.py b/hooks/nova_compute_context.py index fb41acf6..7e3202b3 100644 --- a/hooks/nova_compute_context.py +++ b/hooks/nova_compute_context.py @@ -16,7 +16,6 @@ import json import os import platform import shutil -import socket import uuid from charmhelpers.core.unitdata import kv @@ -795,23 +794,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 diff --git a/hooks/nova_compute_hooks.py b/hooks/nova_compute_hooks.py index 1ef6394b..547ce7ac 100755 --- a/hooks/nova_compute_hooks.py +++ b/hooks/nova_compute_hooks.py @@ -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() install_vaultlocker() diff --git a/hooks/nova_compute_utils.py b/hooks/nova_compute_utils.py index db000019..6ab27015 100644 --- a/hooks/nova_compute_utils.py +++ b/hooks/nova_compute_utils.py @@ -180,6 +180,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'], @@ -213,7 +226,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': [], diff --git a/templates/stein/nova.conf b/templates/stein/nova.conf index 4264c8dc..ab258373 100644 --- a/templates/stein/nova.conf +++ b/templates/stein/nova.conf @@ -17,8 +17,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 -%} diff --git a/templates/train/nova.conf b/templates/train/nova.conf index 18b27202..2706d056 100644 --- a/templates/train/nova.conf +++ b/templates/train/nova.conf @@ -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 -%} diff --git a/unit_tests/test_actions_openstack_upgrade.py b/unit_tests/test_actions_openstack_upgrade.py index f5beaa68..fbf855bb 100644 --- a/unit_tests/test_actions_openstack_upgrade.py +++ b/unit_tests/test_actions_openstack_upgrade.py @@ -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'): diff --git a/unit_tests/test_nova_compute_contexts.py b/unit_tests/test_nova_compute_contexts.py index 3c65e160..99f6a07b 100644 --- a/unit_tests/test_nova_compute_contexts.py +++ b/unit_tests/test_nova_compute_contexts.py @@ -656,25 +656,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): diff --git a/unit_tests/test_nova_compute_hooks.py b/unit_tests/test_nova_compute_hooks.py index 961bad54..e7b9f77a 100644 --- a/unit_tests/test_nova_compute_hooks.py +++ b/unit_tests/test_nova_compute_hooks.py @@ -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') diff --git a/unit_tests/test_nova_compute_utils.py b/unit_tests/test_nova_compute_utils.py index b1883996..8ed99543 100644 --- a/unit_tests/test_nova_compute_utils.py +++ b/unit_tests/test_nova_compute_utils.py @@ -1134,3 +1134,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)