3
from textwrap import dedent
4
from mocker import MockerTestCase, ARGS
6
class JujuHookTest(MockerTestCase):
9
self.config_services = [{
10
"service_name": "haproxy_test",
11
"service_host": "0.0.0.0",
13
"service_options": ["balance leastconn"],
14
"server_options": "maxconn 25"}]
15
self.config_services_extended = [
16
{"service_name": "unit_service",
17
"service_host": "supplied-hostname",
18
"service_port": "999",
19
"service_options": ["balance leastconn"],
20
"server_options": "maxconn 99"}]
21
self.relation_services = [
22
{"service_name": "foo_svc",
23
"service_options": ["balance leastconn"],
24
"servers": [("A", "hA", "1", "oA1 oA2")]},
25
{"service_name": "bar_svc",
26
"service_options": ["balance leastconn"],
28
("A", "hA", "1", "oA1 oA2"), ("B", "hB", "2", "oB1 oB2")]}]
29
self.relation_services2 = [
30
{"service_name": "foo_svc",
31
"service_options": ["balance leastconn"],
32
"servers": [("A2", "hA2", "12", "oA12 oA22")]}]
33
hooks.default_haproxy_config_dir = self.makeDir()
34
hooks.default_haproxy_config = self.makeFile()
35
hooks.default_haproxy_service_config_dir = self.makeDir()
36
obj = self.mocker.replace("hooks.juju_log")
38
self.mocker.count(0, None)
39
obj = self.mocker.replace("hooks.unit_get")
41
self.mocker.result("test-host.example.com")
42
self.mocker.count(0, None)
45
def _expect_config_get(self, **kwargs):
47
"default_timeouts": "queue 1000, connect 1000, client 1000, server 1000",
48
"global_log": "127.0.0.1 local0, 127.0.0.1 local1 notice",
49
"global_spread_checks": 0,
50
"monitoring_allowed_cidr": "127.0.0.1/32",
51
"monitoring_username": "haproxy",
52
"default_log": "global",
53
"global_group": "haproxy",
54
"monitoring_stats_refresh": 3,
56
"services": yaml.dump(self.config_services),
57
"global_maxconn": 4096,
58
"global_user": "haproxy",
59
"default_options": "httplog, dontlognull",
60
"monitoring_port": 10000,
61
"global_debug": False,
62
"nagios_context": "juju",
63
"global_quiet": False,
64
"enable_monitoring": False,
65
"monitoring_password": "changeme",
66
"default_mode": "http"}
67
obj = self.mocker.replace("hooks.config_get")
70
self.mocker.result(result)
71
self.mocker.count(1, None)
73
def _expect_relation_get_all(self, relation, extra={}):
74
obj = self.mocker.replace("hooks.relation_get_all")
76
relation = {"hostname": "10.0.1.2",
77
"private-address": "10.0.1.2",
79
relation.update(extra)
80
result = {"1": {"unit/0": relation}}
81
self.mocker.result(result)
82
self.mocker.count(1, None)
84
def _expect_relation_get_all_multiple(self, relation_name):
85
obj = self.mocker.replace("hooks.relation_get_all")
89
"hostname": "10.0.1.2",
90
"private-address": "10.0.1.2",
92
"services": yaml.dump(self.relation_services)}},
94
"hostname": "10.0.1.3",
95
"private-address": "10.0.1.3",
97
"services": yaml.dump(self.relation_services2)}}}
98
self.mocker.result(result)
99
self.mocker.count(1, None)
101
def _expect_relation_get_all_with_services(self, relation, extra={}):
102
extra.update({"services": yaml.dump(self.relation_services)})
103
return self._expect_relation_get_all(relation, extra)
105
def _expect_relation_get(self):
106
obj = self.mocker.replace("hooks.relation_get")
109
self.mocker.result(result)
110
self.mocker.count(1, None)
112
def test_create_services(self):
114
Simplest use case, config stanza seeded in config file, server line
115
added through simple relation. Many servers can join this, but
116
multiple services will not be presented to the outside
118
self._expect_config_get()
119
self._expect_relation_get_all("reverseproxy")
121
hooks.create_services()
122
services = hooks.load_services()
124
listen haproxy_test 0.0.0.0:88
126
server 10_0_1_2__10000 10.0.1.2:10000 maxconn 25
129
self.assertEquals(services, dedent(stanza))
131
def test_create_services_extended_with_relation(self):
133
This case covers specifying an up-front services file to ha-proxy
134
in the config. The relation then specifies a singular hostname,
135
port and server_options setting which is filled into the appropriate
136
haproxy stanza based on multiple criteria.
138
self._expect_config_get(
139
services=yaml.dump(self.config_services_extended))
140
self._expect_relation_get_all("reverseproxy")
142
hooks.create_services()
143
services = hooks.load_services()
145
listen unit_service supplied-hostname:999
147
server 10_0_1_2__10000 10.0.1.2:10000 maxconn 99
150
self.assertEquals(dedent(stanza), services)
152
def test_create_services_pure_relation(self):
154
In this case, the relation is in control of the haproxy config file.
155
Each relation chooses what server it creates in the haproxy file, it
156
relies on the haproxy service only for the hostname and front-end port.
157
Each member of the relation will put a backend server entry under in
158
the desired stanza. Each realtion can in fact supply multiple
159
entries from the same juju service unit if desired.
161
self._expect_config_get()
162
self._expect_relation_get_all_with_services("reverseproxy")
164
hooks.create_services()
165
services = hooks.load_services()
167
listen foo_svc 0.0.0.0:88
169
server A hA:1 oA1 oA2
171
self.assertIn(dedent(stanza), services)
173
listen bar_svc 0.0.0.0:89
175
server A hA:1 oA1 oA2
176
server B hB:2 oB1 oB2
178
self.assertIn(dedent(stanza), services)
180
def test_create_services_pure_relation_multiple(self):
182
This is much liek the pure_relation case, where the relation specifies
183
a "services" override. However, in this case we have multiple relations
184
that partially override each other. We expect that the created haproxy
185
conf file will combine things appropriately.
187
self._expect_config_get()
188
self._expect_relation_get_all_multiple("reverseproxy")
190
hooks.create_services()
191
result = hooks.load_services()
193
listen foo_svc 0.0.0.0:88
195
server A hA:1 oA1 oA2
196
server A2 hA2:12 oA12 oA22
198
self.assertIn(dedent(stanza), result)
200
listen bar_svc 0.0.0.0:89
202
server A hA:1 oA1 oA2
203
server B hB:2 oB1 oB2
205
self.assertIn(dedent(stanza), result)
207
def test_get_config_services_config_only(self):
209
Attempting to catch the case where a relation is not joined yet
211
self._expect_config_get()
212
obj = self.mocker.replace("hooks.relation_get_all")
214
self.mocker.result(None)
216
result = hooks.get_config_services()
217
self.assertEquals(result, self.config_services)
219
def test_get_config_services_relation_no_services(self):
221
If the config specifies services and the realtion does not, just the
222
config services should come through.
224
self._expect_config_get()
225
self._expect_relation_get_all("reverseproxy")
227
result = hooks.get_config_services()
228
self.assertEquals(result, self.config_services)
230
def test_get_config_services_relation_with_services(self):
232
Testing with both the config and relation providing services should
233
yield the just the relation
235
self._expect_config_get()
236
self._expect_relation_get_all_with_services("reverseproxy")
238
result = hooks.get_config_services()
239
# Just test "servers" since hostname and port and maybe other keys
240
# will be added by the hook
241
self.assertEquals(result[0]["servers"],
242
self.relation_services[0]["servers"])
244
def test_config_generation_indempotent(self):
245
self._expect_config_get()
246
self._expect_relation_get_all_multiple("reverseproxy")
249
# Test that we generate the same haproxy.conf file each time
250
hooks.create_services()
251
result1 = hooks.load_services()
252
hooks.create_services()
253
result2 = hooks.load_services()
254
self.assertEqual(result1, result2)
256
def test_get_all_services(self):
257
self._expect_config_get()
258
self._expect_relation_get_all_multiple("reverseproxy")
260
baseline = [{"service_name": "foo_svc", "service_port": 88},
261
{"service_name": "bar_svc", "service_port": 89}]
262
services = hooks.get_all_services()
263
self.assertEqual(baseline, services)