Merge lp:~stub/charm-helpers/bug-1557769-ensure-ip-address into lp:charm-helpers

Proposed by Stuart Bishop
Status: Needs review
Proposed branch: lp:~stub/charm-helpers/bug-1557769-ensure-ip-address
Merge into: lp:charm-helpers
Diff against target: 131 lines (+56/-9)
3 files modified
Makefile (+2/-2)
charmhelpers/core/hookenv.py (+19/-2)
tests/core/test_hookenv.py (+35/-5)
To merge this branch: bzr merge lp:~stub/charm-helpers/bug-1557769-ensure-ip-address
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+289128@code.launchpad.net

Description of the change

Juju 1.25.4 seems to be again returning names in some situations where we expect IP addresses. Fix this, even though it might be considered a Juju regression.

To post a comment you must log in.
Revision history for this message
Cory Johns (johnsca) wrote :

From my understanding, private-address was never guaranteed to return an IP and in fact almost always returns some form of host name, meaning this function was always poorly named. I believe it depends on the provider what is actually returned.

For the big data charms (and originally for CloudFoundry), we use a similar helper[1] to force it to IPs, but that doesn't support IPv6 as yours does. We also ran into at least one instance where the "host name" returned wasn't resolvable and the IP was part of the host name string and had to be parsed out. My point is just that there a lot of corner cases in what should be a simple function.

It would be ideal if Juju handled this for us and had a private-ip field as well.

[1]: https://github.com/juju-solutions/jujubigdata/blob/master/jujubigdata/utils.py#L404

Revision history for this message
Stuart Bishop (stub) wrote :

I'm leaving it up to the juju-core bug squad to decide if this is a juju-core bug or not :) I'm fixing it here for old versions of Juju, and because I need the fix right now for a production deployment. We can open feature requests if they decline to accept Bug #1557769, and if they get implemented, update this function to use it when it is available.

