Merge ~xavpaice/charm-nrpe:lp1937888 into charm-nrpe:master
- Git
- lp:~xavpaice/charm-nrpe
- lp1937888
- Merge into master
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) |
Related bugs: |
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_
for binding the nrpe service, and if configured to do so returns the
public address for relation data and NRPE host exports.
Description of the change
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
A CI job is currently in progress. A follow up comment will be added when it completes.
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:1332acc124e
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
James Troup (elmo) wrote : | # |
See comment inline.
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
A CI job is currently in progress. A follow up comment will be added when it completes.
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:6dd95bb4bd2
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
A CI job is currently in progress. A follow up comment will be added when it completes.
Xav Paice (xavpaice) wrote : | # |
I've rewritten the change and added some tests.
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:4f7b0a08823
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:4f7b0a08823
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
A CI job is currently in progress. A follow up comment will be added when it completes.
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
FAILED: Continuous integration, rev:2edd09aa334
https:/
Executed test runs:
FAILURE: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
A CI job is currently in progress. A follow up comment will be added when it completes.
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
PASSED: Continuous integration, rev:a46e8c68dea
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
A CI job is currently in progress. A follow up comment will be added when it completes.
🤖 prod-jenkaas-bootstack (prod-jenkaas-bootstack) wrote : | # |
PASSED: Continuous integration, rev:4344ba9b531
https:/
Executed test runs:
SUCCESS: https:/
None: https:/
Click here to trigger a rebuild:
https:/
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision 74ca861645689f8
Preview Diff
1 | diff --git a/hooks/nrpe_helpers.py b/hooks/nrpe_helpers.py | |||
2 | index 8384d76..b4f40b9 100644 | |||
3 | --- a/hooks/nrpe_helpers.py | |||
4 | +++ b/hooks/nrpe_helpers.py | |||
5 | @@ -77,42 +77,42 @@ class Monitors(dict): | |||
6 | 77 | return mons | 77 | return mons |
7 | 78 | 78 | ||
8 | 79 | 79 | ||
10 | 80 | def get_local_ingress_address(binding): | 80 | def get_ingress_address(binding, external=False): |
11 | 81 | """Get ingress IP address for a binding. | 81 | """Get ingress IP address for a binding. |
12 | 82 | 82 | ||
14 | 83 | binding - e.g. 'monitors' | 83 | Returns a local IP address for incoming requests to NRPE. |
15 | 84 | |||
16 | 85 | :param binding: name of the binding, e.g. 'monitors' | ||
17 | 86 | :param external: bool, if True return the public address if charm config requests | ||
18 | 87 | otherwise return the local address which would be used for incoming | ||
19 | 88 | nrpe requests. | ||
20 | 84 | """ | 89 | """ |
21 | 85 | # using network-get to retrieve the address details if available. | 90 | # using network-get to retrieve the address details if available. |
22 | 86 | hookenv.log("Getting ingress IP address for binding %s" % binding) | 91 | hookenv.log("Getting ingress IP address for binding %s" % binding) |
23 | 92 | if hookenv.config("nagios_address_type").lower() == "public" and external: | ||
24 | 93 | return hookenv.unit_get("public-address") | ||
25 | 94 | |||
26 | 95 | ip_address = None | ||
27 | 87 | try: | 96 | try: |
28 | 88 | network_info = hookenv.network_get(binding) | 97 | network_info = hookenv.network_get(binding) |
29 | 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: |
30 | 90 | hookenv.log("Using ingress-addresses") | ||
31 | 91 | # workaround lp#1897261 | ||
32 | 92 | try: | 99 | try: |
33 | 93 | ip_address = network_info["bind-addresses"][0]["addresses"][0][ | 100 | ip_address = network_info["bind-addresses"][0]["addresses"][0][ |
34 | 94 | "address" | 101 | "address" |
35 | 95 | ] | 102 | ] |
36 | 103 | hookenv.log("Using ingress-addresses, found %s" % ip_address) | ||
37 | 96 | except KeyError: | 104 | except KeyError: |
57 | 97 | # ignore KeyError and populate ip_address per old method | 105 | hookenv.log("Using primary-addresses") |
58 | 98 | ip_address = None | 106 | ip_address = hookenv.network_get_primary_address(binding) |
59 | 99 | if ip_address not in network_info["ingress-addresses"]: | 107 | |
60 | 100 | ip_address = network_info["ingress-addresses"][0] | 108 | except (NotImplementedError, FileNotFoundError) as e: |
61 | 101 | hookenv.log(ip_address) | 109 | hookenv.log( |
62 | 102 | return ip_address | 110 | "Unable to determine inbound IP address for binding {} with {}".format( |
63 | 103 | except (NotImplementedError, FileNotFoundError): | 111 | binding, e |
64 | 104 | # We'll fallthrough to the Pre 2.3 code below. | 112 | ), |
65 | 105 | pass | 113 | level=hookenv.ERROR, |
66 | 106 | 114 | ) | |
67 | 107 | # Pre 2.3 output | 115 | |
49 | 108 | try: | ||
50 | 109 | ip_address = hookenv.network_get_primary_address(binding) | ||
51 | 110 | hookenv.log("Using primary-addresses") | ||
52 | 111 | except NotImplementedError: | ||
53 | 112 | # pre Juju 2.0 | ||
54 | 113 | ip_address = hookenv.unit_private_ip() | ||
55 | 114 | hookenv.log("Using unit_private_ip") | ||
56 | 115 | hookenv.log(ip_address) | ||
68 | 116 | return ip_address | 116 | return ip_address |
69 | 117 | 117 | ||
70 | 118 | 118 | ||
71 | @@ -200,7 +200,8 @@ class MonitorsRelation(helpers.RelationContext): | |||
72 | 200 | 200 | ||
73 | 201 | def provide_data(self): | 201 | def provide_data(self): |
74 | 202 | """Return relation info.""" | 202 | """Return relation info.""" |
76 | 203 | address = get_local_ingress_address("monitors") | 203 | # get the address to send to Nagios for host definition |
77 | 204 | address = get_ingress_address("monitors", external=True) | ||
78 | 204 | 205 | ||
79 | 205 | relation_info = { | 206 | relation_info = { |
80 | 206 | "target-id": self.principal_relation.nagios_hostname(), | 207 | "target-id": self.principal_relation.nagios_hostname(), |
81 | @@ -299,24 +300,10 @@ class NagiosInfo(dict): | |||
82 | 299 | ) | 300 | ) |
83 | 300 | self["nagios_hostname"] = self.principal_relation.nagios_hostname() | 301 | self["nagios_hostname"] = self.principal_relation.nagios_hostname() |
84 | 301 | 302 | ||
103 | 302 | address = None | 303 | # export_host.cfg.tmpl host definition for Nagios |
104 | 303 | if hookenv.config("nagios_address_type").lower() == "public": | 304 | self["nagios_ipaddress"] = get_ingress_address("monitors", external=True) |
105 | 304 | address = hookenv.unit_get("public-address") | 305 | # Address configured for NRPE to listen on |
106 | 305 | elif hookenv.config("nagios_master") != "None": | 306 | self["nrpe_ipaddress"] = get_ingress_address("monitors") |
89 | 306 | # Try to work out the correct interface/IP. We can't use both | ||
90 | 307 | # network-get nor 'unit-get private-address' because both can | ||
91 | 308 | # return the wrong IP on systems with more than one interface | ||
92 | 309 | # (LP: #1736050). | ||
93 | 310 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
94 | 311 | s.connect((hookenv.config("nagios_master").split(",")[0], 80)) | ||
95 | 312 | address = s.getsockname()[0] | ||
96 | 313 | s.close() | ||
97 | 314 | # Fallback to unit-get private-address | ||
98 | 315 | if not address: | ||
99 | 316 | address = hookenv.unit_get("private-address") | ||
100 | 317 | |||
101 | 318 | self["nagios_ipaddress"] = address | ||
102 | 319 | self["nrpe_ipaddress"] = get_local_ingress_address("monitors") | ||
107 | 320 | 307 | ||
108 | 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" |
109 | 322 | self["debug"] = "1" if hookenv.config("debug") else "0" | 309 | self["debug"] = "1" if hookenv.config("debug") else "0" |
110 | diff --git a/tests/functional/tests/bundles/focal.yaml b/tests/functional/tests/bundles/focal.yaml | |||
111 | index 2689a08..33bea11 100644 | |||
112 | --- a/tests/functional/tests/bundles/focal.yaml | |||
113 | +++ b/tests/functional/tests/bundles/focal.yaml | |||
114 | @@ -3,6 +3,7 @@ applications: | |||
115 | 3 | rabbitmq-server: | 3 | rabbitmq-server: |
116 | 4 | charm: cs:rabbitmq-server | 4 | charm: cs:rabbitmq-server |
117 | 5 | num_units: 1 | 5 | num_units: 1 |
118 | 6 | constraints: root-disk=8G | ||
119 | 6 | container: | 7 | container: |
120 | 7 | charm: cs:ubuntu | 8 | charm: cs:ubuntu |
121 | 8 | num_units: 1 | 9 | num_units: 1 |
122 | diff --git a/tests/functional/tests/tests.yaml b/tests/functional/tests/tests.yaml | |||
123 | index 8034f51..a661597 100644 | |||
124 | --- a/tests/functional/tests/tests.yaml | |||
125 | +++ b/tests/functional/tests/tests.yaml | |||
126 | @@ -1,7 +1,9 @@ | |||
127 | 1 | charm_name: nrpe | 1 | charm_name: nrpe |
128 | 2 | gate_bundles: | 2 | gate_bundles: |
130 | 3 | - xenial | 3 | - focal |
131 | 4 | - bionic | 4 | - bionic |
132 | 5 | - xenial | ||
133 | 6 | smoke_bundles: | ||
134 | 5 | - focal | 7 | - focal |
135 | 6 | tests: | 8 | tests: |
136 | 7 | - tests.nrpe_tests.TestNrpe | 9 | - tests.nrpe_tests.TestNrpe |
137 | diff --git a/tests/unit/test_nrpe_helpers.py b/tests/unit/test_nrpe_helpers.py | |||
138 | index fc17454..949eb92 100644 | |||
139 | --- a/tests/unit/test_nrpe_helpers.py | |||
140 | +++ b/tests/unit/test_nrpe_helpers.py | |||
141 | @@ -4,6 +4,7 @@ from unittest import mock | |||
142 | 4 | 4 | ||
143 | 5 | import netifaces | 5 | import netifaces |
144 | 6 | 6 | ||
145 | 7 | import nrpe_helpers | ||
146 | 7 | from nrpe_helpers import match_cidr_to_ifaces | 8 | from nrpe_helpers import match_cidr_to_ifaces |
147 | 8 | 9 | ||
148 | 9 | 10 | ||
149 | @@ -11,39 +12,107 @@ class TestMatchCidrToIfaces(unittest.TestCase): | |||
150 | 11 | """Test match_cidr_to_ifaces helper function.""" | 12 | """Test match_cidr_to_ifaces helper function.""" |
151 | 12 | 13 | ||
152 | 13 | mock_iface_ip_data = { | 14 | mock_iface_ip_data = { |
157 | 14 | 'lo': '127.0.0.1', | 15 | "lo": "127.0.0.1", |
158 | 15 | 'eno1': '10.0.0.0', | 16 | "eno1": "10.0.0.0", |
159 | 16 | 'eno2': '10.1.0.0', | 17 | "eno2": "10.1.0.0", |
160 | 17 | 'fan-252': '252.0.0.1', | 18 | "fan-252": "252.0.0.1", |
161 | 18 | } | 19 | } |
162 | 19 | 20 | ||
163 | 20 | def test_single_dev_match(self): | 21 | def test_single_dev_match(self): |
164 | 21 | """Test single interface match.""" | 22 | """Test single interface match.""" |
166 | 22 | self._run_mocked_test('10.0.0.0/16', ['eno1']) | 23 | self._run_mocked_test("10.0.0.0/16", ["eno1"]) |
167 | 23 | 24 | ||
168 | 24 | def test_multi_dev_match(self): | 25 | def test_multi_dev_match(self): |
169 | 25 | """Test multiple interface match.""" | 26 | """Test multiple interface match.""" |
171 | 26 | self._run_mocked_test('10.0.0.0/8', ['eno1', 'eno2']) | 27 | self._run_mocked_test("10.0.0.0/8", ["eno1", "eno2"]) |
172 | 27 | 28 | ||
173 | 28 | def test_no_dev_match(self): | 29 | def test_no_dev_match(self): |
174 | 29 | """Test no interface match.""" | 30 | """Test no interface match.""" |
176 | 30 | self._run_mocked_test('192.168.0.0/16', []) | 31 | self._run_mocked_test("192.168.0.0/16", []) |
177 | 31 | 32 | ||
178 | 32 | def test_cidr_with_host_bits_set(self): | 33 | def test_cidr_with_host_bits_set(self): |
179 | 33 | """Test invalid CIDR input (e.g. "eno1").""" | 34 | """Test invalid CIDR input (e.g. "eno1").""" |
180 | 34 | with self.assertRaises(Exception): | 35 | with self.assertRaises(Exception): |
182 | 35 | match_cidr_to_ifaces('10.1.2.3/8') # Should be 10.0.0.0/8 | 36 | match_cidr_to_ifaces("10.1.2.3/8") # Should be 10.0.0.0/8 |
183 | 36 | 37 | ||
184 | 37 | def test_iface_passed_in_as_cidr(self): | 38 | def test_iface_passed_in_as_cidr(self): |
185 | 38 | """Test invalid CIDR input (e.g. "eno1").""" | 39 | """Test invalid CIDR input (e.g. "eno1").""" |
186 | 39 | with self.assertRaises(Exception): | 40 | with self.assertRaises(Exception): |
188 | 40 | match_cidr_to_ifaces('eno1') | 41 | match_cidr_to_ifaces("eno1") |
189 | 41 | 42 | ||
192 | 42 | @mock.patch('netifaces.ifaddresses') | 43 | @mock.patch("netifaces.ifaddresses") |
193 | 43 | @mock.patch('netifaces.interfaces') | 44 | @mock.patch("netifaces.interfaces") |
194 | 44 | def _run_mocked_test(self, cidr, matches, ifaces_mock, addrs_mock): | 45 | def _run_mocked_test(self, cidr, matches, ifaces_mock, addrs_mock): |
195 | 45 | iface_ip_tuples = list(self.mock_iface_ip_data.items()) | 46 | iface_ip_tuples = list(self.mock_iface_ip_data.items()) |
196 | 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] |
199 | 47 | addrs_mock.side_effect = [{netifaces.AF_INET: [{'addr': t[1]}]} for t in | 48 | addrs_mock.side_effect = [ |
200 | 48 | iface_ip_tuples] | 49 | {netifaces.AF_INET: [{"addr": t[1]}]} for t in iface_ip_tuples |
201 | 50 | ] | ||
202 | 49 | self.assertEqual(match_cidr_to_ifaces(cidr), matches) | 51 | self.assertEqual(match_cidr_to_ifaces(cidr), matches) |
203 | 52 | |||
204 | 53 | |||
205 | 54 | class TestIngressAddress(unittest.TestCase): | ||
206 | 55 | """Test functions to provide a suitable ingress address.""" | ||
207 | 56 | |||
208 | 57 | @mock.patch("nrpe_helpers.hookenv.config") | ||
209 | 58 | @mock.patch("nrpe_helpers.hookenv.network_get") | ||
210 | 59 | def test_get_bind_address(self, mock_network_get, mock_config): | ||
211 | 60 | """Prove we get a local IP address for interface binding.""" | ||
212 | 61 | mock_config.return_value = "private" | ||
213 | 62 | mock_network_get.return_value = { | ||
214 | 63 | "bind-addresses": [ | ||
215 | 64 | { | ||
216 | 65 | "mac-address": "06:f1:3a:74:ad:fe", | ||
217 | 66 | "interface-name": "ens5", | ||
218 | 67 | "addresses": [ | ||
219 | 68 | { | ||
220 | 69 | "hostname": "", | ||
221 | 70 | "address": "172.31.29.247", | ||
222 | 71 | "cidr": "172.31.16.0/20", | ||
223 | 72 | } | ||
224 | 73 | ], | ||
225 | 74 | "macaddress": "06:f1:3a:74:ad:fe", | ||
226 | 75 | "interfacename": "ens5", | ||
227 | 76 | } | ||
228 | 77 | ], | ||
229 | 78 | "egress-subnets": ["3.8.134.119/32"], | ||
230 | 79 | "ingress-addresses": ["3.8.134.119"], | ||
231 | 80 | } | ||
232 | 81 | self.assertEqual(nrpe_helpers.get_ingress_address("mockbinding"), | ||
233 | 82 | "172.31.29.247") | ||
234 | 83 | |||
235 | 84 | @mock.patch("nrpe_helpers.hookenv.config") | ||
236 | 85 | @mock.patch("nrpe_helpers.hookenv.network_get") | ||
237 | 86 | def test_get_private_address(self, mock_network_get, mock_config): | ||
238 | 87 | """Prove we get a local IP address for Nagios relation.""" | ||
239 | 88 | mock_config.return_value = "private" | ||
240 | 89 | mock_network_get.return_value = { | ||
241 | 90 | "bind-addresses": [ | ||
242 | 91 | { | ||
243 | 92 | "mac-address": "06:f1:3a:74:ad:fe", | ||
244 | 93 | "interface-name": "ens5", | ||
245 | 94 | "addresses": [ | ||
246 | 95 | { | ||
247 | 96 | "hostname": "", | ||
248 | 97 | "address": "172.31.29.247", | ||
249 | 98 | "cidr": "172.31.16.0/20", | ||
250 | 99 | } | ||
251 | 100 | ], | ||
252 | 101 | "macaddress": "06:f1:3a:74:ad:fe", | ||
253 | 102 | "interfacename": "ens5", | ||
254 | 103 | } | ||
255 | 104 | ], | ||
256 | 105 | "egress-subnets": ["3.8.134.119/32"], | ||
257 | 106 | "ingress-addresses": ["3.8.134.119"], | ||
258 | 107 | } | ||
259 | 108 | self.assertEqual(nrpe_helpers.get_ingress_address("mockbinding", external=True), | ||
260 | 109 | "172.31.29.247") | ||
261 | 110 | |||
262 | 111 | @mock.patch("nrpe_helpers.hookenv.config") | ||
263 | 112 | @mock.patch("nrpe_helpers.hookenv.unit_get") | ||
264 | 113 | def test_get_public_address(self, mock_unit_get, mock_config): | ||
265 | 114 | """Prove we get a public IP address for Nagios relation.""" | ||
266 | 115 | mock_config.return_value = "public" | ||
267 | 116 | mock_unit_get.return_value = "1.2.3.4" | ||
268 | 117 | self.assertEqual(nrpe_helpers.get_ingress_address("mockbinding", external=True), | ||
269 | 118 | "1.2.3.4") |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.