Merge lp:~dpb/landscape-charm/vhost-config-relation into lp:~landscape/landscape-charm/trunk

Proposed by David Britton
Status: Merged
Approved by: David Britton
Approved revision: 198
Merged at revision: 181
Proposed branch: lp:~dpb/landscape-charm/vhost-config-relation
Merge into: lp:~landscape/landscape-charm/trunk
Diff against target: 523 lines (+316/-27)
7 files modified
Makefile (+14/-2)
config/landscape-deployments.yaml (+3/-2)
hooks/hooks.py (+96/-17)
hooks/lib/util.py (+32/-6)
hooks/test_hooks.py (+167/-0)
metadata.yaml (+2/-0)
tests/01-begin (+2/-0)
To merge this branch: bzr merge lp:~dpb/landscape-charm/vhost-config-relation
Reviewer Review Type Date Requested Status
Andreas Hasenack Approve
Adam Collard (community) Approve
Review via email: mp+222878@code.launchpad.net

Commit message

We can now link apache2 and landscape in a relation. This will allow the rather awkward step of "specify this random vhost base64 blob in a juju set command" to be removed from our instructions and bundle.yaml. It also gets us one step closer to a real bundle that can go into the charm store.

Also:
* ssl cert stored from apache
* get apache2 servername and use it to set the root_url of landscape
* some deve cleanups

Description of the change

In this exciting change, we can now link apache2 and landscape in a relation. This will allow the rather awkward step of "specify this random vhost base64 blob in a juju set command" to be removed from our instructions and bundle.yaml.

It also gets us one step closer to a real bundle that can go into the charm store.

Instead, landsacpe will specify it over a relation to apache2 directly. The admin is still in control of this behavior with add-relation/remove-relation, but the defaults should serve most all users well.

Major Features:

- vhost-config relation added (need latest apache2 charm to test)
- base64 blobs removed from deployer yaml.
- self-signed-SSL cert and apache "servername" auto-discovered on the same relation, cert stored in the usual place, root_url updated automatically with servername
- unit test cases
- I'm going to add an integration test case to check that the ssl cert and root url are set correctly, but maybe in a follow-on branch, I'll see.

To test:

- make lint test integration-test
- deploy and of the landscape-bundles and see if the ssl cert is passed correctly and stored on the server in the right place (landscape/0).

To post a comment you must log in.
Revision history for this message
Adam Collard (adam-collard) wrote :

SQL injection vulnerabilities, see inline comments.

review: Needs Fixing
Revision history for this message
David Britton (dpb) wrote :

Thanks Adam, please look again, I fixed all those.

Revision history for this message
David Britton (dpb) wrote :

BTW, I committed r184 and tested it, and it hit a very weird error in the locking code. Not only did it hit an error, but the error was swallowed and the charm just got stuck by exiting 0 and blocking other relations.

I need to investigate not only why the change was bad, but also why it didn't get an error back to juju (exit 1)

Revision history for this message
David Britton (dpb) wrote :

> BTW, I committed r184 and tested it, and it hit a very weird error in the
> locking code. Not only did it hit an error, but the error was swallowed and
> the charm just got stuck by exiting 0 and blocking other relations.
>
> I need to investigate not only why the change was bad, but also why it didn't
> get an error back to juju (exit 1)

So... I've reverted that for now.

Revision history for this message
David Britton (dpb) wrote :

OK, remerged, and ready for a second review. Sorry, got distracted with the documentation earlier today and when I pushed it was not ready/tested.

During testing, I addressed a couple things that I noticed:

1) I added a make target to deploy from the current branch. Simplifies testing a bit.

2) I removed a couple of try/except blocks that were trapping errors in the lock_exclusive code path. That code shouldn't fail unless it legitimately *should* fail the hook. It blocks getting the lock by design, that try/execpt block was inserted chasing another bug and was never removed. It finally bit me during testing, and I figured it was time to remove it. Also added a test to check this case.

Revision history for this message
Adam Collard (adam-collard) wrote :

Looks good! A couple of possible cleanups inline below. Successfully deployed dense MAAS target (after fighting AMT for a bit).

review: Approve
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

I deployed max, and all is good. I then destroyed the single apache2 unit (juju destroy-unit apache2/0), and added a new one, and as a new machine (not lxc). It got the host bohr.beretstack. I logged in, saw LDS as normal, but in the settings page, root-url was still set to the IP of the unit I destroyed.

Is this scenario something this branch should handle?

The certificate was regenerated, as expected, since it's a whole new unit, and landscape/0's /etc/ssl/certs/landscape_server_ca.crt has the updated one, which is excellent:

root@juju-machine-0-lxc-14:/etc/ssl/certs# openssl x509 -in landscape_server_ca.crt -noout -subject
subject= /CN=bohr.beretstack

AFAICT, the only thing that didn't get updated was the root-url in the landscape DB.

review: Needs Information
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

Hmm, wait a second. The DB has the right root-url:

landscape-standalone-main=# select encode(key, 'escape') as key, encode(value, 'escape') as value from system_configuration ;
              key | value
-------------------------------+------------------------------
 landscape.system_email_sender | u17:noreply@10.1.4.61
 landscape.root_url | u24:https://bohr.beretstack/
(2 rows)

system_email_sender is still wrong, btw.

What is odd is that the landscape UI is showing the wrong root_url in the standalone-settings page. There it is https://10.1.4.61/, and I'm accessing the UI via https://bohr.beretstack/account/standalone/standalone-settings

review: Needs Information
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

The root url in the settings page was the correct one only after I restarted the app services. This looks to be a bug in landscape code.

Revision history for this message
David Britton (dpb) wrote :

Finalized feedback. Also added a relation between landscape-app and apache2 (vhost-config). There may yet be more bugs with the max deployment, but will fix those in follow-ups if need be.

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

+1

review: Approve
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
Download full text (6.8 KiB)

The attempt to merge lp:~davidpbritton/landscape-charm/vhost-config-relation into lp:landscape-charm/trunk failed. Below is the output from the failed tests.

trial hooks
hooks.test_hooks
  TestHooksService
    test__download_file_failure ... [OK]
    test__download_file_success ... [OK]
    test__enable_service ... [OK]
    test__enable_wrong_service ... [OK]
    test__install_license_text ... [OK]
    test__install_license_url ... [OK]
    test__replace_in_file ... [OK]
    test_amqp_relation_changed_no_hostname_password ... [OK]
    test_amqp_relation_joined ... [OK]
    test_calc_daemon_count ... [OK]
    test_config_changed ... [OK]
    test_config_changed_service_count_bare ... [OK]
    test_config_changed_service_count_labeled ... [OK]
    test_config_changed_service_count_update_haproxy ... [OK]
    test_config_changed_starts_landscape ... [OK]
    test_config_changed_without_amqp_skips_start ... [OK]
    test_config_changed_without_db_skips_start ... [OK]
    test_config_changed_zero ... [OK]
    test_data_relation_changed_creates_new_log_and_repository_paths ... [OK]
    test_data_relation_changed_error_on_mountpoint_from_subordinate ... [OK]
    test_data_relation_changed_sets_mountpoint_awaits_init ... [OK]
    test_data_relation_changed_success_no_repository_data ... [OK]
    test_data_relation_changed_success_no_repository_path ... [OK]
    test_data_relation_changed_success_with_repository_data ... [OK]
    test_db_admin_relation_changed ... [ERROR]
    test_db_admin_relation_changed_failover_state_ignore ... [OK]
    test_db_admin_relation_changed_hot_standby_state_ignore ... [OK]
    test_db_admin_relation_changed_no_config_if_db_down ... [OK]
    test_db_admin_relation_changed_no_user ... [OK]
    test_db_admin_relation_changed_not_in_allowed_units ... [OK]
    test_db_admin_relation_changed_standalone_state_ignore ... [OK]
    test_get_requested_service_count ... [OK]
    test_get_requested_services ... [OK]
    test_get_services_dict ... [OK]
    test_get_services_non_proxied ... [OK]
    test_handle_empty_license ... [OK]
    test_handle_no_license ... [OK]
    test_is_db_up_db_not_configur...

Read more...

198. By David Britton

remove lint, fix broken test, add JUJU_ENV to -p in make integration-test

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2014-06-06 19:57:27 +0000
3+++ Makefile 2014-06-14 03:50:22 +0000
4@@ -16,8 +16,16 @@
5 $(EXTRA_UPDATE_ARGUMENTS) \
6 apache2 postgresql juju-gui haproxy rabbitmq-server nfs
7
8-integration-test: verify-juju-test config/repo-file config/license-file config/vhostssl.tmpl config/vhost.tmpl
9- juju test --set-e -p SKIP_SLOW_TESTS,DEPLOYER_TARGET,JUJU_HOME -v --timeout 3000s
10+test-depends: verify-juju-test config/repo-file config/license-file config/vhostssl.tmpl config/vhost.tmpl
11+
12+integration-test: test-depends
13+ juju test --set-e -p SKIP_SLOW_TESTS,DEPLOYER_TARGET,JUJU_HOME,JUJU_ENV -v --timeout 3000s
14+
15+deploy-dense-maas: test-depends
16+ SKIP_TESTS=1 DEPLOYER_TARGET=landscape-dense-maas tests/01-begin
17+
18+deploy-max-dense-maas: test-depends
19+ SKIP_TESTS=1 DEPLOYER_TARGET=landscape-max-dense-maas tests/01-begin
20
21 lint:
22 flake8 --exclude=charmhelpers hooks
23@@ -28,9 +36,13 @@
24 clean: clean-integration-test
25
26 .PHONY: lint \
27+ test-depends \
28+ deploy-dense-maas \
29+ deploy-max-dense-maas \
30 integration-test \
31 verify-juju-test \
32 test \
33 clean \
34 clean-integration-test \
35 update-charm-revision-numbers
36+
37
38=== modified file 'config/landscape-deployments.yaml'
39--- config/landscape-deployments.yaml 2014-06-12 10:38:38 +0000
40+++ config/landscape-deployments.yaml 2014-06-14 03:50:22 +0000
41@@ -25,8 +25,6 @@
42 ssl_cert: SELFSIGNED
43 ssl_certlocation: apache2.cert
44 ssl_keylocation: apache2.key
45- vhost_https_template: include-base64://vhostssl.tmpl
46- vhost_http_template: include-base64://vhost.tmpl
47
48 landscape:
49 inherits: _common
50@@ -53,6 +51,7 @@
51 relations:
52 - [landscape, rabbitmq-server]
53 - [landscape, haproxy]
54+ - ["landscape:vhost-config", "apache2:vhost-config"]
55 - ["landscape:db-admin", "postgresql:db-admin"]
56 - ["haproxy:website", "apache2:reverseproxy"]
57 - [landscape-msg, rabbitmq-server]
58@@ -119,6 +118,7 @@
59 relations:
60 - [landscape, rabbitmq-server]
61 - [landscape, haproxy]
62+ - ["landscape:vhost-config", "apache2:vhost-config"]
63 - ["landscape:db-admin", "postgresql:db-admin"]
64 - ["haproxy:website", "apache2:reverseproxy"]
65 - [landscape-msg, rabbitmq-server]
66@@ -130,6 +130,7 @@
67 - [landscape-app, rabbitmq-server]
68 - [landscape-app, haproxy]
69 - ["landscape-app:db-admin", "postgresql:db-admin"]
70+ - ["landscape-app:vhost-config", "apache2:vhost-config"]
71
72 landscape-max-dense-maas:
73 inherits: landscape-max
74
75=== modified file 'hooks/hooks.py'
76--- hooks/hooks.py 2014-06-09 20:33:41 +0000
77+++ hooks/hooks.py 2014-06-14 03:50:22 +0000
78@@ -9,15 +9,15 @@
79 from lib import util
80 from lib.juju import Juju
81
82-from base64 import b64encode
83+from base64 import b64encode, b64decode
84 from configobj import ConfigObj, ConfigObjError
85+from contextlib import closing
86 from copy import deepcopy
87 import cStringIO
88 import datetime
89 import grp
90 import os
91 import psutil
92-import psycopg2
93 import pwd
94 import pycurl
95 import re
96@@ -27,6 +27,9 @@
97 from subprocess import check_call, check_output, CalledProcessError, call
98
99
100+SSL_CERT_LOCATION = "/etc/ssl/certs/landscape_server_ca.crt"
101+
102+
103 def _get_installed_version(name):
104 """Returns the version string of name using dpkg-query or returns None"""
105 try:
106@@ -95,6 +98,29 @@
107 services=yaml.safe_dump(_get_services_haproxy()))
108
109
110+def notify_vhost_config_relation(relation_id=None):
111+ """
112+ Notify the vhost-config relation.
113+
114+ This will mark it "ready to proceed". If relation_id is specified
115+ use that as the relation context, otherwise look up and notify all
116+ vhost-config relations.
117+ """
118+ vhosts = []
119+ with open("%s/config/vhostssl.tmpl" % ROOT, 'r') as handle:
120+ vhosts.append({
121+ "port": "443", "template": b64encode(handle.read())})
122+ with open("%s/config/vhost.tmpl" % ROOT, 'r') as handle:
123+ vhosts.append({
124+ "port": "80", "template": b64encode(handle.read())})
125+
126+ relation_ids = [relation_id]
127+ if relation_id is None:
128+ relation_ids = juju.relation_ids("vhost-config")
129+ for relation_id in relation_ids:
130+ juju.relation_set(relation_id=relation_id, vhosts=yaml.dump(vhosts))
131+
132+
133 def db_admin_relation_joined():
134 pass
135
136@@ -150,20 +176,13 @@
137 "password": password},
138 "schema": {"store_user": admin, "store_password": admin_password}})
139
140- try:
141- # Name as lock so we don't try to reuse it as a database connection
142- lock = util.connect_exclusive(host, admin, admin_password)
143- except psycopg2.Error:
144- # Another unit is performing database configuration.
145- pass
146- else:
147- try:
148- util.create_user(user, password, host, admin, admin_password)
149- _create_maintenance_user(password, host, admin, admin_password)
150- check_call("setup-landscape-server")
151- finally:
152- juju.juju_log("Landscape database initialized!")
153- lock.close()
154+ with closing(util.connect_exclusive(host, admin, admin_password)):
155+ util.create_user(user, password, host, admin, admin_password)
156+ _create_maintenance_user(password, host, admin, admin_password)
157+ check_call("setup-landscape-server")
158+ juju.juju_log("Landscape database initialized!")
159+
160+ notify_vhost_config_relation()
161
162 try:
163 # Handle remove-relation db-admin. This call will fail because
164@@ -316,6 +335,65 @@
165 config_changed()
166
167
168+def vhost_config_relation_changed():
169+ """Relate to apache to configure a vhost.
170+
171+ This hook will supply vhost configuration as well as read simple data
172+ out of apache (servername, certificate). This data is necessary for
173+ informing clients of the correct URL and cert to use when connecting
174+ to the server.
175+ """
176+ notify_vhost_config_relation(os.environ.get("JUJU_RELATION_ID", None))
177+
178+ config_obj = _get_config_obj(LANDSCAPE_SERVICE_CONF)
179+ try:
180+ section = config_obj["stores"]
181+ database = section["main"]
182+ host = section["host"]
183+ user = section["user"]
184+ password = section["password"]
185+ except KeyError:
186+ juju.juju_log("Database not ready yet, deferring call")
187+ sys.exit(0)
188+
189+ relids = juju.relation_ids("vhost-config")
190+ if relids:
191+ relid = relids[0]
192+ apache2_unit = juju.relation_list(relid)[0]
193+ apache_servername = juju.relation_get(
194+ "servername", unit_name=apache2_unit, relation_id=relid)
195+ else:
196+ apache_servername = juju.relation_get("servername")
197+
198+ if not apache_servername:
199+ juju.juju_log("Waiting for data from apache, deferring")
200+ sys.exit(0)
201+ apache_url = "https://%s/" % apache_servername
202+
203+ if not _is_db_up():
204+ juju.juju_log("Waiting for database to become available, deferring.")
205+ sys.exit(0)
206+
207+ with closing(util.connect_exclusive(host, user, password)):
208+ juju.juju_log("Updating Landscape root_url: %s" % apache_url)
209+ util.change_root_url(database, user, password, host, apache_url)
210+
211+ # This data may or may not be present, dependeing on if cert is self
212+ # signed from apache.
213+ ssl_cert = juju.relation_get(
214+ "ssl_cert", unit_name=apache2_unit, relation_id=relid)
215+ if ssl_cert:
216+ juju.juju_log("Writing new SSL cert: %s" % SSL_CERT_LOCATION)
217+ with open(SSL_CERT_LOCATION, 'w') as f:
218+ f.write(str(b64decode(ssl_cert)))
219+ else:
220+ if os.path.exists(SSL_CERT_LOCATION):
221+ os.remove(SSL_CERT_LOCATION)
222+
223+ # only starts services again if is_db_up and _is_amqp_up
224+ config_changed()
225+
226+
227 def config_changed():
228 """Update and restart services based on config setting changes.
229
230@@ -743,7 +821,8 @@
231 "data-relation-changed": data_relation_changed,
232 "db-admin-relation-joined": db_admin_relation_joined,
233 "db-admin-relation-changed": db_admin_relation_changed,
234- "website-relation-joined": website_relation_joined}
235+ "website-relation-joined": website_relation_joined,
236+ "vhost-config-relation-changed": vhost_config_relation_changed}
237 hook = os.path.basename(sys.argv[0])
238 # If the hook is unsupported, let it raise a KeyError and exit with error.
239 hooks[hook]()
240
241=== modified file 'hooks/lib/util.py'
242--- hooks/lib/util.py 2014-01-31 20:38:36 +0000
243+++ hooks/lib/util.py 2014-06-14 03:50:22 +0000
244@@ -2,8 +2,9 @@
245 Utility library for juju hooks
246 """
247
248-from psycopg2 import connect
249+from psycopg2 import connect, Error as psycopg2Error
250 from juju import Juju
251+from contextlib import closing
252
253 juju = Juju()
254
255@@ -38,7 +39,7 @@
256 password=admin_password)
257 try:
258 cur = conn.cursor()
259- cur.execute("SELECT usename FROM pg_user WHERE usename='%s'" % user)
260+ cur.execute("SELECT usename FROM pg_user WHERE usename=%s", (user,))
261 result = cur.fetchall()
262 if not result:
263 juju.juju_log("Creating postgres db user: %s" % user)
264@@ -48,6 +49,32 @@
265 conn.close()
266
267
268+def change_root_url(database, user, password, host, url):
269+ """Change the root url in the database."""
270+ url = "u%s:%s" % (len(url), url)
271+ with closing(connect(database=database, host=host,
272+ user=user, password=password)) as conn:
273+ cur = conn.cursor()
274+ cur.execute("SELECT encode(key, 'escape'),encode(value, 'escape') "
275+ "FROM system_configuration "
276+ "WHERE key='landscape.root_url' FOR UPDATE")
277+ result = cur.fetchall()
278+ if not result:
279+ juju.juju_log("Setting new root_url: %s" % url)
280+ cur.execute(
281+ "INSERT INTO system_configuration "
282+ "VALUES (decode('landscape.root_url', 'escape'), "
283+ " decode(%s, 'escape'))", (url,))
284+ else:
285+ juju.juju_log("Updating root_url %s => %s" % (result, url))
286+ cur.execute(
287+ "UPDATE system_configuration "
288+ "SET key=decode('landscape.root_url', 'escape'),"
289+ " value=decode(%s, 'escape') "
290+ "WHERE encode(key, 'escape')='landscape.root_url'", (url,))
291+ conn.commit()
292+
293+
294 def is_db_up(database, host, user, password):
295 """
296 Return True if the database relation is configured with write permission,
297@@ -59,10 +86,9 @@
298 # Ensure we are user with write access, to avoid hot standby dbs
299 cur.execute(
300 'CREATE TEMP TABLE "write_access_test_%s" (id serial PRIMARY KEY) '
301- "ON COMMIT DROP"
302- % juju.local_unit().replace("/", "_"))
303- except Exception as e:
304- juju.juju_log(str(e))
305+ "ON COMMIT DROP" % juju.local_unit().replace("/", "_"))
306+ except psycopg2Error as e:
307+ juju.juju_log("Database not yet up: %s" % e)
308 return False
309 else:
310 return True
311
312=== modified file 'hooks/test_hooks.py'
313--- hooks/test_hooks.py 2014-05-23 20:51:15 +0000
314+++ hooks/test_hooks.py 2014-06-14 03:50:22 +0000
315@@ -3,9 +3,11 @@
316 import hooks
317 import mocker
318 import os
319+import psycopg2
320 import pycurl
321 import stat
322 import subprocess
323+import tempfile
324 import yaml
325
326
327@@ -1482,6 +1484,171 @@
328 baseline = (("services", yaml.safe_dump(self.all_services)),)
329 self.assertEqual(baseline, hooks.juju._outgoing_relation_data)
330
331+ def test_notify_vhost_config_relation_specify_id(self):
332+ """
333+ notify the vhost-config relation on a separate ID.
334+ """
335+ hooks.notify_vhost_config_relation("foo/0")
336+ with open("%s/config/vhostssl.tmpl" % hooks.ROOT, 'r') as f:
337+ vhostssl_template = f.read()
338+ with open("%s/config/vhost.tmpl" % hooks.ROOT, 'r') as f:
339+ vhost_template = f.read()
340+ baseline = yaml.dump(
341+ [{"port": "443", "template": base64.b64encode(vhostssl_template)},
342+ {"port": "80", "template": base64.b64encode(vhost_template)}])
343+ self.assertEqual(
344+ (("vhosts", baseline),), hooks.juju._outgoing_relation_data)
345+
346+ def test_notify_vhost_config_relation(self):
347+ """notify the vhost-config relation on the "current" ID."""
348+ hooks.notify_vhost_config_relation()
349+ with open("%s/config/vhostssl.tmpl" % hooks.ROOT, 'r') as f:
350+ vhostssl_template = f.read()
351+ with open("%s/config/vhost.tmpl" % hooks.ROOT, 'r') as f:
352+ vhost_template = f.read()
353+ baseline = yaml.dump(
354+ [{"port": "443", "template": base64.b64encode(vhostssl_template)},
355+ {"port": "80", "template": base64.b64encode(vhost_template)}])
356+ self.assertEqual(
357+ (("vhosts", baseline),), hooks.juju._outgoing_relation_data)
358+
359+ def test_vhost_config_relation_changed_exit_no_configuration(self):
360+ """Ensure vhost_relation_changed deferrs if db is not up."""
361+ self.assertRaises(SystemExit, hooks.vhost_config_relation_changed)
362+ self.assertEquals(len(hooks.juju._logs), 1)
363+ self.assertIn('Database not ready yet', hooks.juju._logs[0])
364+
365+ def test_vhost_config_relation_changed_wait_apache_servername(self):
366+ """Ensure vhost_relation_changed deferrs if db is not up."""
367+ _get_config_obj = self.mocker.replace(hooks._get_config_obj)
368+ _get_config_obj(hooks.LANDSCAPE_SERVICE_CONF)
369+ self.mocker.result({
370+ "stores": {
371+ "main": "database",
372+ "host": "host",
373+ "user": "user",
374+ "password": "password"}})
375+ notify_vhost = self.mocker.replace(hooks.notify_vhost_config_relation)
376+ notify_vhost(None)
377+ self.mocker.replay()
378+ self.assertRaises(SystemExit, hooks.vhost_config_relation_changed)
379+ self.assertIn('Waiting for data from apache', hooks.juju._logs[-1])
380+
381+ def test_vhost_config_relation_changed_fail_root_url(self):
382+ """Ensure vhost_relation_changed deferrs if db is not up."""
383+ _get_config_obj = self.mocker.replace(hooks._get_config_obj)
384+ _get_config_obj(hooks.LANDSCAPE_SERVICE_CONF)
385+ hooks.juju._incoming_relation_data += (("servername", "foobar"),)
386+ self.mocker.result({
387+ "stores": {
388+ "main": "database",
389+ "host": "host",
390+ "user": "user",
391+ "password": "password"}})
392+ notify_vhost = self.mocker.replace(hooks.notify_vhost_config_relation)
393+ notify_vhost(None)
394+ is_db_up = self.mocker.replace(hooks._is_db_up)
395+ is_db_up()
396+ self.mocker.result(False)
397+ self.mocker.replay()
398+ self.assertRaises(SystemExit, hooks.vhost_config_relation_changed)
399+ self.assertIn(
400+ 'Waiting for database to become available, deferring',
401+ hooks.juju._logs[-1])
402+
403+ def test_vhost_config_relation_changed_fail_root_url_db_update(self):
404+ """vhost_config_relation_changed should error if db update fails"""
405+ _get_config_obj = self.mocker.replace(hooks._get_config_obj)
406+ _get_config_obj(hooks.LANDSCAPE_SERVICE_CONF)
407+ hooks.juju._incoming_relation_data += (("servername", "foobar"),)
408+ self.mocker.result({
409+ "stores": {
410+ "main": "database",
411+ "host": "host",
412+ "user": "user",
413+ "password": "password"}})
414+ notify_vhost = self.mocker.replace(hooks.notify_vhost_config_relation)
415+ notify_vhost(None)
416+ is_db_up = self.mocker.replace(hooks._is_db_up)
417+ is_db_up()
418+ self.mocker.result(True)
419+ self.mocker.replay()
420+ self.assertRaises(psycopg2.Error, hooks.vhost_config_relation_changed)
421+
422+ def test_vhost_config_relation_changed_cert_not_provided(self):
423+ """
424+ Ensure vhost_relation_changed runs to completion.
425+
426+ Existing cert should be removed.
427+ """
428+ hooks.SSL_CERT_LOCATION = tempfile.NamedTemporaryFile().name
429+ _get_config_obj = self.mocker.replace(hooks._get_config_obj)
430+ _get_config_obj(hooks.LANDSCAPE_SERVICE_CONF)
431+ hooks.juju._incoming_relation_data += (("servername", "foobar"),)
432+ self.mocker.result({
433+ "stores": {
434+ "main": "database",
435+ "host": "host",
436+ "user": "user",
437+ "password": "password"}})
438+ notify_vhost = self.mocker.replace(hooks.notify_vhost_config_relation)
439+ notify_vhost(None)
440+ mock_conn = self.mocker.mock()
441+ mock_conn.close()
442+ connect_exclusive = self.mocker.replace(hooks.util.connect_exclusive)
443+ connect_exclusive("host", "user", "password")
444+ self.mocker.result(mock_conn)
445+ change_root_url = self.mocker.replace(hooks.util.change_root_url)
446+ change_root_url(
447+ "database", "user", "password", "host", "https://foobar/")
448+ config_changed = self.mocker.replace(hooks.config_changed)
449+ config_changed()
450+ is_db_up = self.mocker.replace(hooks._is_db_up)
451+ is_db_up()
452+ self.mocker.result(True)
453+ self.mocker.replay()
454+ hooks.vhost_config_relation_changed()
455+ self.assertFalse(os.path.exists(hooks.SSL_CERT_LOCATION))
456+
457+ def test_vhost_config_relation_changed_ssl_cert_provided(self):
458+ """
459+ Ensure vhost_relation_changed runs to completion.
460+
461+ Cert passed in to other side of relation should be written on disk.
462+ """
463+ hooks.SSL_CERT_LOCATION = tempfile.NamedTemporaryFile().name
464+ _get_config_obj = self.mocker.replace(hooks._get_config_obj)
465+ _get_config_obj(hooks.LANDSCAPE_SERVICE_CONF)
466+ hooks.juju._incoming_relation_data += (("servername", "foobar"),)
467+ hooks.juju._incoming_relation_data += (
468+ ("ssl_cert", base64.b64encode("foobar")),)
469+ self.mocker.result({
470+ "stores": {
471+ "main": "database",
472+ "host": "host",
473+ "user": "user",
474+ "password": "password"}})
475+ notify_vhost = self.mocker.replace(hooks.notify_vhost_config_relation)
476+ notify_vhost(None)
477+ is_db_up = self.mocker.replace(hooks._is_db_up)
478+ is_db_up()
479+ self.mocker.result(True)
480+ mock_conn = self.mocker.mock()
481+ mock_conn.close()
482+ connect_exclusive = self.mocker.replace(hooks.util.connect_exclusive)
483+ connect_exclusive("host", "user", "password")
484+ self.mocker.result(mock_conn)
485+ change_root_url = self.mocker.replace(hooks.util.change_root_url)
486+ change_root_url(
487+ "database", "user", "password", "host", "https://foobar/")
488+ config_changed = self.mocker.replace(hooks.config_changed)
489+ config_changed()
490+ self.mocker.replay()
491+ hooks.vhost_config_relation_changed()
492+ self.assertTrue(os.path.exists(hooks.SSL_CERT_LOCATION))
493+ with open(hooks.SSL_CERT_LOCATION, 'r') as f:
494+ self.assertEqual("foobar", f.read())
495+
496
497 class TestHooksUtils(TestHooks):
498 def test__setup_apache(self):
499
500=== added symlink 'hooks/vhost-config-relation-changed'
501=== target is u'hooks.py'
502=== modified file 'metadata.yaml'
503--- metadata.yaml 2014-04-25 15:04:36 +0000
504+++ metadata.yaml 2014-06-14 03:50:22 +0000
505@@ -23,3 +23,5 @@
506 optional: true
507 website:
508 interface: http
509+ vhost-config:
510+ interface: apache-vhost-config
511
512=== modified file 'tests/01-begin'
513--- tests/01-begin 2014-06-11 12:50:56 +0000
514+++ tests/01-begin 2014-06-14 03:50:22 +0000
515@@ -191,6 +191,8 @@
516 self.service_name = service_name
517
518
519+@unittest.skipIf(
520+ getenv("SKIP_TESTS", None), "Requested to skip all tests.")
521 class BaseLandscapeTests(unittest.TestCase):
522 """
523 Base class with some commonality between all test classes.

Subscribers

People subscribed via source and target branches