Merge lp:~free.ekanayaka/charms/trusty/haproxy/advertise-public-ip into lp:charms/trusty/haproxy
- Trusty Tahr (14.04)
- advertise-public-ip
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chris Glass (community) | Approve | ||
Review via email: mp+255792@code.launchpad.net |
Commit message
Description of the change
Add a reverseproxy-
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) : | # |
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 | 97 | [... optionally more servers here ...]]}} | 97 | [... optionally more servers here ...]]}} |
6 | 98 | 98 | ||
7 | 99 | 99 | ||
8 | 100 | In all cases if your service needs to know the public IP(s) of the haproxy unit(s) | ||
9 | 101 | it relates to, or the value of the default SSL certificate set on or generated by | ||
10 | 102 | the haproxy service, you can look for the 'public-address' and 'ssl_cert' keys | ||
11 | 103 | on your relation, which are set by the haproxy service as soon as it joins the | ||
12 | 104 | reverseproxy relation. | ||
13 | 105 | |||
14 | 106 | |||
15 | 100 | ## Website Relation | 107 | ## Website Relation |
16 | 101 | 108 | ||
17 | 102 | 109 | ||
18 | 103 | 110 | ||
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 | 285 | return | 285 | return |
24 | 286 | if ssl_cert == "SELFSIGNED": | 286 | if ssl_cert == "SELFSIGNED": |
25 | 287 | log("Using self-signed certificate") | 287 | log("Using self-signed certificate") |
27 | 288 | content = get_selfsigned_cert() | 288 | content = "".join(get_selfsigned_cert()) |
28 | 289 | else: | 289 | else: |
29 | 290 | ssl_key = config_data.get("ssl_key") | 290 | ssl_key = config_data.get("ssl_key") |
30 | 291 | if not ssl_key: | 291 | if not ssl_key: |
31 | @@ -900,6 +900,10 @@ | |||
32 | 900 | else: | 900 | else: |
33 | 901 | haproxy_monitoring = None | 901 | haproxy_monitoring = None |
34 | 902 | remove_services() | 902 | remove_services() |
35 | 903 | if config_data.changed("ssl_cert"): | ||
36 | 904 | # TODO: handle also the case where it's the public-address value | ||
37 | 905 | # that changes (see also #1444062) | ||
38 | 906 | _notify_reverseproxy() | ||
39 | 903 | if not create_services(): | 907 | if not create_services(): |
40 | 904 | sys.exit() | 908 | sys.exit() |
41 | 905 | haproxy_services = load_services() | 909 | haproxy_services = load_services() |
42 | @@ -942,10 +946,30 @@ | |||
43 | 942 | def reverseproxy_interface(hook_name=None): | 946 | def reverseproxy_interface(hook_name=None): |
44 | 943 | if hook_name is None: | 947 | if hook_name is None: |
45 | 944 | return None | 948 | return None |
46 | 949 | if hook_name == "joined": | ||
47 | 950 | # When we join a new reverseproxy relation we communicate to the | ||
48 | 951 | # remote unit our public IP and public SSL certificate, since | ||
49 | 952 | # some applications might need it in order to tell third parties | ||
50 | 953 | # how to interact with them. | ||
51 | 954 | _notify_reverseproxy(relation_ids=(relation_id(),)) | ||
52 | 955 | return | ||
53 | 945 | if hook_name in ("changed", "departed"): | 956 | if hook_name in ("changed", "departed"): |
54 | 946 | config_changed() | 957 | config_changed() |
55 | 947 | 958 | ||
56 | 948 | 959 | ||
57 | 960 | def _notify_reverseproxy(relation_ids=None): | ||
58 | 961 | config_data = config_get() | ||
59 | 962 | ssl_cert = config_data.get("ssl_cert") | ||
60 | 963 | if ssl_cert == "SELFSIGNED": | ||
61 | 964 | ssl_cert = base64.b64encode(get_selfsigned_cert()[0]) | ||
62 | 965 | relation_settings = { | ||
63 | 966 | "public-address": unit_get("public-address"), | ||
64 | 967 | "ssl_cert": ssl_cert, | ||
65 | 968 | } | ||
66 | 969 | for rid in relation_ids or get_relation_ids("reverseproxy"): | ||
67 | 970 | relation_set(relation_id=rid, relation_settings=relation_settings) | ||
68 | 971 | |||
69 | 972 | |||
70 | 949 | def website_interface(hook_name=None): | 973 | def website_interface(hook_name=None): |
71 | 950 | if hook_name is None: | 974 | if hook_name is None: |
72 | 951 | return None | 975 | return None |
73 | @@ -1116,17 +1140,20 @@ | |||
74 | 1116 | 1140 | ||
75 | 1117 | If no self-signed certificate is there or the existing one doesn't match | 1141 | If no self-signed certificate is there or the existing one doesn't match |
76 | 1118 | our unit data, a new one will be created. | 1142 | our unit data, a new one will be created. |
77 | 1143 | |||
78 | 1144 | @return: A 2-tuple whose first item holds the content of the public | ||
79 | 1145 | certificate and the second item the content of the private key. | ||
80 | 1119 | """ | 1146 | """ |
81 | 1120 | cert_file = os.path.join(default_haproxy_lib_dir, "selfsigned_ca.crt") | 1147 | cert_file = os.path.join(default_haproxy_lib_dir, "selfsigned_ca.crt") |
82 | 1121 | key_file = os.path.join(default_haproxy_lib_dir, "selfsigned.key") | 1148 | key_file = os.path.join(default_haproxy_lib_dir, "selfsigned.key") |
83 | 1122 | if is_selfsigned_cert_stale(cert_file, key_file): | 1149 | if is_selfsigned_cert_stale(cert_file, key_file): |
84 | 1123 | log("Generating self-signed certificate") | 1150 | log("Generating self-signed certificate") |
85 | 1124 | gen_selfsigned_cert(cert_file, key_file) | 1151 | gen_selfsigned_cert(cert_file, key_file) |
87 | 1125 | content = "" | 1152 | result = () |
88 | 1126 | for content_file in [cert_file, key_file]: | 1153 | for content_file in [cert_file, key_file]: |
89 | 1127 | with open(content_file, "r") as fd: | 1154 | with open(content_file, "r") as fd: |
92 | 1128 | content += fd.read() | 1155 | result += (fd.read(),) |
93 | 1129 | return content | 1156 | return result |
94 | 1130 | 1157 | ||
95 | 1131 | 1158 | ||
96 | 1132 | # XXX taken from the apache2 charm. | 1159 | # XXX taken from the apache2 charm. |
97 | @@ -1246,6 +1273,8 @@ | |||
98 | 1246 | reverseproxy_interface("changed") | 1273 | reverseproxy_interface("changed") |
99 | 1247 | elif hook_name == "reverseproxy-relation-departed": | 1274 | elif hook_name == "reverseproxy-relation-departed": |
100 | 1248 | reverseproxy_interface("departed") | 1275 | reverseproxy_interface("departed") |
101 | 1276 | elif hook_name == "reverseproxy-relation-joined": | ||
102 | 1277 | reverseproxy_interface("joined") | ||
103 | 1249 | elif hook_name == "website-relation-joined": | 1278 | elif hook_name == "website-relation-joined": |
104 | 1250 | website_interface("joined") | 1279 | website_interface("joined") |
105 | 1251 | elif hook_name == "website-relation-changed": | 1280 | elif hook_name == "website-relation-changed": |
106 | 1252 | 1281 | ||
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 | 14 | def setUp(self): | 14 | def setUp(self): |
114 | 15 | super(ConfigChangedTest, self).setUp() | 15 | super(ConfigChangedTest, self).setUp() |
115 | 16 | self.config_get = self.patch_hook("config_get") | 16 | self.config_get = self.patch_hook("config_get") |
116 | 17 | self.config_get().changed.return_value = False | ||
117 | 17 | self.get_service_ports = self.patch_hook("get_service_ports") | 18 | self.get_service_ports = self.patch_hook("get_service_ports") |
118 | 18 | self.get_listen_stanzas = self.patch_hook("get_listen_stanzas") | 19 | self.get_listen_stanzas = self.patch_hook("get_listen_stanzas") |
119 | 19 | self.create_haproxy_globals = self.patch_hook( | 20 | self.create_haproxy_globals = self.patch_hook( |
120 | @@ -83,6 +84,19 @@ | |||
121 | 83 | "HAProxy configuration check failed, exiting.") | 84 | "HAProxy configuration check failed, exiting.") |
122 | 84 | self.sys_exit.assert_called_once_with(1) | 85 | self.sys_exit.assert_called_once_with(1) |
123 | 85 | 86 | ||
124 | 87 | def test_config_changed_notify_reverseproxy(self): | ||
125 | 88 | """ | ||
126 | 89 | If the ssl_cert config value changes, the reverseproxy relations get | ||
127 | 90 | updated. | ||
128 | 91 | """ | ||
129 | 92 | config_data = self.config_get() | ||
130 | 93 | config_data.changed.return_value = True | ||
131 | 94 | _notify_reverseproxy = self.patch_hook("_notify_reverseproxy") | ||
132 | 95 | |||
133 | 96 | hooks.config_changed() | ||
134 | 97 | config_data.changed.assert_called_once_with("ssl_cert") | ||
135 | 98 | _notify_reverseproxy.assert_called_once() | ||
136 | 99 | |||
137 | 86 | 100 | ||
138 | 87 | class HelpersTest(TestCase): | 101 | class HelpersTest(TestCase): |
139 | 88 | def test_constructs_haproxy_config(self): | 102 | def test_constructs_haproxy_config(self): |
140 | 89 | 103 | ||
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 | 1 | import base64 | ||
146 | 1 | import yaml | 2 | import yaml |
147 | 2 | 3 | ||
148 | 3 | from testtools import TestCase | 4 | from testtools import TestCase |
149 | @@ -487,3 +488,45 @@ | |||
150 | 487 | 488 | ||
151 | 488 | expected = {'service_name': 'left', 'foo': 'bar', 'bar': 'baz'} | 489 | expected = {'service_name': 'left', 'foo': 'bar', 'bar': 'baz'} |
152 | 489 | self.assertEqual(expected, hooks.merge_service(s1, s2)) | 490 | self.assertEqual(expected, hooks.merge_service(s1, s2)) |
153 | 491 | |||
154 | 492 | def test_join_reverseproxy_relation(self): | ||
155 | 493 | """ | ||
156 | 494 | When haproxy joins a reverseproxy relation it advertises its public | ||
157 | 495 | IP and public certificate by setting values on the relation. | ||
158 | 496 | """ | ||
159 | 497 | ssl_cert = base64.b64encode("<cert data>") | ||
160 | 498 | self.config_get.return_value = {"ssl_cert": ssl_cert} | ||
161 | 499 | unit_get = self.patch_hook("unit_get") | ||
162 | 500 | unit_get.return_value = "1.2.3.4" | ||
163 | 501 | relation_id = self.patch_hook("relation_id") | ||
164 | 502 | relation_id.return_value = "reverseproxy:1" | ||
165 | 503 | relation_set = self.patch_hook("relation_set") | ||
166 | 504 | hooks.reverseproxy_interface(hook_name="joined") | ||
167 | 505 | unit_get.assert_called_once_with("public-address") | ||
168 | 506 | relation_set.assert_called_once_with( | ||
169 | 507 | relation_id="reverseproxy:1", | ||
170 | 508 | relation_settings={ | ||
171 | 509 | "public-address": "1.2.3.4", | ||
172 | 510 | "ssl_cert": ssl_cert}) | ||
173 | 511 | |||
174 | 512 | def test_join_reverseproxy_relation_with_selfsigned_cert(self): | ||
175 | 513 | """ | ||
176 | 514 | When haproxy joins a reverseproxy relation and a self-signed | ||
177 | 515 | certificate is configured, then it's included in the relation. | ||
178 | 516 | """ | ||
179 | 517 | self.config_get.return_value = {"ssl_cert": "SELFSIGNED"} | ||
180 | 518 | unit_get = self.patch_hook("unit_get") | ||
181 | 519 | unit_get.return_value = "1.2.3.4" | ||
182 | 520 | relation_id = self.patch_hook("relation_id") | ||
183 | 521 | relation_id.return_value = "reverseproxy:1" | ||
184 | 522 | get_selfsigned_cert = self.patch_hook("get_selfsigned_cert") | ||
185 | 523 | get_selfsigned_cert.return_value = ("<self-signed>", None) | ||
186 | 524 | relation_set = self.patch_hook("relation_set") | ||
187 | 525 | hooks.reverseproxy_interface(hook_name="joined") | ||
188 | 526 | unit_get.assert_called_once_with("public-address") | ||
189 | 527 | ssl_cert = base64.b64encode("<self-signed>") | ||
190 | 528 | relation_set.assert_called_once_with( | ||
191 | 529 | relation_id="reverseproxy:1", | ||
192 | 530 | relation_settings={ | ||
193 | 531 | "public-address": "1.2.3.4", | ||
194 | 532 | "ssl_cert": ssl_cert}) |
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!