~aglenyoung/charms/precise/haproxy/haproxy-stats-socket

« back to all changes in this revision

Viewing changes to hooks/test_hooks.py

  • Committer: Marco Ceppi
  • Date: 2013-10-17 03:33:47 UTC
  • mfrom: (60.1.27 haproxy)
  • Revision ID: marco@ceppi.net-20131017033347-7ha6tdiivmpardtx
[sidnei] The 'all_services' config now supports a static list of servers to be used *in addition* to the ones provided via relation.
[sidnei] When more than one haproxy units exist, the configured service is upgraded in-place to a mode where traffic is routed to a single haproxy unit (the first one in unit-name order) and the remaining ones are configured as 'backup'. This is done to allow the enforcement of a 'maxconn' session in the configured services, which would not be possible to enforce otherwise.
[sidnei] Changes to the configured services are properly propagated to the upstream relation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
import hooks
2
 
import yaml
3
 
from textwrap import dedent
4
 
from mocker import MockerTestCase, ARGS
5
 
 
6
 
class JujuHookTest(MockerTestCase):
7
 
 
8
 
    def setUp(self):
9
 
        self.config_services = [{
10
 
            "service_name": "haproxy_test",
11
 
            "service_host": "0.0.0.0",
12
 
            "service_port": "88",
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"],
27
 
            "servers": [
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")
37
 
        obj(ARGS)
38
 
        self.mocker.count(0, None)
39
 
        obj = self.mocker.replace("hooks.unit_get")
40
 
        obj("public-address")
41
 
        self.mocker.result("test-host.example.com")
42
 
        self.mocker.count(0, None)
43
 
        self.maxDiff = None
44
 
    
45
 
    def _expect_config_get(self, **kwargs):
46
 
        result = {
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,
55
 
            "default_retries": 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")
68
 
        obj()
69
 
        result.update(kwargs)
70
 
        self.mocker.result(result)
71
 
        self.mocker.count(1, None)
72
 
 
73
 
    def _expect_relation_get_all(self, relation, extra={}):
74
 
        obj = self.mocker.replace("hooks.relation_get_all")
75
 
        obj(relation)
76
 
        relation = {"hostname": "10.0.1.2",
77
 
                    "private-address": "10.0.1.2",
78
 
                    "port": "10000"}
79
 
        relation.update(extra)
80
 
        result = {"1": {"unit/0": relation}}
81
 
        self.mocker.result(result)
82
 
        self.mocker.count(1, None)
83
 
 
84
 
    def _expect_relation_get_all_multiple(self, relation_name):
85
 
        obj = self.mocker.replace("hooks.relation_get_all")
86
 
        obj(relation_name)
87
 
        result = {
88
 
                "1": {"unit/0": {
89
 
                    "hostname": "10.0.1.2",
90
 
                    "private-address": "10.0.1.2",
91
 
                    "port": "10000",
92
 
                    "services": yaml.dump(self.relation_services)}},
93
 
                "2": {"unit/1": {
94
 
                    "hostname": "10.0.1.3",
95
 
                    "private-address": "10.0.1.3",
96
 
                    "port": "10001",
97
 
                    "services": yaml.dump(self.relation_services2)}}}
98
 
        self.mocker.result(result)
99
 
        self.mocker.count(1, None)
100
 
 
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)
104
 
 
105
 
    def _expect_relation_get(self):
106
 
        obj = self.mocker.replace("hooks.relation_get")
107
 
        obj()
108
 
        result = {}
109
 
        self.mocker.result(result)
110
 
        self.mocker.count(1, None)
111
 
 
112
 
    def test_create_services(self):
113
 
        """
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
117
 
        """
118
 
        self._expect_config_get()
119
 
        self._expect_relation_get_all("reverseproxy")
120
 
        self.mocker.replay()
121
 
        hooks.create_services()
122
 
        services = hooks.load_services()
123
 
        stanza = """\
124
 
            listen haproxy_test 0.0.0.0:88
125
 
                balance leastconn
126
 
                server 10_0_1_2__10000 10.0.1.2:10000 maxconn 25
127
 
 
128
 
        """
129
 
        self.assertEquals(services, dedent(stanza))
130
 
 
131
 
    def test_create_services_extended_with_relation(self):
132
 
        """
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.
137
 
        """
138
 
        self._expect_config_get(
139
 
                services=yaml.dump(self.config_services_extended))
140
 
        self._expect_relation_get_all("reverseproxy")
141
 
        self.mocker.replay()
142
 
        hooks.create_services()
143
 
        services = hooks.load_services()
144
 
        stanza = """\
145
 
            listen unit_service supplied-hostname:999
146
 
                balance leastconn
147
 
                server 10_0_1_2__10000 10.0.1.2:10000 maxconn 99
148
 
 
149
 
        """
150
 
        self.assertEquals(dedent(stanza), services)
151
 
 
152
 
    def test_create_services_pure_relation(self):
153
 
        """
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.
160
 
        """
161
 
        self._expect_config_get()
162
 
        self._expect_relation_get_all_with_services("reverseproxy")
163
 
        self.mocker.replay()
164
 
        hooks.create_services()
165
 
        services = hooks.load_services()
166
 
        stanza = """\
167
 
            listen foo_svc 0.0.0.0:88
168
 
                balance leastconn
169
 
                server A hA:1 oA1 oA2
170
 
        """
171
 
        self.assertIn(dedent(stanza), services)
172
 
        stanza = """\
173
 
            listen bar_svc 0.0.0.0:89
174
 
                balance leastconn
175
 
                server A hA:1 oA1 oA2
176
 
                server B hB:2 oB1 oB2
177
 
        """
178
 
        self.assertIn(dedent(stanza), services)
179
 
 
180
 
    def test_create_services_pure_relation_multiple(self):
181
 
        """
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.
186
 
        """
187
 
        self._expect_config_get()
188
 
        self._expect_relation_get_all_multiple("reverseproxy")
189
 
        self.mocker.replay()
190
 
        hooks.create_services()
191
 
        result = hooks.load_services()
192
 
        stanza = """\
193
 
            listen foo_svc 0.0.0.0:88
194
 
                balance leastconn
195
 
                server A hA:1 oA1 oA2
196
 
                server A2 hA2:12 oA12 oA22
197
 
        """
198
 
        self.assertIn(dedent(stanza), result)
199
 
        stanza = """\
200
 
            listen bar_svc 0.0.0.0:89
201
 
                balance leastconn
202
 
                server A hA:1 oA1 oA2
203
 
                server B hB:2 oB1 oB2
204
 
        """
205
 
        self.assertIn(dedent(stanza), result)
206
 
 
207
 
    def test_get_config_services_config_only(self):
208
 
        """
209
 
        Attempting to catch the case where a relation is not joined yet
210
 
        """
211
 
        self._expect_config_get()
212
 
        obj = self.mocker.replace("hooks.relation_get_all")
213
 
        obj("reverseproxy")
214
 
        self.mocker.result(None)
215
 
        self.mocker.replay()
216
 
        result = hooks.get_config_services()
217
 
        self.assertEquals(result, self.config_services)
218
 
 
219
 
    def test_get_config_services_relation_no_services(self):
220
 
        """
221
 
        If the config specifies services and the realtion does not, just the
222
 
        config services should come through.
223
 
        """
224
 
        self._expect_config_get()
225
 
        self._expect_relation_get_all("reverseproxy")
226
 
        self.mocker.replay()
227
 
        result = hooks.get_config_services()
228
 
        self.assertEquals(result, self.config_services)
229
 
 
230
 
    def test_get_config_services_relation_with_services(self):
231
 
        """
232
 
        Testing with both the config and relation providing services should
233
 
        yield the just the relation
234
 
        """
235
 
        self._expect_config_get()
236
 
        self._expect_relation_get_all_with_services("reverseproxy")
237
 
        self.mocker.replay()
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"])
243
 
 
244
 
    def test_config_generation_indempotent(self):
245
 
        self._expect_config_get()
246
 
        self._expect_relation_get_all_multiple("reverseproxy")
247
 
        self.mocker.replay()
248
 
 
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)
255
 
 
256
 
    def test_get_all_services(self):
257
 
        self._expect_config_get()
258
 
        self._expect_relation_get_all_multiple("reverseproxy")
259
 
        self.mocker.replay()
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)