Merge ~moon127/charm-k8s-unifi-poller/+git/charm-k8s-unifi-poller:multi-controller-support into charm-k8s-unifi-poller:master

Proposed by Gareth Woolridge
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)
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.

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
Tom Haddon (mthaddon) :
Revision history for this message
Tom Haddon (mthaddon) wrote :

Discussed IRL the question of up_ - there are other valid values we'd want to pass in besides up_unifi_controller_$blah (e.g. up_influxdb_interval), so checking for up_ makes sense. Agreed to switch to urlparse to avoid hard to read regex for the URLs though.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
John Lenton (chipaca) :
review: Approve
Revision history for this message
Tom Haddon (mthaddon) wrote :

Lgtm, thx

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

Change successfully merged at revision c4cd11792f372ee0a72379ee19c692ebb08092f8

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/README.md b/README.md
2index 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.
21diff --git a/config.yaml b/config.yaml
22index 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: ''
52diff --git a/src/charm.py b/src/charm.py
53index 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):
150diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
151index 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 }

Subscribers

People subscribed via source and target branches