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
=== modified file 'README.md'
--- README.md 2015-03-05 11:18:33 +0000
+++ README.md 2015-04-14 16:46:20 +0000
@@ -97,6 +97,13 @@
97 [... optionally more servers here ...]]}}97 [... optionally more servers here ...]]}}
9898
9999
100In all cases if your service needs to know the public IP(s) of the haproxy unit(s)
101it relates to, or the value of the default SSL certificate set on or generated by
102the haproxy service, you can look for the 'public-address' and 'ssl_cert' keys
103on your relation, which are set by the haproxy service as soon as it joins the
104reverseproxy relation.
105
106
100## Website Relation107## Website Relation
101108
102109
103110
=== modified file 'hooks/hooks.py'
--- hooks/hooks.py 2015-03-17 10:45:09 +0000
+++ hooks/hooks.py 2015-04-14 16:46:20 +0000
@@ -285,7 +285,7 @@
285 return285 return
286 if ssl_cert == "SELFSIGNED":286 if ssl_cert == "SELFSIGNED":
287 log("Using self-signed certificate")287 log("Using self-signed certificate")
288 content = get_selfsigned_cert()288 content = "".join(get_selfsigned_cert())
289 else:289 else:
290 ssl_key = config_data.get("ssl_key")290 ssl_key = config_data.get("ssl_key")
291 if not ssl_key:291 if not ssl_key:
@@ -900,6 +900,10 @@
900 else:900 else:
901 haproxy_monitoring = None901 haproxy_monitoring = None
902 remove_services()902 remove_services()
903 if config_data.changed("ssl_cert"):
904 # TODO: handle also the case where it's the public-address value
905 # that changes (see also #1444062)
906 _notify_reverseproxy()
903 if not create_services():907 if not create_services():
904 sys.exit()908 sys.exit()
905 haproxy_services = load_services()909 haproxy_services = load_services()
@@ -942,10 +946,30 @@
942def reverseproxy_interface(hook_name=None):946def reverseproxy_interface(hook_name=None):
943 if hook_name is None:947 if hook_name is None:
944 return None948 return None
949 if hook_name == "joined":
950 # When we join a new reverseproxy relation we communicate to the
951 # remote unit our public IP and public SSL certificate, since
952 # some applications might need it in order to tell third parties
953 # how to interact with them.
954 _notify_reverseproxy(relation_ids=(relation_id(),))
955 return
945 if hook_name in ("changed", "departed"):956 if hook_name in ("changed", "departed"):
946 config_changed()957 config_changed()
947958
948959
960def _notify_reverseproxy(relation_ids=None):
961 config_data = config_get()
962 ssl_cert = config_data.get("ssl_cert")
963 if ssl_cert == "SELFSIGNED":
964 ssl_cert = base64.b64encode(get_selfsigned_cert()[0])
965 relation_settings = {
966 "public-address": unit_get("public-address"),
967 "ssl_cert": ssl_cert,
968 }
969 for rid in relation_ids or get_relation_ids("reverseproxy"):
970 relation_set(relation_id=rid, relation_settings=relation_settings)
971
972
949def website_interface(hook_name=None):973def website_interface(hook_name=None):
950 if hook_name is None:974 if hook_name is None:
951 return None975 return None
@@ -1116,17 +1140,20 @@
11161140
1117 If no self-signed certificate is there or the existing one doesn't match1141 If no self-signed certificate is there or the existing one doesn't match
1118 our unit data, a new one will be created.1142 our unit data, a new one will be created.
1143
1144 @return: A 2-tuple whose first item holds the content of the public
1145 certificate and the second item the content of the private key.
1119 """1146 """
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")
1121 key_file = os.path.join(default_haproxy_lib_dir, "selfsigned.key")1148 key_file = os.path.join(default_haproxy_lib_dir, "selfsigned.key")
1122 if is_selfsigned_cert_stale(cert_file, key_file):1149 if is_selfsigned_cert_stale(cert_file, key_file):
1123 log("Generating self-signed certificate")1150 log("Generating self-signed certificate")
1124 gen_selfsigned_cert(cert_file, key_file)1151 gen_selfsigned_cert(cert_file, key_file)
1125 content = ""1152 result = ()
1126 for content_file in [cert_file, key_file]:1153 for content_file in [cert_file, key_file]:
1127 with open(content_file, "r") as fd:1154 with open(content_file, "r") as fd:
1128 content += fd.read()1155 result += (fd.read(),)
1129 return content1156 return result
11301157
11311158
1132# XXX taken from the apache2 charm.1159# XXX taken from the apache2 charm.
@@ -1246,6 +1273,8 @@
1246 reverseproxy_interface("changed")1273 reverseproxy_interface("changed")
1247 elif hook_name == "reverseproxy-relation-departed":1274 elif hook_name == "reverseproxy-relation-departed":
1248 reverseproxy_interface("departed")1275 reverseproxy_interface("departed")
1276 elif hook_name == "reverseproxy-relation-joined":
1277 reverseproxy_interface("joined")
1249 elif hook_name == "website-relation-joined":1278 elif hook_name == "website-relation-joined":
1250 website_interface("joined")1279 website_interface("joined")
1251 elif hook_name == "website-relation-changed":1280 elif hook_name == "website-relation-changed":
12521281
=== added symlink 'hooks/reverseproxy-relation-joined'
=== target is u'hooks.py'
=== modified file 'hooks/tests/test_config_changed_hooks.py'
--- hooks/tests/test_config_changed_hooks.py 2015-02-19 17:05:11 +0000
+++ hooks/tests/test_config_changed_hooks.py 2015-04-14 16:46:20 +0000
@@ -14,6 +14,7 @@
14 def setUp(self):14 def setUp(self):
15 super(ConfigChangedTest, self).setUp()15 super(ConfigChangedTest, self).setUp()
16 self.config_get = self.patch_hook("config_get")16 self.config_get = self.patch_hook("config_get")
17 self.config_get().changed.return_value = False
17 self.get_service_ports = self.patch_hook("get_service_ports")18 self.get_service_ports = self.patch_hook("get_service_ports")
18 self.get_listen_stanzas = self.patch_hook("get_listen_stanzas")19 self.get_listen_stanzas = self.patch_hook("get_listen_stanzas")
19 self.create_haproxy_globals = self.patch_hook(20 self.create_haproxy_globals = self.patch_hook(
@@ -83,6 +84,19 @@
83 "HAProxy configuration check failed, exiting.")84 "HAProxy configuration check failed, exiting.")
84 self.sys_exit.assert_called_once_with(1)85 self.sys_exit.assert_called_once_with(1)
8586
87 def test_config_changed_notify_reverseproxy(self):
88 """
89 If the ssl_cert config value changes, the reverseproxy relations get
90 updated.
91 """
92 config_data = self.config_get()
93 config_data.changed.return_value = True
94 _notify_reverseproxy = self.patch_hook("_notify_reverseproxy")
95
96 hooks.config_changed()
97 config_data.changed.assert_called_once_with("ssl_cert")
98 _notify_reverseproxy.assert_called_once()
99
86100
87class HelpersTest(TestCase):101class HelpersTest(TestCase):
88 def test_constructs_haproxy_config(self):102 def test_constructs_haproxy_config(self):
89103
=== modified file 'hooks/tests/test_reverseproxy_hooks.py'
--- hooks/tests/test_reverseproxy_hooks.py 2015-03-02 12:47:08 +0000
+++ hooks/tests/test_reverseproxy_hooks.py 2015-04-14 16:46:20 +0000
@@ -1,3 +1,4 @@
1import base64
1import yaml2import yaml
23
3from testtools import TestCase4from testtools import TestCase
@@ -487,3 +488,45 @@
487488
488 expected = {'service_name': 'left', 'foo': 'bar', 'bar': 'baz'}489 expected = {'service_name': 'left', 'foo': 'bar', 'bar': 'baz'}
489 self.assertEqual(expected, hooks.merge_service(s1, s2))490 self.assertEqual(expected, hooks.merge_service(s1, s2))
491
492 def test_join_reverseproxy_relation(self):
493 """
494 When haproxy joins a reverseproxy relation it advertises its public
495 IP and public certificate by setting values on the relation.
496 """
497 ssl_cert = base64.b64encode("<cert data>")
498 self.config_get.return_value = {"ssl_cert": ssl_cert}
499 unit_get = self.patch_hook("unit_get")
500 unit_get.return_value = "1.2.3.4"
501 relation_id = self.patch_hook("relation_id")
502 relation_id.return_value = "reverseproxy:1"
503 relation_set = self.patch_hook("relation_set")
504 hooks.reverseproxy_interface(hook_name="joined")
505 unit_get.assert_called_once_with("public-address")
506 relation_set.assert_called_once_with(
507 relation_id="reverseproxy:1",
508 relation_settings={
509 "public-address": "1.2.3.4",
510 "ssl_cert": ssl_cert})
511
512 def test_join_reverseproxy_relation_with_selfsigned_cert(self):
513 """
514 When haproxy joins a reverseproxy relation and a self-signed
515 certificate is configured, then it's included in the relation.
516 """
517 self.config_get.return_value = {"ssl_cert": "SELFSIGNED"}
518 unit_get = self.patch_hook("unit_get")
519 unit_get.return_value = "1.2.3.4"
520 relation_id = self.patch_hook("relation_id")
521 relation_id.return_value = "reverseproxy:1"
522 get_selfsigned_cert = self.patch_hook("get_selfsigned_cert")
523 get_selfsigned_cert.return_value = ("<self-signed>", None)
524 relation_set = self.patch_hook("relation_set")
525 hooks.reverseproxy_interface(hook_name="joined")
526 unit_get.assert_called_once_with("public-address")
527 ssl_cert = base64.b64encode("<self-signed>")
528 relation_set.assert_called_once_with(
529 relation_id="reverseproxy:1",
530 relation_settings={
531 "public-address": "1.2.3.4",
532 "ssl_cert": ssl_cert})

Subscribers

People subscribed via source and target branches

to all changes: