Merge lp:~free.ekanayaka/charms/trusty/haproxy/advertise-public-ip into lp:charms/trusty/haproxy

Proposed by Free Ekanayaka
Status: Merged
Merged at revision: 90
Proposed branch: lp:~free.ekanayaka/charms/trusty/haproxy/advertise-public-ip
Merge into: lp:charms/trusty/haproxy
Diff against target: 194 lines (+97/-4)
4 files modified
README.md (+7/-0)
hooks/hooks.py (+33/-4)
hooks/tests/test_config_changed_hooks.py (+14/-0)
hooks/tests/test_reverseproxy_hooks.py (+43/-0)
To merge this branch: bzr merge lp:~free.ekanayaka/charms/trusty/haproxy/advertise-public-ip
Reviewer Review Type Date Requested Status
Chris Glass (community) Approve
Review via email: mp+255792@code.launchpad.net

Description of the change

Add a reverseproxy-relation-joined hook that will expose the unit's public IP and public SSL certificate, in case related services need it.

To post a comment you must log in.
91. By Free Ekanayaka

Fix typo

92. By Free Ekanayaka

Add hook link

93. By Free Ekanayaka

encode self-signed cert

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

Thanks for the proposed change, the functionality looks good and is useful, however I would like to see the exposed certificate updated in case it changes after initial deployment before I can +1 this branch.

Furthermore:

[0] Please add some human documentation for this change in the README file.

Thanks!

review: Needs Fixing
Revision history for this message
Chris Glass (tribaal) :
Revision history for this message
Free Ekanayaka (free.ekanayaka) :
94. By Free Ekanayaka

Address review comments

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

> Thanks for the proposed change, the functionality looks good and is useful,
> however I would like to see the exposed certificate updated in case it changes
> after initial deployment before I can +1 this branch.
>
> Furthermore:
>
> [0] Please add some human documentation for this change in the README file.
>
> Thanks!

Should be all fixed.

95. By Free Ekanayaka

Add comment

96. By Free Ekanayaka

Add comment

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

