Merge lp:~tribaal/landscape-charm/add-root-url into lp:~landscape/landscape-charm/trunk

Proposed by Chris Glass
Status: Merged
Approved by: Chris Glass
Approved revision: 256
Merged at revision: 253
Proposed branch: lp:~tribaal/landscape-charm/add-root-url
Merge into: lp:~landscape/landscape-charm/trunk
Diff against target: 282 lines (+167/-5)
10 files modified
config.yaml (+6/-0)
hooks/lib/relations/config.py (+23/-0)
hooks/lib/relations/haproxy.py (+2/-1)
hooks/lib/relations/tests/test_config.py (+59/-0)
hooks/lib/relations/tests/test_landscape.py (+1/-1)
hooks/lib/services.py (+3/-2)
hooks/lib/tests/test_templates.py (+25/-1)
hooks/lib/tests/test_utils.py (+29/-0)
hooks/lib/utils.py (+14/-0)
templates/service.conf (+5/-0)
To merge this branch: bzr merge lp:~tribaal/landscape-charm/add-root-url
Reviewer Review Type Date Requested Status
Free Ekanayaka (community) Approve
Benji York (community) Approve
🤖 Landscape Builder test results Approve
Review via email: mp+256918@code.launchpad.net

Commit message

This branch introduces a root-url global configuration item, and sets it to either the
value of the root-url charm configuration option or (if it's not present), the
haproxy's public-address (as set throught the website relation).

Description of the change

This branch introduces a root-url global configuration item, and sets it to either the
value of the root-url charm configuration option or (if it's not present), the
haproxy's public-address (as set throught the website relation).

To post a comment you must log in.
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 252
Branch: lp:~tribaal/landscape-charm/add-root-url
Jenkins: https://ci.lscape.net/job/latch-test/520/

review: Approve (test results)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 253
Branch: lp:~tribaal/landscape-charm/add-root-url
Jenkins: https://ci.lscape.net/job/latch-test/522/

review: Approve (test results)
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Looks good, however I have blocking comment, see below.

review: Needs Fixing
Revision history for this message
Free Ekanayaka (free.ekanayaka) :
Revision history for this message
Free Ekanayaka (free.ekanayaka) :
Revision history for this message
Alberto Donato (ack) :
Revision history for this message
Free Ekanayaka (free.ekanayaka) :
Revision history for this message
Benji York (benji) wrote :

This branch looks good.

review: Approve
254. By Chris Glass

Addressed review comments, refactoring.

Revision history for this message
Chris Glass (tribaal) wrote :

All comments addressed.

Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Thanks! +1 with a couple of minors

review: Approve
255. By Chris Glass

Fixed last comments.

Revision history for this message
Chris Glass (tribaal) wrote :

Fixed last comments.

256. By Chris Glass

lint

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2015-04-13 10:57:02 +0000
3+++ config.yaml 2015-04-23 14:34:15 +0000
4@@ -48,6 +48,12 @@
5 Has no effect if changed after a deployment.
6 type: string
7 default:
8+ root-url:
9+ description: |
10+ The root URL for this landscape deployment. If left blank the public
11+ IP of the first related HAproxy unit will be used instead.
12+ type: string
13+ default: ""
14 ssl-cert:
15 type: string
16 description: |
17
18=== added file 'hooks/lib/relations/config.py'
19--- hooks/lib/relations/config.py 1970-01-01 00:00:00 +0000
20+++ hooks/lib/relations/config.py 2015-04-23 14:34:15 +0000
21@@ -0,0 +1,23 @@
22+from charmhelpers.core import hookenv
23+from lib.hook import HookError
24+from lib.utils import is_valid_url
25+
26+
27+class ConfigRequirer(dict):
28+ """Dependency manager for the service configuration.
29+
30+ Take care of validating and exposing configuration values for use
31+ in service manager callbacks."""
32+
33+ def __init__(self, hookenv=hookenv):
34+ config = hookenv.config()
35+ self._validate(config)
36+ self.update({"config": config})
37+
38+ def _validate(self, config):
39+ root_url = config.get("root-url")
40+ if root_url and not is_valid_url(root_url):
41+ raise HookError(
42+ "The 'root-url' configuration value is not a valid URL."
43+ " Please make sure it is of the form"
44+ " 'http[s]://<hostname>/'")
45
46=== modified file 'hooks/lib/relations/haproxy.py'
47--- hooks/lib/relations/haproxy.py 2015-04-17 09:48:05 +0000
48+++ hooks/lib/relations/haproxy.py 2015-04-23 14:34:15 +0000
49@@ -94,7 +94,8 @@
50 "crts": self._get_ssl_certificate(),
51 "servers": [self._get_server("appserver")],
52 "backends": [
53- self._get_backend("message", [self._get_server("message-server")]),
54+ self._get_backend(
55+ "message", [self._get_server("message-server")]),
56 self._get_backend("api", [self._get_server("api")]),
57 ],
58 })
59
60=== added file 'hooks/lib/relations/tests/test_config.py'
61--- hooks/lib/relations/tests/test_config.py 1970-01-01 00:00:00 +0000
62+++ hooks/lib/relations/tests/test_config.py 2015-04-23 14:34:15 +0000
63@@ -0,0 +1,59 @@
64+from lib.tests.helpers import HookenvTest
65+from lib.relations.config import ConfigRequirer
66+from lib.hook import HookError
67+
68+
69+class ServicesHookTest(HookenvTest):
70+
71+ def test_root_url_is_set_and_invalid(self):
72+ """
73+ If an invalid root-url config option is set, a HookeError is raised.
74+ """
75+ self.hookenv.config().update({"root-url": "asd"})
76+
77+ with self.assertRaises(HookError) as error:
78+ ConfigRequirer(hookenv=self.hookenv)
79+
80+ expected = ("The 'root-url' configuration value is not a valid URL."
81+ " Please make sure it is of the form"
82+ " 'http[s]://<hostname>/'")
83+ self.assertEqual(expected, error.exception.message)
84+
85+ def test_root_url_is_set_without_protocol(self):
86+ """
87+ If an invalid root-url config option is set without a protocol, a
88+ HookError is raised.
89+ """
90+ self.hookenv.config().update({"root-url": "example.com/"})
91+ with self.assertRaises(HookError) as error:
92+ ConfigRequirer(hookenv=self.hookenv)
93+
94+ expected = ("The 'root-url' configuration value is not a valid URL."
95+ " Please make sure it is of the form"
96+ " 'http[s]://<hostname>/'")
97+ self.assertEqual(expected, error.exception.message)
98+
99+ def test_root_url_is_set_without_trailing_slash(self):
100+ """
101+ If an invalid root-url config option is set without a trailing slash,
102+ a HookError is raised.
103+ """
104+ self.hookenv.config().update({"root-url": "https://example.com"})
105+ with self.assertRaises(HookError) as error:
106+ ConfigRequirer(hookenv=self.hookenv)
107+
108+ expected = ("The 'root-url' configuration value is not a valid URL."
109+ " Please make sure it is of the form"
110+ " 'http[s]://<hostname>/'")
111+ self.assertEqual(expected, error.exception.message)
112+
113+ def test_a_valid_root_url_configuration_key_is_set(self):
114+ """No excpetion is raised if the root-url config entry is valid.
115+
116+ The resulting ConfigRequirer is a dict with a "config" entry that
117+ matches the contents of the config passed.
118+ """
119+ self.hookenv.config().update({"root-url": "https://example.com/"})
120+ result = ConfigRequirer(hookenv=self.hookenv)
121+ self.assertEqual({"config": {"root-url": "https://example.com/"}},
122+ result)
123
124=== modified file 'hooks/lib/relations/tests/test_landscape.py'
125--- hooks/lib/relations/tests/test_landscape.py 2015-04-17 09:40:16 +0000
126+++ hooks/lib/relations/tests/test_landscape.py 2015-04-23 14:34:15 +0000
127@@ -138,7 +138,7 @@
128 "secret-token": "old-token",
129 "leader-ip": "old-ip"}))
130 context = LandscapeLeaderContext(
131- host=self.host, path=self.path, hookenv=self.hookenv)
132+ host=self.host, path=self.path, hookenv=self.hookenv)
133 self.assertEqual({"database-password": "old-sekret",
134 "secret-token": "old-token",
135 "leader-ip": "old-ip"}, context)
136
137=== modified file 'hooks/lib/services.py'
138--- hooks/lib/services.py 2015-04-17 11:34:11 +0000
139+++ hooks/lib/services.py 2015-04-23 14:34:15 +0000
140@@ -13,6 +13,7 @@
141 HAProxyProvider, HAProxyRequirer, OFFLINE_FOLDER)
142 from lib.relations.landscape import (
143 LandscapeLeaderContext, LandscapeRequirer, LandscapeProvider)
144+from lib.relations.config import ConfigRequirer
145 from lib.relations.hosted import HostedRequirer
146 from lib.callbacks.scripts import SchemaBootstrap, LSCtl
147 from lib.callbacks.filesystem import (
148@@ -60,12 +61,12 @@
149 # Required data is available to the render_template calls below.
150 "required_data": [
151 LandscapeRequirer(leader_context),
152+ ConfigRequirer(self._hookenv),
153 PostgreSQLRequirer(),
154 RabbitMQRequirer(),
155 HAProxyRequirer(),
156 HostedRequirer(),
157- {"config": hookenv.config(),
158- "is_leader": is_leader},
159+ {"is_leader": is_leader},
160 ],
161 "data_ready": [
162 render_template(
163
164=== modified file 'hooks/lib/tests/test_templates.py'
165--- hooks/lib/tests/test_templates.py 2015-04-20 08:42:13 +0000
166+++ hooks/lib/tests/test_templates.py 2015-04-23 14:34:15 +0000
167@@ -4,7 +4,7 @@
168 from lib.tests.helpers import TemplateTest
169 from lib.tests.sample import (
170 SAMPLE_DB_UNIT_DATA, SAMPLE_LEADER_CONTEXT_DATA, SAMPLE_AMQP_UNIT_DATA,
171- SAMPLE_HOSTED_DATA)
172+ SAMPLE_HOSTED_DATA, SAMPLE_WEBSITE_UNIT_DATA)
173
174
175 class ServiceConfTest(TemplateTest):
176@@ -16,6 +16,7 @@
177 self.context = {
178 "db": [SAMPLE_DB_UNIT_DATA.copy()],
179 "amqp": [SAMPLE_AMQP_UNIT_DATA.copy()],
180+ "haproxy": SAMPLE_WEBSITE_UNIT_DATA,
181 "leader": SAMPLE_LEADER_CONTEXT_DATA.copy(),
182 "hosted": [SAMPLE_HOSTED_DATA.copy()],
183 "config": {},
184@@ -93,6 +94,29 @@
185 config.readfp(buffer)
186 self.assertEqual("1.2.3.4", config.get("package-search", "host"))
187
188+ def test_render_with_haproxy_address_as_root_url(self):
189+ """
190+ The service.conf file has root-url set to the haproxy public IP if the
191+ config doesn't have a root-url entry.
192+ """
193+ self.context["haproxy"]["public-address"] = "4.3.2.1"
194+
195+ buffer = StringIO(self.template.render(self.context))
196+ config = ConfigParser()
197+ config.readfp(buffer)
198+ self.assertEqual("https://4.3.2.1/", config.get("global", "root-url"))
199+
200+ def test_render_with_config_provided_root_url(self):
201+ """
202+ The service.conf file has root-url set to the content of the root-url
203+ charm config option if it is specified.
204+ """
205+ self.context["config"]["root-url"] = "https://8.8.8.8/"
206+ buffer = StringIO(self.template.render(self.context))
207+ config = ConfigParser()
208+ config.readfp(buffer)
209+ self.assertEqual("https://8.8.8.8/", config.get("global", "root-url"))
210+
211
212 class LandscapeDefaultsTest(TemplateTest):
213
214
215=== added file 'hooks/lib/tests/test_utils.py'
216--- hooks/lib/tests/test_utils.py 1970-01-01 00:00:00 +0000
217+++ hooks/lib/tests/test_utils.py 2015-04-23 14:34:15 +0000
218@@ -0,0 +1,29 @@
219+import unittest
220+
221+from lib.utils import is_valid_url
222+
223+
224+class IsValidURLTestCase(unittest.TestCase):
225+
226+ def test_missing_trailing_slash(self):
227+ """No trailing slash means the URL is invalid for root-url."""
228+ self.assertFalse(is_valid_url("https://google.com"))
229+
230+ def test_missing_protocol(self):
231+ """A protocol (http or https) is needed for a URL to be a valid
232+ root-url."""
233+ self.assertFalse(is_valid_url("ftp://google.com/"))
234+
235+ def test_http_or_https_protocol(self):
236+ """The URL for the root-url should have either http or https as a
237+ protocol."""
238+ self.assertTrue(is_valid_url("http://google.com/"))
239+ self.assertTrue(is_valid_url("https://google.com/"))
240+
241+ def test_missing_protocol_delimiter(self):
242+ """We need the URL to have the protocol field"""
243+ self.assertFalse(is_valid_url("httpisawesome.com/"))
244+
245+ def test_correct_url_is_validated(self):
246+ """A "correct" URL is validated by the is_valid_url function."""
247+ self.assertTrue(is_valid_url("http://example.com:9090/blah/"))
248
249=== added file 'hooks/lib/utils.py'
250--- hooks/lib/utils.py 1970-01-01 00:00:00 +0000
251+++ hooks/lib/utils.py 2015-04-23 14:34:15 +0000
252@@ -0,0 +1,14 @@
253+
254+
255+def is_valid_url(value):
256+ """
257+ A helper to validate a string is a URL suitable to use as root-url.
258+ """
259+ if not value[-1] == "/":
260+ return False
261+ if not value.startswith("http"):
262+ return False
263+ if not "://" in value:
264+ return False
265+
266+ return True
267
268=== modified file 'templates/service.conf'
269--- templates/service.conf 2015-04-20 08:56:42 +0000
270+++ templates/service.conf 2015-04-23 14:34:15 +0000
271@@ -4,6 +4,11 @@
272 # Syslog address is either a socket path or server:port string.
273 syslog-address = /dev/log
274 deployment-mode = {{ hosted[0]["deployment-mode"] }}
275+{% if config["root-url"] %}
276+root-url = {{ config["root-url"] }}
277+{% else %}
278+root-url = https://{{ haproxy["public-address"] }}/
279+{% endif %}
280
281 [stores]
282 host = {{ postgres["host"] }}:{{ postgres["port"] }}

Subscribers

People subscribed via source and target branches