Merge lp:~bjornt/maas/peer_proxy into lp:~maas-committers/maas/trunk

Proposed by Björn Tillenius
Status: Merged
Approved by: Björn Tillenius
Approved revision: no longer in the source branch.
Merged at revision: 6092
Proposed branch: lp:~bjornt/maas/peer_proxy
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~bjornt/maas/get-apt-proxy
Diff against target: 646 lines (+363/-16)
17 files modified
src/maasserver/api/tests/test_maas.py (+11/-0)
src/maasserver/compose_preseed.py (+2/-1)
src/maasserver/forms/settings.py (+12/-0)
src/maasserver/models/config.py (+1/-0)
src/maasserver/proxyconfig.py (+17/-0)
src/maasserver/service_monitor.py (+2/-1)
src/maasserver/tests/test_compose_preseed.py (+23/-11)
src/maasserver/tests/test_preseed.py (+36/-0)
src/maasserver/tests/test_proxyconfig.py (+68/-0)
src/maasserver/tests/test_service_monitor.py (+21/-0)
src/maasserver/triggers/system.py (+42/-0)
src/maasserver/triggers/testing.py (+14/-0)
src/maasserver/triggers/tests/test_init.py (+2/-0)
src/maasserver/triggers/tests/test_system_listener.py (+97/-1)
src/provisioningserver/templates/proxy/maas-proxy.conf.template (+4/-0)
src/provisioningserver/utils/service_monitor.py (+4/-2)
src/provisioningserver/utils/tests/test_service_monitor.py (+7/-0)
To merge this branch: bzr merge lp:~bjornt/maas/peer_proxy
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+325758@code.launchpad.net

Commit message

Add support for using the http proxy as a peer proxy.

If use_peer_proxy is True in the settings, the built-in http proxy will
be configured to use the specified http proxy as a cache peer. So that
machines will go through the built-in proxy, which in turn will go
through the external http proxy to download packages.

It's possible to configure use_peer_proxy via the API for now. The UI
will come later.

Description of the change

Add support for using the http proxy as a peer proxy.

If use_peer_proxy is True in the settings, the built-in http proxy will
be configured to use the specified http proxy as a cache peer. So that
machines will go through the built-in proxy, which in turn will go
through the external http proxy to download packages.

It's possible to configure use_peer_proxy via the API for now. The UI
will come later.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Does this fix bug #1276945? If so, can you link the bug?

Other than that, looks good to me. Nice that you updated the trigger, too; sometimes that's overlooked and it causes problems.

review: Approve
Revision history for this message
Björn Tillenius (bjornt) wrote :

Ah, yes, it does fixes that bug. I didn't know about it, but I have linked it now.

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Thanks. I can't tell how you linked the bug; can you be sure to link it from a bzr commit, if you didn't already? (such as by doing 'bzr commit --unchanged -m "Link bug." --fixes lp:1276945 && bzr push') That way "Fix Committed" on the bug will automatically be set when it lands.

Oh, also consider adding the second paragraph in your description to the commit message.

Revision history for this message
MAAS Lander (maas-lander) wrote :
Download full text (2.0 MiB)

The attempt to merge lp:~bjornt/maas/peer_proxy into lp:maas failed. Below is the output from the failed tests.

Hit:1 http://security.ubuntu.com/ubuntu xenial-security InRelease
Hit:2 http://archive.ubuntu.com/ubuntu xenial InRelease
Get:3 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [102 kB]
Get:4 http://archive.ubuntu.com/ubuntu xenial-backports InRelease [102 kB]
Fetched 204 kB in 1s (173 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
    --no-install-recommends install apache2 archdetect-deb authbind avahi-utils bash bind9 bind9utils build-essential bzr bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common isc-dhcp-server libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libnss-wrapper libpq-dev make nodejs-legacy npm postgresql psmisc pxelinux python3-all python3-apt python3-attr python3-bson python3-convoy python3-crochet python3-cssselect python3-curtin python3-dev python3-distro-info python3-django python3-django-nose python3-django-piston3 python3-dnspython python3-docutils python3-formencode python3-hivex python3-httplib2 python3-jinja2 python3-jsonschema python3-lxml python3-netaddr python3-netifaces python3-novaclient python3-oauth python3-oauthlib python3-openssl python3-paramiko python3-petname python3-pexpect python3-psycopg2 python3-pyinotify python3-pyparsing python3-pyvmomi python3-requests python3-seamicroclient python3-setuptools python3-simplestreams python3-sphinx python3-tempita python3-twisted python3-txtftp python3-tz python3-yaml python3-zope.interface python-bson python-crochet python-django python-django-piston python-djorm-ext-pgarray python-formencode python-lxml python-netaddr python-netifaces python-pocket-lint python-psycopg2 python-simplejson python-tempita python-twisted python-yaml socat syslinux-common tgt ubuntu-cloudimage-keyring wget xvfb
Reading package lists...
Building dependency tree...
Reading state information...
authbind is already the newest version (2.1.1+nmu1).
avahi-utils is already the newest version (0.6.32~rc+dfsg-1ubuntu2).
build-essential is already the newest version (12.1ubuntu2).
debhelper is already the newest version (9.20160115ubuntu3).
distro-info is already the newest version (0.14build1).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
psmisc is already the newest version (22.21-2.1build1).
pxelinux is already the newest version (3:6.03+dfsg-11ubuntu1).
python-formencode is already the newest version (1.3.0-0ubuntu5).
python-lxml is already the newest version (3.5.0-1build1).
python-netaddr is already the newest version (0.7.18-1).
python-netifaces is already the newest version (0.10.4-0.1build2).
python-psycopg2 is already the newest version (2.6.1-1build2).
python-simplejson is already ...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/tests/test_maas.py'
2--- src/maasserver/api/tests/test_maas.py 2017-02-17 14:23:04 +0000
3+++ src/maasserver/api/tests/test_maas.py 2017-06-16 13:49:45 +0000
4@@ -281,3 +281,14 @@
5 self.assertThat(
6 PackageRepository.get_ports_archive().url,
7 Equals(ports_archive))
8+
9+ def test_set_config_use_peer_proxy(self):
10+ self.become_admin()
11+ response = self.client.post(
12+ reverse('maas_handler'), {
13+ "op": "set_config",
14+ "name": "use_peer_proxy",
15+ "value": True,
16+ })
17+ self.assertEqual(http.client.OK, response.status_code)
18+ self.assertTrue(Config.objects.get_config("use_peer_proxy"))
19
20=== modified file 'src/maasserver/compose_preseed.py'
21--- src/maasserver/compose_preseed.py 2017-06-15 16:04:10 +0000
22+++ src/maasserver/compose_preseed.py 2017-06-16 13:49:45 +0000
23@@ -37,7 +37,8 @@
24 http_proxy = Config.objects.get_config("http_proxy")
25 if http_proxy is not None:
26 http_proxy = http_proxy.strip()
27- if http_proxy:
28+ use_peer_proxy = Config.objects.get_config("use_peer_proxy")
29+ if http_proxy and not use_peer_proxy:
30 return http_proxy
31 else:
32 return compose_URL(
33
34=== modified file 'src/maasserver/forms/settings.py'
35--- src/maasserver/forms/settings.py 2017-04-24 16:47:00 +0000
36+++ src/maasserver/forms/settings.py 2017-06-16 13:49:45 +0000
37@@ -225,6 +225,18 @@
38 "downloading boot images.")
39 }
40 },
41+ 'use_peer_proxy': {
42+ 'default': False,
43+ 'form': forms.BooleanField,
44+ 'form_kwargs': {
45+ 'label': "Use the built-in proxy with an external proxy as a peer",
46+ 'required': False,
47+ 'help_text': (
48+ "If enable_http_proxy is set, the built-in proxy will be "
49+ "configured to use http_proxy as a peer proxy. The deployed "
50+ "machines will be configured to use the built-in proxy.")
51+ }
52+ },
53 'http_proxy': {
54 'default': None,
55 'form': forms.URLField,
56
57=== modified file 'src/maasserver/models/config.py'
58--- src/maasserver/models/config.py 2017-04-07 21:52:35 +0000
59+++ src/maasserver/models/config.py 2017-06-16 13:49:45 +0000
60@@ -69,6 +69,7 @@
61 'default_osystem': DEFAULT_OS.name,
62 'default_distro_series': DEFAULT_OS.get_default_release(),
63 'enable_http_proxy': True,
64+ 'use_peer_proxy': False,
65 'http_proxy': None,
66 'upstream_dns': None,
67 'dnssec_validation': "auto",
68
69=== modified file 'src/maasserver/proxyconfig.py'
70--- src/maasserver/proxyconfig.py 2017-05-02 19:00:58 +0000
71+++ src/maasserver/proxyconfig.py 2017-06-16 13:49:45 +0000
72@@ -13,8 +13,10 @@
73 import os
74 import socket
75 import sys
76+from urllib.parse import urlparse
77
78 from django.conf import settings
79+from maasserver.models import Config
80 from maasserver.models.subnet import Subnet
81 from maasserver.service_monitor import service_monitor
82 from maasserver.utils.orm import transactional
83@@ -67,6 +69,10 @@
84 def write_config():
85 allowed_subnets = Subnet.objects.filter(allow_proxy=True)
86 cidrs = [subnet.cidr for subnet in allowed_subnets]
87+
88+ http_proxy = Config.objects.get_config("http_proxy")
89+ upstream_proxy_enabled = (
90+ Config.objects.get_config("use_peer_proxy") and http_proxy)
91 context = {
92 'allowed': allowed_subnets,
93 'modified': str(datetime.date.today()),
94@@ -76,7 +82,18 @@
95 'snap_path': snappy.get_snap_path(),
96 'snap_data_path': snappy.get_snap_data_path(),
97 'snap_common_path': snappy.get_snap_common_path(),
98+ 'upstream_peer_proxy': upstream_proxy_enabled,
99 }
100+
101+ proxy_enabled = Config.objects.get_config("enable_http_proxy")
102+ if proxy_enabled and upstream_proxy_enabled:
103+ http_proxy_hostname = urlparse(http_proxy).hostname
104+ http_proxy_port = urlparse(http_proxy).port
105+ context.update({
106+ 'upstream_proxy_address': http_proxy_hostname,
107+ 'upstream_proxy_port': http_proxy_port,
108+ })
109+
110 template_path = locate_template('proxy', MAAS_PROXY_CONF_TEMPLATE)
111 template = tempita.Template.from_filename(
112 template_path, encoding="UTF-8")
113
114=== modified file 'src/maasserver/service_monitor.py'
115--- src/maasserver/service_monitor.py 2016-10-18 16:57:31 +0000
116+++ src/maasserver/service_monitor.py 2017-06-16 13:49:45 +0000
117@@ -52,7 +52,8 @@
118 # Avoid recursive import.
119 from maasserver import proxyconfig
120 if (Config.objects.get_config("enable_http_proxy") and
121- Config.objects.get_config("http_proxy")):
122+ Config.objects.get_config("http_proxy") and
123+ not Config.objects.get_config("use_peer_proxy")):
124 return (SERVICE_STATE.OFF,
125 "disabled, alternate proxy is configured in settings.")
126 elif proxyconfig.is_config_present() is False:
127
128=== modified file 'src/maasserver/tests/test_compose_preseed.py'
129--- src/maasserver/tests/test_compose_preseed.py 2017-06-15 16:04:10 +0000
130+++ src/maasserver/tests/test_compose_preseed.py 2017-06-16 13:49:45 +0000
131@@ -51,26 +51,37 @@
132 rack='2001:db8::1',
133 result='http://[2001:db8::1]:8000/',
134 enable=True,
135+ use_peer_proxy=False,
136 http_proxy='')),
137 ("ipv4", dict(
138 rack='10.0.1.1',
139 result='http://10.0.1.1:8000/',
140 enable=True,
141- http_proxy='')),
142- ("name", dict(
143- rack='example.com',
144- result='http://example.com:8000/',
145- enable=True,
146- http_proxy='')),
147- ("override", dict(
148- rack='wrong.com',
149- result='http://example.com:111/',
150- enable=True,
151- http_proxy='http://example.com:111/')),
152+ use_peer_proxy=False,
153+ http_proxy='')),
154+ ("builtin", dict(
155+ rack='region.example.com',
156+ result='http://region.example.com:8000/',
157+ enable=True,
158+ use_peer_proxy=False,
159+ http_proxy='')),
160+ ("external", dict(
161+ rack='region.example.com',
162+ result='http://proxy.example.com:111/',
163+ enable=True,
164+ use_peer_proxy=False,
165+ http_proxy='http://proxy.example.com:111/')),
166+ ("peer-proxy", dict(
167+ rack='region.example.com',
168+ result='http://region.example.com:8000/',
169+ enable=True,
170+ use_peer_proxy=True,
171+ http_proxy='http://proxy.example.com:111/')),
172 ("disabled", dict(
173 rack='example.com',
174 result=None,
175 enable=False,
176+ use_peer_proxy=False,
177 http_proxy='')),
178 )
179
180@@ -89,6 +100,7 @@
181 interface=True, status=NODE_STATUS.COMMISSIONING)
182 Config.objects.set_config("enable_http_proxy", self.enable)
183 Config.objects.set_config("http_proxy", self.http_proxy)
184+ Config.objects.set_config("use_peer_proxy", self.use_peer_proxy)
185 actual = get_apt_proxy(node.get_boot_rack_controller())
186 self.assertEqual(self.result, actual)
187
188
189=== modified file 'src/maasserver/tests/test_preseed.py'
190--- src/maasserver/tests/test_preseed.py 2017-04-26 19:02:50 +0000
191+++ src/maasserver/tests/test_preseed.py 2017-06-16 13:49:45 +0000
192@@ -34,6 +34,7 @@
193 BootResource,
194 Config,
195 PackageRepository,
196+ signals,
197 )
198 from maasserver.preseed import (
199 compose_curtin_archive_config,
200@@ -1658,6 +1659,12 @@
201 These tests check that the preseed templates render and 'look right'.
202 """
203
204+ def setUp(self):
205+ super().setUp()
206+ # We don't want to test that the bootsources get updated.
207+ self.addCleanup(signals.bootsources.signals.enable)
208+ signals.bootsources.signals.disable()
209+
210 def assertSystemInfo(self, config):
211 self.assertThat(config, ContainsDict({
212 'system_info': MatchesDict({
213@@ -1747,6 +1754,35 @@
214 self.assertAptConfig(preseed, apt_proxy)
215 self.assertSystemInfo(preseed)
216
217+ def test_get_enlist_userdata_no_proxy(self):
218+ Config.objects.set_config('enable_http_proxy', False)
219+ Config.objects.set_config('http_proxy', 'http://example.com:3128')
220+ preseed = yaml.safe_load(get_enlist_userdata())
221+ self.assertIsNone(preseed['apt_proxy'])
222+ self.assertNotIn('proxy', preseed['apt'])
223+
224+ def test_get_enlist_userdata_use_builtin_proxy(self):
225+ Config.objects.set_config('enable_http_proxy', True)
226+ Config.objects.set_config('http_proxy', '')
227+ preseed = yaml.safe_load(get_enlist_userdata())
228+ self.assertEqual('http://localhost:8000/', preseed['apt_proxy'])
229+ self.assertEqual('http://localhost:8000/', preseed['apt']['proxy'])
230+
231+ def test_get_enlist_userdata_use_external_proxy(self):
232+ Config.objects.set_config('enable_http_proxy', True)
233+ Config.objects.set_config('http_proxy', 'http://example.com:3128/')
234+ preseed = yaml.safe_load(get_enlist_userdata())
235+ self.assertEqual('http://example.com:3128/', preseed['apt_proxy'])
236+ self.assertEqual('http://example.com:3128/', preseed['apt']['proxy'])
237+
238+ def test_get_enlist_userdata_use_peer_proxy(self):
239+ Config.objects.set_config('enable_http_proxy', True)
240+ Config.objects.set_config('use_peer_proxy', True)
241+ Config.objects.set_config('http_proxy', 'http://example.com:3128/')
242+ preseed = yaml.safe_load(get_enlist_userdata())
243+ self.assertEqual('http://localhost:8000/', preseed['apt_proxy'])
244+ self.assertEqual('http://localhost:8000/', preseed['apt']['proxy'])
245+
246 def test_get_preseed_returns_commissioning_preseed(self):
247 node = factory.make_Node_with_Interface_on_Subnet(
248 primary_rack=self.rpc_rack_controller,
249
250=== modified file 'src/maasserver/tests/test_proxyconfig.py'
251--- src/maasserver/tests/test_proxyconfig.py 2017-05-02 18:42:35 +0000
252+++ src/maasserver/tests/test_proxyconfig.py 2017-06-16 13:49:45 +0000
253@@ -6,11 +6,13 @@
254 __all__ = []
255
256 import os
257+from pathlib import Path
258
259 from crochet import wait_for
260 from django.conf import settings
261 from fixtures import EnvironmentVariableFixture
262 from maasserver import proxyconfig
263+from maasserver.models import Config
264 from maasserver.testing.factory import factory
265 from maasserver.testing.testcase import (
266 MAASServerTestCase,
267@@ -58,6 +60,7 @@
268 def setUp(self):
269 super(TestProxyUpdateConfig, self).setUp()
270 self.tmpdir = self.make_dir()
271+ self.proxy_path = Path(self.tmpdir) / proxyconfig.MAAS_PROXY_CONF_NAME
272 self.service_monitor = self.patch(proxyconfig, "service_monitor")
273 self.useFixture(
274 EnvironmentVariableFixture('MAAS_PROXY_CONFIG_DIR', self.tmpdir))
275@@ -86,6 +89,71 @@
276
277 @wait_for_reactor
278 @inlineCallbacks
279+ def test__with_use_peer_proxy_with_http_proxy(self):
280+ self.patch(settings, "PROXY_CONNECT", True)
281+ yield deferToDatabase(
282+ transactional(Config.objects.set_config),
283+ "enable_http_proxy", True)
284+ yield deferToDatabase(
285+ transactional(Config.objects.set_config),
286+ "use_peer_proxy", True)
287+ yield deferToDatabase(
288+ transactional(Config.objects.set_config),
289+ "http_proxy", "http://example.com:8000/")
290+ yield deferToDatabase(self.make_subnet, allow_proxy=False)
291+ yield deferToDatabase(self.make_subnet)
292+ yield proxyconfig.proxy_update_config(reload_proxy=False)
293+ cache_peer_line = (
294+ "cache_peer example.com parent 8000 0 no-query default")
295+ with self.proxy_path.open() as proxy_file:
296+ lines = [line.strip() for line in proxy_file.readlines()]
297+ self.assertIn('never_direct allow all', lines)
298+ self.assertIn(cache_peer_line, lines)
299+
300+ @wait_for_reactor
301+ @inlineCallbacks
302+ def test__with_use_peer_proxy_without_http_proxy(self):
303+ self.patch(settings, "PROXY_CONNECT", True)
304+ yield deferToDatabase(
305+ transactional(Config.objects.set_config),
306+ "enable_http_proxy", True)
307+ yield deferToDatabase(
308+ transactional(Config.objects.set_config),
309+ "use_peer_proxy", True)
310+ yield deferToDatabase(
311+ transactional(Config.objects.set_config),
312+ "http_proxy", "")
313+ yield deferToDatabase(self.make_subnet, allow_proxy=False)
314+ yield deferToDatabase(self.make_subnet)
315+ yield proxyconfig.proxy_update_config(reload_proxy=False)
316+ with self.proxy_path.open() as proxy_file:
317+ lines = [line.strip() for line in proxy_file.readlines()]
318+ self.assertNotIn('never_direct allow all', lines)
319+ self.assertNotIn('cache_peer', lines)
320+
321+ @wait_for_reactor
322+ @inlineCallbacks
323+ def test__without_use_peer_proxy(self):
324+ self.patch(settings, "PROXY_CONNECT", True)
325+ yield deferToDatabase(
326+ transactional(Config.objects.set_config),
327+ "enable_http_proxy", True)
328+ yield deferToDatabase(
329+ transactional(Config.objects.set_config),
330+ "use_peer_proxy", False)
331+ yield deferToDatabase(
332+ transactional(Config.objects.set_config),
333+ "http_proxy", "http://example.com:8000/")
334+ yield deferToDatabase(self.make_subnet, allow_proxy=False)
335+ yield deferToDatabase(self.make_subnet)
336+ yield proxyconfig.proxy_update_config(reload_proxy=False)
337+ with self.proxy_path.open() as proxy_file:
338+ lines = [line.strip() for line in proxy_file.readlines()]
339+ self.assertNotIn('never_direct allow all', lines)
340+ self.assertNotIn('cache_peer', lines)
341+
342+ @wait_for_reactor
343+ @inlineCallbacks
344 def test__calls_reloadService(self):
345 self.patch(settings, "PROXY_CONNECT", True)
346 yield deferToDatabase(self.make_subnet)
347
348=== modified file 'src/maasserver/tests/test_service_monitor.py'
349--- src/maasserver/tests/test_service_monitor.py 2017-01-28 00:51:47 +0000
350+++ src/maasserver/tests/test_service_monitor.py 2017-06-16 13:49:45 +0000
351@@ -131,3 +131,24 @@
352 (SERVICE_STATE.OFF,
353 'disabled, alternate proxy is configured in settings.'),
354 expected_state)
355+
356+ @wait_for_reactor
357+ @inlineCallbacks
358+ def test_getExpectedState_returns_on_for_proxy_on_and_set_peer_proxy(self):
359+ # Disable boot source cache signals.
360+ self.addCleanup(bootsources.signals.enable)
361+ bootsources.signals.disable()
362+
363+ service = self.make_proxy_service()
364+ yield deferToDatabase(
365+ transactional(Config.objects.set_config),
366+ "enable_http_proxy", True)
367+ yield deferToDatabase(
368+ transactional(Config.objects.set_config),
369+ "use_peer_proxy", True)
370+ yield deferToDatabase(
371+ transactional(Config.objects.set_config),
372+ "http_proxy", factory.make_url())
373+ self.patch(proxyconfig, "is_config_present").return_value = True
374+ expected_state = yield maybeDeferred(service.getExpectedState)
375+ self.assertEqual((SERVICE_STATE.ON, None), expected_state)
376
377=== modified file 'src/maasserver/triggers/system.py'
378--- src/maasserver/triggers/system.py 2017-05-05 15:30:51 +0000
379+++ src/maasserver/triggers/system.py 2017-06-16 13:49:45 +0000
380@@ -1050,6 +1050,38 @@
381 """)
382
383
384+# Triggered when the proxy settings are updated.
385+PEER_PROXY_CONFIG_INSERT = dedent("""\
386+ CREATE OR REPLACE FUNCTION sys_proxy_config_use_peer_proxy_insert()
387+ RETURNS trigger as $$
388+ BEGIN
389+ IF (NEW.name = 'enable_proxy' OR
390+ NEW.name = 'use_peer_proxy' OR
391+ NEW.name = 'http_proxy') THEN
392+ PERFORM pg_notify('sys_proxy', '');
393+ END IF;
394+ RETURN NEW;
395+ END;
396+ $$ LANGUAGE plpgsql;
397+ """)
398+
399+
400+# Triggered when the proxy settings are updated.
401+PEER_PROXY_CONFIG_UPDATE = dedent("""\
402+ CREATE OR REPLACE FUNCTION sys_proxy_config_use_peer_proxy_update()
403+ RETURNS trigger as $$
404+ BEGIN
405+ IF (NEW.name = 'enable_proxy' OR
406+ NEW.name = 'use_peer_proxy' OR
407+ NEW.name = 'http_proxy') THEN
408+ PERFORM pg_notify('sys_proxy', '');
409+ END IF;
410+ RETURN NEW;
411+ END;
412+ $$ LANGUAGE plpgsql;
413+ """)
414+
415+
416 def render_sys_dns_procedure(proc_name, on_delete=False):
417 """Render a database procedure that creates a new DNS publication.
418
419@@ -1338,3 +1370,13 @@
420 register_trigger(
421 "maasserver_subnet",
422 "sys_proxy_subnet_delete", "delete")
423+
424+ # - Config/http_proxy (when use_peer_proxy)
425+ register_procedure(PEER_PROXY_CONFIG_INSERT)
426+ register_trigger(
427+ "maasserver_config", "sys_proxy_config_use_peer_proxy_insert",
428+ "insert")
429+ register_procedure(PEER_PROXY_CONFIG_UPDATE)
430+ register_trigger(
431+ "maasserver_config", "sys_proxy_config_use_peer_proxy_update",
432+ "update")
433
434=== modified file 'src/maasserver/triggers/testing.py'
435--- src/maasserver/triggers/testing.py 2017-05-05 14:47:35 +0000
436+++ src/maasserver/triggers/testing.py 2017-06-16 13:49:45 +0000
437@@ -19,6 +19,7 @@
438 Pod,
439 )
440 from maasserver.models.cacheset import CacheSet
441+from maasserver.models.config import Config
442 from maasserver.models.dhcpsnippet import DHCPSnippet
443 from maasserver.models.dnsdata import DNSData
444 from maasserver.models.dnspublication import DNSPublication
445@@ -751,6 +752,19 @@
446 def reload_object(self, obj):
447 return reload_object(obj)
448
449+ @transactional
450+ def create_config(self, name, value):
451+ config, freshly_created = Config.objects.get_or_create(
452+ name=name, defaults=dict(value=value))
453+ assert freshly_created, "Config already created."
454+ return config
455+
456+ @transactional
457+ def set_config(self, name, value):
458+ config = Config.objects.get(name=name)
459+ config.value = value
460+ config.save()
461+
462
463 class DNSHelpersMixin:
464 """Helper to get the zone serial and to assert it was incremented."""
465
466=== modified file 'src/maasserver/triggers/tests/test_init.py'
467--- src/maasserver/triggers/tests/test_init.py 2017-04-17 17:04:50 +0000
468+++ src/maasserver/triggers/tests/test_init.py 2017-06-16 13:49:45 +0000
469@@ -123,6 +123,8 @@
470 "config_config_create_notify",
471 "config_config_delete_notify",
472 "config_config_update_notify",
473+ "config_sys_proxy_config_use_peer_proxy_insert",
474+ "config_sys_proxy_config_use_peer_proxy_update",
475 "dhcpsnippet_dhcpsnippet_create_notify",
476 "dhcpsnippet_dhcpsnippet_delete_notify",
477 "dhcpsnippet_dhcpsnippet_update_notify",
478
479=== modified file 'src/maasserver/triggers/tests/test_system_listener.py'
480--- src/maasserver/triggers/tests/test_system_listener.py 2017-05-05 15:11:21 +0000
481+++ src/maasserver/triggers/tests/test_system_listener.py 2017-06-16 13:49:45 +0000
482@@ -3713,7 +3713,7 @@
483 % (json.dumps(kms_host_old), json.dumps(kms_host_new))))
484
485
486-class TestProxySubnetListener(
487+class TestProxyListener(
488 MAASTransactionServerTestCase, TransactionalHelpersMixin):
489 """End-to-end test for the proxy triggers code."""
490
491@@ -3787,3 +3787,99 @@
492 yield dv.get(timeout=2)
493 finally:
494 yield listener.stopService()
495+
496+ @wait_for_reactor
497+ @inlineCallbacks
498+ def test_sends_message_for_config_insert_enable_proxy(self):
499+ yield deferToDatabase(register_system_triggers)
500+ dv = DeferredValue()
501+ listener = self.make_listener_without_delay()
502+ listener.register(
503+ "sys_proxy", lambda *args: dv.set(args))
504+ yield listener.startService()
505+ try:
506+ yield deferToDatabase(self.create_config, "enable_proxy", True)
507+ yield dv.get(timeout=2)
508+ finally:
509+ yield listener.stopService()
510+
511+ @wait_for_reactor
512+ @inlineCallbacks
513+ def test_sends_message_for_config_insert_use_peer_proxy(self):
514+ yield deferToDatabase(register_system_triggers)
515+ dv = DeferredValue()
516+ listener = self.make_listener_without_delay()
517+ listener.register(
518+ "sys_proxy", lambda *args: dv.set(args))
519+ yield listener.startService()
520+ try:
521+ yield deferToDatabase(self.create_config, "use_peer_proxy", True)
522+ yield dv.get(timeout=2)
523+ finally:
524+ yield listener.stopService()
525+
526+ @wait_for_reactor
527+ @inlineCallbacks
528+ def test_sends_message_for_config_insert_http_proxy(self):
529+ yield deferToDatabase(register_system_triggers)
530+ dv = DeferredValue()
531+ listener = self.make_listener_without_delay()
532+ listener.register(
533+ "sys_proxy", lambda *args: dv.set(args))
534+ yield listener.startService()
535+ try:
536+ yield deferToDatabase(
537+ self.create_config, "http_proxy", "http://proxy.example.com")
538+ yield dv.get(timeout=2)
539+ finally:
540+ yield listener.stopService()
541+
542+ @wait_for_reactor
543+ @inlineCallbacks
544+ def test_sends_message_for_config_update_enable_proxy(self):
545+ yield deferToDatabase(register_system_triggers)
546+ yield deferToDatabase(self.create_config, "enable_proxy", True)
547+ dv = DeferredValue()
548+ listener = self.make_listener_without_delay()
549+ listener.register(
550+ "sys_proxy", lambda *args: dv.set(args))
551+ yield listener.startService()
552+ try:
553+ yield deferToDatabase(self.set_config, "enable_proxy", False)
554+ yield dv.get(timeout=2)
555+ finally:
556+ yield listener.stopService()
557+
558+ @wait_for_reactor
559+ @inlineCallbacks
560+ def test_sends_message_for_config_update_use_peer_proxy(self):
561+ yield deferToDatabase(register_system_triggers)
562+ yield deferToDatabase(self.create_config, "use_peer_proxy", True)
563+ dv = DeferredValue()
564+ listener = self.make_listener_without_delay()
565+ listener.register(
566+ "sys_proxy", lambda *args: dv.set(args))
567+ yield listener.startService()
568+ try:
569+ yield deferToDatabase(self.set_config, "use_peer_proxy", False)
570+ yield dv.get(timeout=2)
571+ finally:
572+ yield listener.stopService()
573+
574+ @wait_for_reactor
575+ @inlineCallbacks
576+ def test_sends_message_for_config_update_http_proxy(self):
577+ yield deferToDatabase(register_system_triggers)
578+ yield deferToDatabase(
579+ self.create_config, "http_proxy", "http://proxy1.example.com")
580+ dv = DeferredValue()
581+ listener = self.make_listener_without_delay()
582+ listener.register(
583+ "sys_proxy", lambda *args: dv.set(args))
584+ yield listener.startService()
585+ try:
586+ yield deferToDatabase(
587+ self.set_config, "http_proxy", "http://proxy2.example.com")
588+ yield dv.get(timeout=2)
589+ finally:
590+ yield listener.stopService()
591
592=== modified file 'src/provisioningserver/templates/proxy/maas-proxy.conf.template'
593--- src/provisioningserver/templates/proxy/maas-proxy.conf.template 2017-05-12 16:23:16 +0000
594+++ src/provisioningserver/templates/proxy/maas-proxy.conf.template 2017-06-16 13:49:45 +0000
595@@ -57,3 +57,7 @@
596 cache_log /var/log/maas/proxy/cache.log
597 cache_store_log /var/log/maas/proxy/store.log
598 {{endif}}
599+{{if upstream_peer_proxy}}
600+cache_peer {{upstream_proxy_address}} parent {{upstream_proxy_port}} 0 no-query default
601+never_direct allow all
602+{{endif}}
603
604=== modified file 'src/provisioningserver/utils/service_monitor.py'
605--- src/provisioningserver/utils/service_monitor.py 2017-02-17 14:23:04 +0000
606+++ src/provisioningserver/utils/service_monitor.py 2017-06-16 13:49:45 +0000
607@@ -299,17 +299,19 @@
608
609 @asynchronous
610 @inlineCallbacks
611- def restartService(self, name):
612+ def restartService(self, name, if_on=False):
613 """Restart service.
614
615 Service will only be restarted if its expected state is ON.
616 `ServiceNotOnError` will be raised if restart is called and the
617- services expected state is not ON.
618+ services expected state is not ON, except if if_on is True.
619 """
620 service = self.getServiceByName(name)
621 expected_state, _ = yield maybeDeferred(service.getExpectedState)
622 _check_service_state_expected(expected_state)
623 if expected_state != SERVICE_STATE.ON:
624+ if if_on:
625+ return
626 raise ServiceNotOnError(
627 "Service '%s' is not expected to be on, unable to restart." % (
628 service.service_name))
629
630=== modified file 'src/provisioningserver/utils/tests/test_service_monitor.py'
631--- src/provisioningserver/utils/tests/test_service_monitor.py 2017-02-17 14:23:04 +0000
632+++ src/provisioningserver/utils/tests/test_service_monitor.py 2017-06-16 13:49:45 +0000
633@@ -378,6 +378,13 @@
634 yield service_monitor.reloadService(fake_service.name)
635
636 @inlineCallbacks
637+ def test__reloadService_returns_when_if_on(self):
638+ fake_service = make_fake_service(SERVICE_STATE.OFF)
639+ service_monitor = self.make_service_monitor([fake_service])
640+ yield service_monitor.restartService(fake_service.name, if_on=True)
641+ # No exception expected.
642+
643+ @inlineCallbacks
644 def test__reloadService_calls_ensureService_then_reloads(self):
645 fake_service = make_fake_service(SERVICE_STATE.ON)
646 service_monitor = self.make_service_monitor([fake_service])