Looks good now, thanks for addressing these changes!

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.md'
2--- README.md 2015-03-05 11:18:33 +0000
3+++ README.md 2015-04-14 16:46:20 +0000
4@@ -97,6 +97,13 @@
5 [... optionally more servers here ...]]}}
6
7
8+In all cases if your service needs to know the public IP(s) of the haproxy unit(s)
9+it relates to, or the value of the default SSL certificate set on or generated by
10+the haproxy service, you can look for the 'public-address' and 'ssl_cert' keys
11+on your relation, which are set by the haproxy service as soon as it joins the
12+reverseproxy relation.
13+
14+
15 ## Website Relation
16
17
18
19=== modified file 'hooks/hooks.py'
20--- hooks/hooks.py 2015-03-17 10:45:09 +0000
21+++ hooks/hooks.py 2015-04-14 16:46:20 +0000
22@@ -285,7 +285,7 @@
23 return
24 if ssl_cert == "SELFSIGNED":
25 log("Using self-signed certificate")
26- content = get_selfsigned_cert()
27+ content = "".join(get_selfsigned_cert())
28 else:
29 ssl_key = config_data.get("ssl_key")
30 if not ssl_key:
31@@ -900,6 +900,10 @@
32 else:
33 haproxy_monitoring = None
34 remove_services()
35+ if config_data.changed("ssl_cert"):
36+ # TODO: handle also the case where it's the public-address value
37+ # that changes (see also #1444062)
38+ _notify_reverseproxy()
39 if not create_services():
40 sys.exit()
41 haproxy_services = load_services()
42@@ -942,10 +946,30 @@
43 def reverseproxy_interface(hook_name=None):
44 if hook_name is None:
45 return None
46+ if hook_name == "joined":
47+ # When we join a new reverseproxy relation we communicate to the
48+ # remote unit our public IP and public SSL certificate, since
49+ # some applications might need it in order to tell third parties
50+ # how to interact with them.
51+ _notify_reverseproxy(relation_ids=(relation_id(),))
52+ return
53 if hook_name in ("changed", "departed"):
54 config_changed()
55
56
57+def _notify_reverseproxy(relation_ids=None):
58+ config_data = config_get()
59+ ssl_cert = config_data.get("ssl_cert")
60+ if ssl_cert == "SELFSIGNED":
61+ ssl_cert = base64.b64encode(get_selfsigned_cert()[0])
62+ relation_settings = {
63+ "public-address": unit_get("public-address"),
64+ "ssl_cert": ssl_cert,
65+ }
66+ for rid in relation_ids or get_relation_ids("reverseproxy"):
67+ relation_set(relation_id=rid, relation_settings=relation_settings)
68+
69+
70 def website_interface(hook_name=None):
71 if hook_name is None:
72 return None
73@@ -1116,17 +1140,20 @@
74
75 If no self-signed certificate is there or the existing one doesn't match
76 our unit data, a new one will be created.
77+
78+ @return: A 2-tuple whose first item holds the content of the public
79+ certificate and the second item the content of the private key.
80 """
81 cert_file = os.path.join(default_haproxy_lib_dir, "selfsigned_ca.crt")
82 key_file = os.path.join(default_haproxy_lib_dir, "selfsigned.key")
83 if is_selfsigned_cert_stale(cert_file, key_file):
84 log("Generating self-signed certificate")
85 gen_selfsigned_cert(cert_file, key_file)
86- content = ""
87+ result = ()
88 for content_file in [cert_file, key_file]:
89 with open(content_file, "r") as fd:
90- content += fd.read()
91- return content
92+ result += (fd.read(),)
93+ return result
94
95
96 # XXX taken from the apache2 charm.
97@@ -1246,6 +1273,8 @@
98 reverseproxy_interface("changed")
99 elif hook_name == "reverseproxy-relation-departed":
100 reverseproxy_interface("departed")
101+ elif hook_name == "reverseproxy-relation-joined":
102+ reverseproxy_interface("joined")
103 elif hook_name == "website-relation-joined":
104 website_interface("joined")
105 elif hook_name == "website-relation-changed":
106
107=== added symlink 'hooks/reverseproxy-relation-joined'
108=== target is u'hooks.py'
109=== modified file 'hooks/tests/test_config_changed_hooks.py'
110--- hooks/tests/test_config_changed_hooks.py 2015-02-19 17:05:11 +0000
111+++ hooks/tests/test_config_changed_hooks.py 2015-04-14 16:46:20 +0000
112@@ -14,6 +14,7 @@
113 def setUp(self):
114 super(ConfigChangedTest, self).setUp()
115 self.config_get = self.patch_hook("config_get")
116+ self.config_get().changed.return_value = False
117 self.get_service_ports = self.patch_hook("get_service_ports")
118 self.get_listen_stanzas = self.patch_hook("get_listen_stanzas")
119 self.create_haproxy_globals = self.patch_hook(
120@@ -83,6 +84,19 @@
121 "HAProxy configuration check failed, exiting.")
122 self.sys_exit.assert_called_once_with(1)
123
124+ def test_config_changed_notify_reverseproxy(self):
125+ """
126+ If the ssl_cert config value changes, the reverseproxy relations get
127+ updated.
128+ """
129+ config_data = self.config_get()
130+ config_data.changed.return_value = True
131+ _notify_reverseproxy = self.patch_hook("_notify_reverseproxy")
132+
133+ hooks.config_changed()
134+ config_data.changed.assert_called_once_with("ssl_cert")
135+ _notify_reverseproxy.assert_called_once()
136+
137
138 class HelpersTest(TestCase):
139 def test_constructs_haproxy_config(self):
140
141=== modified file 'hooks/tests/test_reverseproxy_hooks.py'
142--- hooks/tests/test_reverseproxy_hooks.py 2015-03-02 12:47:08 +0000
143+++ hooks/tests/test_reverseproxy_hooks.py 2015-04-14 16:46:20 +0000
144@@ -1,3 +1,4 @@
145+import base64
146 import yaml
147
148 from testtools import TestCase
149@@ -487,3 +488,45 @@
150
151 expected = {'service_name': 'left', 'foo': 'bar', 'bar': 'baz'}
152 self.assertEqual(expected, hooks.merge_service(s1, s2))
153+
154+ def test_join_reverseproxy_relation(self):
155+ """
156+ When haproxy joins a reverseproxy relation it advertises its public
157+ IP and public certificate by setting values on the relation.
158+ """
159+ ssl_cert = base64.b64encode("<cert data>")
160+ self.config_get.return_value = {"ssl_cert": ssl_cert}
161+ unit_get = self.patch_hook("unit_get")
162+ unit_get.return_value = "1.2.3.4"
163+ relation_id = self.patch_hook("relation_id")
164+ relation_id.return_value = "reverseproxy:1"
165+ relation_set = self.patch_hook("relation_set")
166+ hooks.reverseproxy_interface(hook_name="joined")
167+ unit_get.assert_called_once_with("public-address")
168+ relation_set.assert_called_once_with(
169+ relation_id="reverseproxy:1",
170+ relation_settings={
171+ "public-address": "1.2.3.4",
172+ "ssl_cert": ssl_cert})
173+
174+ def test_join_reverseproxy_relation_with_selfsigned_cert(self):
175+ """
176+ When haproxy joins a reverseproxy relation and a self-signed
177+ certificate is configured, then it's included in the relation.
178+ """
179+ self.config_get.return_value = {"ssl_cert": "SELFSIGNED"}
180+ unit_get = self.patch_hook("unit_get")
181+ unit_get.return_value = "1.2.3.4"
182+ relation_id = self.patch_hook("relation_id")
183+ relation_id.return_value = "reverseproxy:1"
184+ get_selfsigned_cert = self.patch_hook("get_selfsigned_cert")
185+ get_selfsigned_cert.return_value = ("<self-signed>", None)
186+ relation_set = self.patch_hook("relation_set")
187+ hooks.reverseproxy_interface(hook_name="joined")
188+ unit_get.assert_called_once_with("public-address")
189+ ssl_cert = base64.b64encode("<self-signed>")
190+ relation_set.assert_called_once_with(
191+ relation_id="reverseproxy:1",
192+ relation_settings={
193+ "public-address": "1.2.3.4",
194+ "ssl_cert": ssl_cert})

Subscribers

People subscribed via source and target branches

to all changes: