Merge ~xavpaice/charm-nrpe:lp1937888 into charm-nrpe:master

Proposed by Xav Paice
Status: Merged
Approved by: James Troup
Approved revision: 4344ba9b531ddeabd017b53ebaa468900679315c
Merged at revision: 74ca861645689f8eeeeebea7175d7c4b7a35f68d
Proposed branch: ~xavpaice/charm-nrpe:lp1937888
Merge into: charm-nrpe:master
Diff against target: 269 lines (+115/-56)
4 files modified
hooks/nrpe_helpers.py (+29/-42)
tests/functional/tests/bundles/focal.yaml (+1/-0)
tests/functional/tests/tests.yaml (+3/-1)
tests/unit/test_nrpe_helpers.py (+82/-13)
Reviewer Review Type Date Requested Status
🤖 prod-jenkaas-bootstack (community) continuous-integration Approve
James Troup (community) Needs Fixing
BootStack Reviewers Pending
Review via email: mp+408524@code.launchpad.net

Commit message

Bind NRPE to local address

    In cases where we specify to use public address and/or Juju returns a
    public address regardless of the setting, we cannot use that address for
    binding the NRPE service.

    This change ensures get_local_ingress_address returns the local address
    for binding the nrpe service, and if configured to do so returns the
    public address for relation data and NRPE host exports.

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :

A CI job is currently in progress. A follow up comment will be added when it completes.

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
James Troup (elmo) wrote :

See comment inline.

review: Needs Fixing
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :

A CI job is currently in progress. A follow up comment will be added when it completes.

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :

A CI job is currently in progress. A follow up comment will be added when it completes.

Revision history for this message
Xav Paice (xavpaice) wrote :

I've rewritten the change and added some tests.

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :

A CI job is currently in progress. A follow up comment will be added when it completes.

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :

A CI job is currently in progress. A follow up comment will be added when it completes.

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Approve (continuous-integration)
Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :

A CI job is currently in progress. A follow up comment will be added when it completes.

Revision history for this message
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote :
review: Approve (continuous-integration)
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision 74ca861645689f8eeeeebea7175d7c4b7a35f68d

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/hooks/nrpe_helpers.py b/hooks/nrpe_helpers.py
index 8384d76..b4f40b9 100644
--- a/hooks/nrpe_helpers.py
+++ b/hooks/nrpe_helpers.py
@@ -77,42 +77,42 @@ class Monitors(dict):
77 return mons77 return mons
7878
7979
80def get_local_ingress_address(binding):80def get_ingress_address(binding, external=False):
81 """Get ingress IP address for a binding.81 """Get ingress IP address for a binding.
8282
83 binding - e.g. 'monitors'83 Returns a local IP address for incoming requests to NRPE.
84
85 :param binding: name of the binding, e.g. 'monitors'
86 :param external: bool, if True return the public address if charm config requests
87 otherwise return the local address which would be used for incoming
88 nrpe requests.
84 """89 """
85 # using network-get to retrieve the address details if available.90 # using network-get to retrieve the address details if available.
86 hookenv.log("Getting ingress IP address for binding %s" % binding)91 hookenv.log("Getting ingress IP address for binding %s" % binding)
92 if hookenv.config("nagios_address_type").lower() == "public" and external:
93 return hookenv.unit_get("public-address")
94
95 ip_address = None
87 try:96 try:
88 network_info = hookenv.network_get(binding)97 network_info = hookenv.network_get(binding)
89 if network_info is not None and "ingress-addresses" in network_info:98 if network_info is not None and "ingress-addresses" in network_info:
90 hookenv.log("Using ingress-addresses")
91 # workaround lp#1897261
92 try:99 try:
93 ip_address = network_info["bind-addresses"][0]["addresses"][0][100 ip_address = network_info["bind-addresses"][0]["addresses"][0][
94 "address"101 "address"
95 ]102 ]
103 hookenv.log("Using ingress-addresses, found %s" % ip_address)
96 except KeyError:104 except KeyError:
97 # ignore KeyError and populate ip_address per old method105 hookenv.log("Using primary-addresses")
98 ip_address = None106 ip_address = hookenv.network_get_primary_address(binding)
99 if ip_address not in network_info["ingress-addresses"]:107
100 ip_address = network_info["ingress-addresses"][0]108 except (NotImplementedError, FileNotFoundError) as e:
101 hookenv.log(ip_address)109 hookenv.log(
102 return ip_address110 "Unable to determine inbound IP address for binding {} with {}".format(
103 except (NotImplementedError, FileNotFoundError):111 binding, e
104 # We'll fallthrough to the Pre 2.3 code below.112 ),
105 pass113 level=hookenv.ERROR,
106114 )
107 # Pre 2.3 output115
108 try:
109 ip_address = hookenv.network_get_primary_address(binding)
110 hookenv.log("Using primary-addresses")
111 except NotImplementedError:
112 # pre Juju 2.0
113 ip_address = hookenv.unit_private_ip()
114 hookenv.log("Using unit_private_ip")
115 hookenv.log(ip_address)
116 return ip_address116 return ip_address
117117
118118
@@ -200,7 +200,8 @@ class MonitorsRelation(helpers.RelationContext):
200200
201 def provide_data(self):201 def provide_data(self):
202 """Return relation info."""202 """Return relation info."""
203 address = get_local_ingress_address("monitors")203 # get the address to send to Nagios for host definition
204 address = get_ingress_address("monitors", external=True)
204205
205 relation_info = {206 relation_info = {
206 "target-id": self.principal_relation.nagios_hostname(),207 "target-id": self.principal_relation.nagios_hostname(),
@@ -299,24 +300,10 @@ class NagiosInfo(dict):
299 )300 )
300 self["nagios_hostname"] = self.principal_relation.nagios_hostname()301 self["nagios_hostname"] = self.principal_relation.nagios_hostname()
301302
302 address = None303 # export_host.cfg.tmpl host definition for Nagios
303 if hookenv.config("nagios_address_type").lower() == "public":304 self["nagios_ipaddress"] = get_ingress_address("monitors", external=True)
304 address = hookenv.unit_get("public-address")305 # Address configured for NRPE to listen on
305 elif hookenv.config("nagios_master") != "None":306 self["nrpe_ipaddress"] = get_ingress_address("monitors")
306 # Try to work out the correct interface/IP. We can't use both
307 # network-get nor 'unit-get private-address' because both can
308 # return the wrong IP on systems with more than one interface
309 # (LP: #1736050).
310 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
311 s.connect((hookenv.config("nagios_master").split(",")[0], 80))
312 address = s.getsockname()[0]
313 s.close()
314 # Fallback to unit-get private-address
315 if not address:
316 address = hookenv.unit_get("private-address")
317
318 self["nagios_ipaddress"] = address
319 self["nrpe_ipaddress"] = get_local_ingress_address("monitors")
320307
321 self["dont_blame_nrpe"] = "1" if hookenv.config("dont_blame_nrpe") else "0"308 self["dont_blame_nrpe"] = "1" if hookenv.config("dont_blame_nrpe") else "0"
322 self["debug"] = "1" if hookenv.config("debug") else "0"309 self["debug"] = "1" if hookenv.config("debug") else "0"
diff --git a/tests/functional/tests/bundles/focal.yaml b/tests/functional/tests/bundles/focal.yaml
index 2689a08..33bea11 100644
--- a/tests/functional/tests/bundles/focal.yaml
+++ b/tests/functional/tests/bundles/focal.yaml
@@ -3,6 +3,7 @@ applications:
3 rabbitmq-server:3 rabbitmq-server:
4 charm: cs:rabbitmq-server4 charm: cs:rabbitmq-server
5 num_units: 15 num_units: 1
6 constraints: root-disk=8G
6 container:7 container:
7 charm: cs:ubuntu8 charm: cs:ubuntu
8 num_units: 19 num_units: 1
diff --git a/tests/functional/tests/tests.yaml b/tests/functional/tests/tests.yaml
index 8034f51..a661597 100644
--- a/tests/functional/tests/tests.yaml
+++ b/tests/functional/tests/tests.yaml
@@ -1,7 +1,9 @@
1charm_name: nrpe1charm_name: nrpe
2gate_bundles:2gate_bundles:
3 - xenial3 - focal
4 - bionic4 - bionic
5 - xenial
6smoke_bundles:
5 - focal7 - focal
6tests:8tests:
7 - tests.nrpe_tests.TestNrpe9 - tests.nrpe_tests.TestNrpe
diff --git a/tests/unit/test_nrpe_helpers.py b/tests/unit/test_nrpe_helpers.py
index fc17454..949eb92 100644
--- a/tests/unit/test_nrpe_helpers.py
+++ b/tests/unit/test_nrpe_helpers.py
@@ -4,6 +4,7 @@ from unittest import mock
44
5import netifaces5import netifaces
66
7import nrpe_helpers
7from nrpe_helpers import match_cidr_to_ifaces8from nrpe_helpers import match_cidr_to_ifaces
89
910
@@ -11,39 +12,107 @@ class TestMatchCidrToIfaces(unittest.TestCase):
11 """Test match_cidr_to_ifaces helper function."""12 """Test match_cidr_to_ifaces helper function."""
1213
13 mock_iface_ip_data = {14 mock_iface_ip_data = {
14 'lo': '127.0.0.1',15 "lo": "127.0.0.1",
15 'eno1': '10.0.0.0',16 "eno1": "10.0.0.0",
16 'eno2': '10.1.0.0',17 "eno2": "10.1.0.0",
17 'fan-252': '252.0.0.1',18 "fan-252": "252.0.0.1",
18 }19 }
1920
20 def test_single_dev_match(self):21 def test_single_dev_match(self):
21 """Test single interface match."""22 """Test single interface match."""
22 self._run_mocked_test('10.0.0.0/16', ['eno1'])23 self._run_mocked_test("10.0.0.0/16", ["eno1"])
2324
24 def test_multi_dev_match(self):25 def test_multi_dev_match(self):
25 """Test multiple interface match."""26 """Test multiple interface match."""
26 self._run_mocked_test('10.0.0.0/8', ['eno1', 'eno2'])27 self._run_mocked_test("10.0.0.0/8", ["eno1", "eno2"])
2728
28 def test_no_dev_match(self):29 def test_no_dev_match(self):
29 """Test no interface match."""30 """Test no interface match."""
30 self._run_mocked_test('192.168.0.0/16', [])31 self._run_mocked_test("192.168.0.0/16", [])
3132
32 def test_cidr_with_host_bits_set(self):33 def test_cidr_with_host_bits_set(self):
33 """Test invalid CIDR input (e.g. "eno1")."""34 """Test invalid CIDR input (e.g. "eno1")."""
34 with self.assertRaises(Exception):35 with self.assertRaises(Exception):
35 match_cidr_to_ifaces('10.1.2.3/8') # Should be 10.0.0.0/836 match_cidr_to_ifaces("10.1.2.3/8") # Should be 10.0.0.0/8
3637
37 def test_iface_passed_in_as_cidr(self):38 def test_iface_passed_in_as_cidr(self):
38 """Test invalid CIDR input (e.g. "eno1")."""39 """Test invalid CIDR input (e.g. "eno1")."""
39 with self.assertRaises(Exception):40 with self.assertRaises(Exception):
40 match_cidr_to_ifaces('eno1')41 match_cidr_to_ifaces("eno1")
4142
42 @mock.patch('netifaces.ifaddresses')43 @mock.patch("netifaces.ifaddresses")
43 @mock.patch('netifaces.interfaces')44 @mock.patch("netifaces.interfaces")
44 def _run_mocked_test(self, cidr, matches, ifaces_mock, addrs_mock):45 def _run_mocked_test(self, cidr, matches, ifaces_mock, addrs_mock):
45 iface_ip_tuples = list(self.mock_iface_ip_data.items())46 iface_ip_tuples = list(self.mock_iface_ip_data.items())
46 ifaces_mock.return_value = [t[0] for t in iface_ip_tuples]47 ifaces_mock.return_value = [t[0] for t in iface_ip_tuples]
47 addrs_mock.side_effect = [{netifaces.AF_INET: [{'addr': t[1]}]} for t in48 addrs_mock.side_effect = [
48 iface_ip_tuples]49 {netifaces.AF_INET: [{"addr": t[1]}]} for t in iface_ip_tuples
50 ]
49 self.assertEqual(match_cidr_to_ifaces(cidr), matches)51 self.assertEqual(match_cidr_to_ifaces(cidr), matches)
52
53
54class TestIngressAddress(unittest.TestCase):
55 """Test functions to provide a suitable ingress address."""
56
57 @mock.patch("nrpe_helpers.hookenv.config")
58 @mock.patch("nrpe_helpers.hookenv.network_get")
59 def test_get_bind_address(self, mock_network_get, mock_config):
60 """Prove we get a local IP address for interface binding."""
61 mock_config.return_value = "private"
62 mock_network_get.return_value = {
63 "bind-addresses": [
64 {
65 "mac-address": "06:f1:3a:74:ad:fe",
66 "interface-name": "ens5",
67 "addresses": [
68 {
69 "hostname": "",
70 "address": "172.31.29.247",
71 "cidr": "172.31.16.0/20",
72 }
73 ],
74 "macaddress": "06:f1:3a:74:ad:fe",
75 "interfacename": "ens5",
76 }
77 ],
78 "egress-subnets": ["3.8.134.119/32"],
79 "ingress-addresses": ["3.8.134.119"],
80 }
81 self.assertEqual(nrpe_helpers.get_ingress_address("mockbinding"),
82 "172.31.29.247")
83
84 @mock.patch("nrpe_helpers.hookenv.config")
85 @mock.patch("nrpe_helpers.hookenv.network_get")
86 def test_get_private_address(self, mock_network_get, mock_config):
87 """Prove we get a local IP address for Nagios relation."""
88 mock_config.return_value = "private"
89 mock_network_get.return_value = {
90 "bind-addresses": [
91 {
92 "mac-address": "06:f1:3a:74:ad:fe",
93 "interface-name": "ens5",
94 "addresses": [
95 {
96 "hostname": "",
97 "address": "172.31.29.247",
98 "cidr": "172.31.16.0/20",
99 }
100 ],
101 "macaddress": "06:f1:3a:74:ad:fe",
102 "interfacename": "ens5",
103 }
104 ],
105 "egress-subnets": ["3.8.134.119/32"],
106 "ingress-addresses": ["3.8.134.119"],
107 }
108 self.assertEqual(nrpe_helpers.get_ingress_address("mockbinding", external=True),
109 "172.31.29.247")
110
111 @mock.patch("nrpe_helpers.hookenv.config")
112 @mock.patch("nrpe_helpers.hookenv.unit_get")
113 def test_get_public_address(self, mock_unit_get, mock_config):
114 """Prove we get a public IP address for Nagios relation."""
115 mock_config.return_value = "public"
116 mock_unit_get.return_value = "1.2.3.4"
117 self.assertEqual(nrpe_helpers.get_ingress_address("mockbinding", external=True),
118 "1.2.3.4")

Subscribers

People subscribed via source and target branches

to all changes: