Merge ~moon127/charm-k8s-unifi-poller/+git/charm-k8s-unifi-poller:multi-controller-support into charm-k8s-unifi-poller:master
- Git
- lp:~moon127/charm-k8s-unifi-poller/+git/charm-k8s-unifi-poller
- multi-controller-support
- Merge into master
Status: | Merged |
---|---|
Approved by: | Gareth Woolridge |
Approved revision: | 9d6a2530cd39db74adf77c77b01f36c1ed30c044 |
Merged at revision: | c4cd11792f372ee0a72379ee19c692ebb08092f8 |
Proposed branch: | ~moon127/charm-k8s-unifi-poller/+git/charm-k8s-unifi-poller:multi-controller-support |
Merge into: | charm-k8s-unifi-poller:master |
Diff against target: |
304 lines (+131/-42) 4 files modified
README.md (+3/-7) config.yaml (+23/-0) src/charm.py (+38/-12) tests/unit/test_charm.py (+67/-23) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tom Haddon | Approve | ||
John Lenton (community) | Approve | ||
Canonical IS Reviewers | Pending | ||
Review via email: mp+389962@code.launchpad.net |
Commit message
Add support for multiple controllers and other options/overrides supported by the upstream unifi-poller via extra_configs. Add tests and lint fixes.
Description of the change
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Tom Haddon (mthaddon) : | # |
Tom Haddon (mthaddon) wrote : | # |
Discussed IRL the question of up_ - there are other valid values we'd want to pass in besides up_unifi_
Tom Haddon (mthaddon) wrote : | # |
I'd have a small preference for `url.scheme not in ("http", "https")` over `url.scheme != ("https" or "http")` but other than that looks good, and I think this is ready for charmcrafters review.
John Lenton (chipaca) wrote : | # |
FWIW,
url.scheme != ("https" or "http")
is *not* the same as
url.scheme not in ("http", "https")
The former is True unless url.scheme is "https", because ("https" or "http") evaluates to "https", and then you're comparing url.scheme to that. You _probably_ were thinking
url.scheme != "https" and url.scheme != "http"
at which point it becomes a matter of personal preference; I find the 'not in' variant much easier to read, and a lot easier to expand if needed.
John Lenton (chipaca) : | # |
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision c4cd11792f372ee
Preview Diff
1 | diff --git a/README.md b/README.md |
2 | index a7792f3..e42a63b 100644 |
3 | --- a/README.md |
4 | +++ b/README.md |
5 | @@ -65,12 +65,8 @@ The charm optionally supports an externally deployed InfluxDB instance which may |
6 | juju config unifi-poller influxdb_username=unifipoller |
7 | juju config unifi=poller influxdb_password=unifipoller |
8 | |
9 | -### Scale Out Usage |
10 | +### Additional Controllers / Config Options |
11 | |
12 | -Currently the Unifi Poller charm supports only a single Unifi Network Controller per instance. |
13 | +The Unifi Poller charm supports configuring additional controllers and additional options, which can be added via the extra_configs charm option. |
14 | |
15 | -For now to poll multiple Unifi Network Controllers we would deploy multiple instances of unifi-poller charm with unique application names, and configure the controller_* config options on each for a specific controller: |
16 | - |
17 | - juju deploy unifi-poller.charm --config image_path=localhost:32000/unifi-poller:focal-latest unifi-poller-controller1 |
18 | - juju deploy unifi-poller.charm --config image_path=localhost:32000/unifi-poller:focal-latest unifi-poller-controller2 |
19 | - ... |
20 | +See config.yaml and the [full list of supported env vars](https://github.com/unifi-poller/unifi-poller/wiki/Configuration#config-file-and-environment-variables) for details. |
21 | diff --git a/config.yaml b/config.yaml |
22 | index 6ad6366..e17157f 100644 |
23 | --- a/config.yaml |
24 | +++ b/config.yaml |
25 | @@ -59,3 +59,26 @@ options: |
26 | description: | |
27 | The password associated with influxdb_user for access the InfluxDB specified in influxdb_url. |
28 | default: '' |
29 | + extra_configs: |
30 | + type: string |
31 | + description: | |
32 | + Additional config options, as yaml, to configure additional options and controllers. |
33 | + Full list of options from https://github.com/unifi-poller/unifi-poller/wiki/Configuration#config-file-and-environment-variables |
34 | + |
35 | + The controller_0 URL, user and password are set via the main charm options, although additional options can be set here. |
36 | + To add additional controllers start from controller_1. |
37 | + |
38 | + Example 1: |
39 | + - up_unifi_controller_1_url=https://<server_ip_1>:8443 |
40 | + - up_unifi_controller_1_user=unifipoller |
41 | + - up_unifi_controller_1_pass=unifipoller |
42 | + - up_unifi_controller_2_url=https://<server_ip_2>:8443 |
43 | + - up_unifi_controller_2_user=unifipoller |
44 | + - up_unifi_controller_2_pass=unifipoller |
45 | + - up_influxdb_interval=120s |
46 | + |
47 | + Example 2: |
48 | + ['up_unifi_controller_1_url=https://<server_1_ip>:8443', up_unifi_controller_1_user=unifipoller, \ |
49 | + up_unifi_controller_1_pass=unifipoller, 'up_unifi_controller_2_url=<server_2_ip>', \ |
50 | + up_unifi_controller_2_user=unifipoller, up_unifi_controller_2_pass=unifipoller, up_influxdb_interval=120s] |
51 | + default: '' |
52 | diff --git a/src/charm.py b/src/charm.py |
53 | index 61d25aa..20a530a 100755 |
54 | --- a/src/charm.py |
55 | +++ b/src/charm.py |
56 | @@ -3,6 +3,9 @@ |
57 | # See LICENSE file for licensing details. |
58 | |
59 | import logging |
60 | +import re |
61 | +from urllib.parse import urlparse |
62 | +import yaml |
63 | |
64 | from ops.charm import CharmBase |
65 | from ops.main import main |
66 | @@ -43,6 +46,10 @@ class UnifiPollerCharm(CharmBase): |
67 | if missing: |
68 | problems.append('required setting(s) missing: {}'.format(', '.join(sorted(missing)))) |
69 | |
70 | + extra_config_errors = self._bad_extra_config_charm_settings() |
71 | + if extra_config_errors: |
72 | + problems.append('invalid extra_config setting(s): {}'.format(', '.join(sorted(extra_config_errors)))) |
73 | + |
74 | return '; '.join(filter(None, problems)) |
75 | |
76 | def _missing_charm_settings(self): |
77 | @@ -60,6 +67,23 @@ class UnifiPollerCharm(CharmBase): |
78 | |
79 | return sorted(missing) |
80 | |
81 | + def _bad_extra_config_charm_settings(self): |
82 | + """Check extra_config configuration_settings.""" |
83 | + config = self.model.config |
84 | + |
85 | + errors = [] |
86 | + if config['extra_configs']: |
87 | + for c in yaml.safe_load(config['extra_configs']): |
88 | + k, value = c.split('=', 1) |
89 | + if not re.match(r'^UP_', k.upper()): |
90 | + errors.append(k) |
91 | + if re.match(r'.*_URL', k.upper()): |
92 | + url = urlparse(value) |
93 | + if url.scheme not in ("http", "https") or not url.netloc: |
94 | + errors.append(k) |
95 | + |
96 | + return sorted(errors) |
97 | + |
98 | def _make_pod_spec(self): |
99 | """Return a pod spec with some core configuration.""" |
100 | config = self.model.config |
101 | @@ -78,7 +102,9 @@ class UnifiPollerCharm(CharmBase): |
102 | 'imageDetails': image_details, |
103 | 'ports': [{'containerPort': METRICS_PORT, 'protocol': 'TCP'}], |
104 | 'envConfig': pod_config, |
105 | - 'kubernetes': {'readinessProbe': {'httpGet': {'path': '/check', 'port': METRICS_PORT}},}, |
106 | + 'kubernetes': { |
107 | + 'readinessProbe': {'httpGet': {'path': '/check', 'port': METRICS_PORT}}, |
108 | + }, |
109 | } |
110 | ], |
111 | } |
112 | @@ -86,19 +112,14 @@ class UnifiPollerCharm(CharmBase): |
113 | def _make_pod_config(self): |
114 | """Return an envConfig with some core configuration.""" |
115 | config = self.model.config |
116 | - pod_config = {} |
117 | - |
118 | - if config['controller_url']: |
119 | - pod_config['UP_UNIFI_DEFAULT_URL'] = config['controller_url'] |
120 | - |
121 | - if config['controller_username']: |
122 | - pod_config['UP_UNIFI_DEFAULT_USER'] = config['controller_username'] |
123 | - |
124 | - if config['controller_password']: |
125 | - pod_config['UP_UNIFI_DEFAULT_PASS'] = config['controller_password'] |
126 | + pod_config = { |
127 | + 'UP_UNIFI_CONTROLLER_0_URL': config['controller_url'], |
128 | + 'UP_UNIFI_CONTROLLER_0_USER': config['controller_username'], |
129 | + 'UP_UNIFI_CONTROLLER_0_PASS': config['controller_password'], |
130 | + } |
131 | |
132 | if config['controller_dpi']: |
133 | - pod_config['UP_UNIFI_DEFAULT_SAVE_DPI'] = config['controller_dpi'] |
134 | + pod_config['UP_UNIFI_CONTROLLER_0_SAVE_DPI'] = config['controller_dpi'] |
135 | |
136 | if config['influxdb_url']: |
137 | pod_config['UP_INFLUXDB_URL'] = config['influxdb_url'] |
138 | @@ -112,6 +133,11 @@ class UnifiPollerCharm(CharmBase): |
139 | if config['influxdb_password']: |
140 | pod_config['UP_INFLUXDB_PASS'] = config['influxdb_password'] |
141 | |
142 | + if config['extra_configs']: |
143 | + for c in yaml.safe_load(config['extra_configs']): |
144 | + k, value = c.split('=', 1) |
145 | + pod_config[k.upper()] = value |
146 | + |
147 | return pod_config |
148 | |
149 | def _configure_pod(self, event): |
150 | diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py |
151 | index 98a0b83..d3e787a 100644 |
152 | --- a/tests/unit/test_charm.py |
153 | +++ b/tests/unit/test_charm.py |
154 | @@ -10,56 +10,79 @@ CONFIG_ALL = { |
155 | 'image_path': 'example.com/unifi-poller:latest', |
156 | 'image_username': 'image_user', |
157 | 'image_password': 'image_pass', |
158 | - 'controller_url': 'https://192.168.88.1:8443/', |
159 | + 'controller_url': 'https://192.168.88.1:8443', |
160 | 'controller_username': 'unifipoller', |
161 | 'controller_password': 'unifipoller', |
162 | 'controller_dpi': 'true', |
163 | - 'influxdb_url': 'http://192.168.88.2:8086/', |
164 | + 'influxdb_url': 'http://192.168.88.2:8086', |
165 | 'influxdb_dbname': 'unifi-k8s', |
166 | 'influxdb_username': 'unifipoller', |
167 | 'influxdb_password': 'unifipoller', |
168 | + 'extra_configs': "['up_unifi_controller_1_url=https://192.168.88.253:8443', up_unifi_controller_1_user=unifipoller, \ |
169 | + up_unifi_controller_1_pass=unifipoller, 'up_unifi_controller_2_url=https://192.168.88.254:8443', \ |
170 | + up_unifi_controller_2_user=unifipoller, up_unifi_controller_2_pass=unifipoller]", |
171 | } |
172 | |
173 | CONFIG_NO_IMAGE = { |
174 | 'image_path': '', |
175 | 'image_username': '', |
176 | 'image_password': '', |
177 | - 'controller_url': 'https://192.168.88.1:8443/', |
178 | + 'controller_url': 'https://192.168.88.1:8443', |
179 | 'controller_username': 'unifipoller', |
180 | 'controller_password': 'unifipoller', |
181 | 'controller_dpi': 'true', |
182 | - 'influxdb_url': 'http://192.168.88.2:8086/', |
183 | + 'influxdb_url': 'http://192.168.88.2:8086', |
184 | 'influxdb_dbname': 'unifi-k8s', |
185 | 'influxdb_username': 'unifipoller', |
186 | 'influxdb_password': 'unifipoller', |
187 | + 'extra_configs': '', |
188 | } |
189 | |
190 | -CONFIG_IMAGE_NO_CREDS = { |
191 | +CONFIG_NO_CONTROLLER_CONFIG = { |
192 | 'image_path': 'example.com/unifi-poller:latest', |
193 | - 'image_username': '', |
194 | - 'image_password': '', |
195 | - 'controller_url': 'https://192.168.88.1:8443/', |
196 | + 'image_username': 'image_user', |
197 | + 'image_password': 'image_pass', |
198 | + 'controller_url': '', |
199 | + 'controller_username': '', |
200 | + 'controller_password': '', |
201 | + 'controller_dpi': '', |
202 | + 'influxdb_url': 'http://192.168.88.2:8086', |
203 | + 'influxdb_dbname': 'unifi-k8s', |
204 | + 'influxdb_username': 'unifipoller', |
205 | + 'influxdb_password': 'unifipoller', |
206 | + 'extra_configs': '', |
207 | +} |
208 | + |
209 | +CONFIG_INVALID_EXTRA_CONFIG = { |
210 | + 'image_path': 'example.com/unifi-poller:latest', |
211 | + 'image_username': 'image_user', |
212 | + 'image_password': 'image_pass', |
213 | + 'controller_url': 'https://192.168.88.1:8443', |
214 | 'controller_username': 'unifipoller', |
215 | 'controller_password': 'unifipoller', |
216 | 'controller_dpi': 'true', |
217 | - 'influxdb_url': 'http://192.168.88.2:8086/', |
218 | + 'influxdb_url': 'http://192.168.88.2:8086', |
219 | 'influxdb_dbname': 'unifi-k8s', |
220 | 'influxdb_username': 'unifipoller', |
221 | 'influxdb_password': 'unifipoller', |
222 | + 'extra_configs': "['up_unifi_controller_1_url=this:not-a-url', up_unifi_controller_1_user=unifipoller, \ |
223 | + up_unifi_controller_1_pass=unifipoller, influx_interval=60s]", |
224 | } |
225 | |
226 | -CONFIG_NO_CONTROLLER_CONFIG = { |
227 | - 'image_path': 'example.com/unifi-poller:latest', |
228 | +CONFIG_INVALID_EXTRA_CONFIG_NO_IMAGE = { |
229 | + 'image_path': '', |
230 | 'image_username': 'image_user', |
231 | 'image_password': 'image_pass', |
232 | - 'controller_url': '', |
233 | - 'controller_username': '', |
234 | - 'controller_password': '', |
235 | - 'controller_dpi': '', |
236 | - 'influxdb_url': 'http://192.168.88.2:8086/', |
237 | + 'controller_url': 'https://192.168.88.1:8443', |
238 | + 'controller_username': 'unifipoller', |
239 | + 'controller_password': 'unifipoller', |
240 | + 'controller_dpi': 'true', |
241 | + 'influxdb_url': 'http://192.168.88.2:8086', |
242 | 'influxdb_dbname': 'unifi-k8s', |
243 | 'influxdb_username': 'unifipoller', |
244 | 'influxdb_password': 'unifipoller', |
245 | + 'extra_configs': "['up_unifi_controller_1_url=this:not-a-url', up_unifi_controller_1_user=unifipoller, \ |
246 | + up_unifi_controller_1_pass=unifipoller, influx_interval=60s]", |
247 | } |
248 | |
249 | |
250 | @@ -83,18 +106,37 @@ class TestCharm(unittest.TestCase): |
251 | expected = 'required setting(s) missing: controller_password, controller_url, controller_username' |
252 | self.assertEqual(self.harness.charm._check_for_config_problems(), expected) |
253 | |
254 | + def test_check_for_invalid_extra_config(self): |
255 | + """Check for correctly reports invalid extra_configs options.""" |
256 | + self.harness.update_config(CONFIG_INVALID_EXTRA_CONFIG) |
257 | + expected = 'invalid extra_config setting(s): influx_interval, up_unifi_controller_1_url' |
258 | + self.assertEqual(self.harness.charm._check_for_config_problems(), expected) |
259 | + |
260 | + def test_check_for_missing_image_invalid_extra_config(self): |
261 | + """Check for correctly reports invalid extra_configs options.""" |
262 | + self.harness.update_config(CONFIG_INVALID_EXTRA_CONFIG_NO_IMAGE) |
263 | + expected = 'required setting(s) missing: image_path; \ |
264 | +invalid extra_config setting(s): influx_interval, up_unifi_controller_1_url' |
265 | + self.assertEqual(self.harness.charm._check_for_config_problems(), expected) |
266 | + |
267 | def test_make_pod_config(self): |
268 | """Make basic, correct pod config.""" |
269 | - self.harness.update_config(CONFIG_IMAGE_NO_CREDS) |
270 | + self.harness.update_config(CONFIG_ALL) |
271 | expected = { |
272 | 'UP_INFLUXDB_DB': 'unifi-k8s', |
273 | 'UP_INFLUXDB_PASS': 'unifipoller', |
274 | - 'UP_INFLUXDB_URL': 'http://192.168.88.2:8086/', |
275 | + 'UP_INFLUXDB_URL': 'http://192.168.88.2:8086', |
276 | 'UP_INFLUXDB_USER': 'unifipoller', |
277 | - 'UP_UNIFI_DEFAULT_PASS': 'unifipoller', |
278 | - 'UP_UNIFI_DEFAULT_SAVE_DPI': 'true', |
279 | - 'UP_UNIFI_DEFAULT_URL': 'https://192.168.88.1:8443/', |
280 | - 'UP_UNIFI_DEFAULT_USER': 'unifipoller', |
281 | + 'UP_UNIFI_CONTROLLER_0_PASS': 'unifipoller', |
282 | + 'UP_UNIFI_CONTROLLER_0_SAVE_DPI': 'true', |
283 | + 'UP_UNIFI_CONTROLLER_0_URL': 'https://192.168.88.1:8443', |
284 | + 'UP_UNIFI_CONTROLLER_0_USER': 'unifipoller', |
285 | + 'UP_UNIFI_CONTROLLER_1_PASS': 'unifipoller', |
286 | + 'UP_UNIFI_CONTROLLER_1_URL': 'https://192.168.88.253:8443', |
287 | + 'UP_UNIFI_CONTROLLER_1_USER': 'unifipoller', |
288 | + 'UP_UNIFI_CONTROLLER_2_USER': 'unifipoller', |
289 | + 'UP_UNIFI_CONTROLLER_2_PASS': 'unifipoller', |
290 | + 'UP_UNIFI_CONTROLLER_2_URL': 'https://192.168.88.254:8443', |
291 | } |
292 | self.assertEqual(self.harness.charm._make_pod_config(), expected) |
293 | |
294 | @@ -113,7 +155,9 @@ class TestCharm(unittest.TestCase): |
295 | }, |
296 | 'ports': [{'containerPort': 9130, 'protocol': 'TCP'}], |
297 | 'envConfig': self.harness.charm._make_pod_config(), |
298 | - 'kubernetes': {'readinessProbe': {'httpGet': {'path': '/check', 'port': 9130}},}, |
299 | + 'kubernetes': { |
300 | + 'readinessProbe': {'httpGet': {'path': '/check', 'port': 9130}}, |
301 | + }, |
302 | } |
303 | ], |
304 | } |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.