I think we should ignore the case where what is returned by unit-info private-address is not even a resolvable name - that is certainly a provider bug (and one I don't think exists anywhere any more?). If getaddrinfo can't decode it (an unparsable or unresolvable name), it is useless to charms and their software and I think I'm doing the right thing by raising a ValueError rather than, say, attempting to munge it or pass it on through for the charm to choke on it. Call sites can of course catch the ValueError and deal with it if they really need to.

Revision history for this message
Stuart Bishop (stub) wrote :

It seems that juju-core has accepted the change in 1.25.4 under some providers as a regression.

Even if not, we should fix charm-helpers to return a consistent value across providers.

The Cassandra charm under 1.25.4 or later has been broken for a few months now under some providers. I'll try to get a release out with a forked charm-helpers next week since it is griefing one of our projects, but i would be nice to get this landed upstream in charm-helpers.

Revision history for this message
Stuart Bishop (stub) wrote :

This is still occurring under Juju 2.1, so I'd like to get this landed in charm-helpers.

Unmerged revisions

550. By Stuart Bishop

Ensure IP addresses are returned by methods claiming to return IP addresses

549. By Stuart Bishop

Missing dependencies

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2015-12-08 05:07:07 +0000
+++ Makefile 2016-03-16 01:42:31 +0000
@@ -37,7 +37,7 @@
37 python setup.py install --user37 python setup.py install --user
3838
39.venv:39.venv:
40 sudo apt-get install -y gcc python-dev python-virtualenv python-apt40 sudo apt-get install -y gcc python-dev python-virtualenv python-apt distro-info git bzr zip
41 virtualenv .venv --system-site-packages41 virtualenv .venv --system-site-packages
42 .venv/bin/pip install -U pip42 .venv/bin/pip install -U pip
43 .venv/bin/pip install -U distribute43 .venv/bin/pip install -U distribute
@@ -45,7 +45,7 @@
45 .venv/bin/pip install bzr45 .venv/bin/pip install bzr
4646
47.venv3:47.venv3:
48 sudo apt-get install -y gcc python3-dev python-virtualenv python3-apt48 sudo apt-get install -y gcc python3-dev python-virtualenv python3-apt distro-info git bzr zip
49 virtualenv .venv3 --python=python3 --system-site-packages49 virtualenv .venv3 --python=python3 --system-site-packages
50 .venv3/bin/pip install -U pip50 .venv3/bin/pip install -U pip
51 .venv3/bin/pip install -U distribute51 .venv3/bin/pip install -U distribute
5252
=== modified file 'charmhelpers/core/hookenv.py'
--- charmhelpers/core/hookenv.py 2016-03-07 18:02:12 +0000
+++ charmhelpers/core/hookenv.py 2016-03-16 01:42:31 +0000
@@ -28,6 +28,7 @@
28import os28import os
29import json29import json
30import yaml30import yaml
31import socket
31import subprocess32import subprocess
32import sys33import sys
33import errno34import errno
@@ -628,12 +629,28 @@
628629
629def unit_public_ip():630def unit_public_ip():
630 """Get this unit's public IP address"""631 """Get this unit's public IP address"""
631 return unit_get('public-address')632 return _ensure_ip(unit_get('public-address'))
632633
633634
634def unit_private_ip():635def unit_private_ip():
635 """Get this unit's private IP address"""636 """Get this unit's private IP address"""
636 return unit_get('private-address')637 return _ensure_ip(unit_get('private-address'))
638
639
640def _ensure_ip(addr):
641 """If addr is a hostname, resolve it to an IP address"""
642 if not addr:
643 return None
644 # We need to use socket.getaddrinfo for IPv6 support.
645 info = socket.getaddrinfo(addr, None)
646 if info is None:
647 # Should never happen
648 raise ValueError("Invalid result None from getaddinfo")
649 try:
650 return info[0][4][0]
651 except IndexError:
652 # Should never happen
653 raise ValueError("Invalid result {!r} from getaddinfo".format(info))
637654
638655
639@cached656@cached
640657
=== modified file 'tests/core/test_hookenv.py'
--- tests/core/test_hookenv.py 2016-03-09 20:30:51 +0000
+++ tests/core/test_hookenv.py 2016-03-16 01:42:31 +0000
@@ -2,9 +2,10 @@
2import json2import json
3from subprocess import CalledProcessError3from subprocess import CalledProcessError
4import shutil4import shutil
5import socket
5import tempfile6import tempfile
6from mock import call, MagicMock, mock_open, patch, sentinel7from mock import call, MagicMock, mock_open, patch, sentinel
7from testtools import TestCase8from unittest import TestCase
8import yaml9import yaml
910
10import six11import six
@@ -361,17 +362,46 @@
361362
362 self.assertEqual(hookenv.local_unit(), 'foo')363 self.assertEqual(hookenv.local_unit(), 'foo')
363364
365 @patch('charmhelpers.core.hookenv._ensure_ip')
364 @patch('charmhelpers.core.hookenv.unit_get')366 @patch('charmhelpers.core.hookenv.unit_get')
365 def test_gets_unit_public_ip(self, _unitget):367 def test_gets_unit_public_ip(self, _unitget, _ensure_ip):
366 _unitget.return_value = sentinel.public_ip368 _unitget.return_value = sentinel.public_address
369 _ensure_ip.return_value = sentinel.public_ip
367 self.assertEqual(sentinel.public_ip, hookenv.unit_public_ip())370 self.assertEqual(sentinel.public_ip, hookenv.unit_public_ip())
368 _unitget.assert_called_once_with('public-address')371 _unitget.assert_called_once_with('public-address')
372 _ensure_ip.assert_called_once_with(sentinel.public_address)
369373
374 @patch('charmhelpers.core.hookenv._ensure_ip')
370 @patch('charmhelpers.core.hookenv.unit_get')375 @patch('charmhelpers.core.hookenv.unit_get')
371 def test_gets_unit_private_ip(self, _unitget):376 def test_gets_unit_private_ip(self, _unitget, _ensure_ip):
372 _unitget.return_value = sentinel.private_ip377 _unitget.return_value = sentinel.private_address
378 _ensure_ip.return_value = sentinel.private_ip
373 self.assertEqual(sentinel.private_ip, hookenv.unit_private_ip())379 self.assertEqual(sentinel.private_ip, hookenv.unit_private_ip())
374 _unitget.assert_called_once_with('private-address')380 _unitget.assert_called_once_with('private-address')
381 _ensure_ip.assert_called_once_with(sentinel.private_address)
382
383 @patch('socket.getaddrinfo')
384 def test_ensure_ip(self, info):
385 # Resolve names to ip addresses. Note that getaddrinfo
386 # accepts both names and ip addresses, passing through
387 # ip addresses unmolested.
388 info.return_value = [(socket.AF_INET, socket.SOCK_STREAM,
389 6, '', (sentinel.ip, 0))]
390 self.assertEqual(hookenv._ensure_ip(sentinel.address), sentinel.ip)
391
392 # Deal with bogus input as best we can.
393 self.assertIsNone(hookenv._ensure_ip(None))
394 self.assertIsNone(hookenv._ensure_ip(''))
395
396 # Deal with weird getaddrinfo return values that should never
397 # occur as best we can.
398 info.return_value = None
399 with self.assertRaises(ValueError):
400 hookenv._ensure_ip(sentinel.address)
401
402 info.return_value = []
403 with self.assertRaises(ValueError):
404 hookenv._ensure_ip(sentinel.address)
375405
376 @patch('charmhelpers.core.hookenv.os')406 @patch('charmhelpers.core.hookenv.os')
377 def test_checks_that_is_running_in_relation_hook(self, os_):407 def test_checks_that_is_running_in_relation_hook(self, os_):

Subscribers

People subscribed via source and target branches