Merge lp:~lamont/maas/create-maas-proxy.conf into lp:~maas-committers/maas/trunk

Proposed by LaMont Jones
Status: Merged
Approved by: LaMont Jones
Approved revision: no longer in the source branch.
Merged at revision: 4862
Proposed branch: lp:~lamont/maas/create-maas-proxy.conf
Merge into: lp:~maas-committers/maas/trunk
Prerequisite: lp:~lamont/maas/bug-1379567
Diff against target: 793 lines (+521/-14)
13 files modified
etc/maas/templates/dns/zone.template (+0/-3)
src/maas/demo.py (+3/-0)
src/maas/development.py (+4/-0)
src/maas/settings.py (+5/-0)
src/maasserver/migrations/builtin/maasserver/0048_add_subnet_allow_proxy.py (+5/-2)
src/maasserver/proxyconfig.py (+86/-0)
src/maasserver/region_controller.py (+34/-4)
src/maasserver/tests/test_proxyconfig.py (+111/-0)
src/maasserver/tests/test_region_controller.py (+96/-5)
src/maasserver/triggers/system.py (+50/-0)
src/maasserver/triggers/tests/test_system.py (+6/-0)
src/maasserver/triggers/tests/test_system_listener.py (+76/-0)
src/provisioningserver/templates/proxy/maas-proxy.conf.template (+45/-0)
To merge this branch: bzr merge lp:~lamont/maas/create-maas-proxy.conf
Reviewer Review Type Date Requested Status
LaMont Jones (community) Approve
Blake Rouse (community) Needs Fixing
Review via email: mp+290154@code.launchpad.net

Commit message

Create /var/cache/maas/maas-proxy.conf for maas-proxy to use.

Description of the change

Create /var/cache/maas/maas-proxy.conf for maas-proxy to use.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Overall this looks really good. The service reloading needs an improvement to use the serviceMonitor so the service status is kept in sync with what as known about the service.

review: Needs Fixing
Revision history for this message
LaMont Jones (lamont) wrote :

Marking this approved since merging Blake's changes that were the cause of his "needs fixing".

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'etc/maas/templates/dns/zone.template'
--- etc/maas/templates/dns/zone.template 2016-02-03 09:11:25 +0000
+++ etc/maas/templates/dns/zone.template 2016-03-31 23:44:54 +0000
@@ -1,7 +1,4 @@
1; Zone file modified: {{modified}}.1; Zone file modified: {{modified}}.
2; Note that the modification time of this file doesn't reflect
3; the actual modification time. MAAS controls the modification time
4; of this file to be able to force the zone to be reloaded by BIND.
5$TTL {{ttl}}2$TTL {{ttl}}
6@ IN SOA {{domain}}. nobody.example.com. (3@ IN SOA {{domain}}. nobody.example.com. (
7 {{serial}} ; serial4 {{serial}} ; serial
85
=== modified file 'src/maas/demo.py'
--- src/maas/demo.py 2016-03-25 14:52:23 +0000
+++ src/maas/demo.py 2016-03-31 23:44:54 +0000
@@ -27,6 +27,9 @@
27# Connect to the DHCP server. TODO: Use the signals manager instead.27# Connect to the DHCP server. TODO: Use the signals manager instead.
28DHCP_CONNECT = True28DHCP_CONNECT = True
2929
30# Connect to the PROXY server. TODO: Use the signals manager instead.
31PROXY_CONNECT = True
32
30MAAS_CLI = abspath("bin/maas-region")33MAAS_CLI = abspath("bin/maas-region")
3134
32# For demo purposes, give nodes unauthenticated access to their metadata35# For demo purposes, give nodes unauthenticated access to their metadata
3336
=== modified file 'src/maas/development.py'
--- src/maas/development.py 2016-03-28 20:03:45 +0000
+++ src/maas/development.py 2016-03-31 23:44:54 +0000
@@ -33,6 +33,10 @@
33# basis. TODO: Use the signals manager instead.33# basis. TODO: Use the signals manager instead.
34DHCP_CONNECT = False34DHCP_CONNECT = False
3535
36# Don't setup PROXY servers in tests, this will be enabled on a case per case
37# basis. TODO: Use the signals manager instead.
38PROXY_CONNECT = False
39
36# Invalid strings should be visible.40# Invalid strings should be visible.
37TEMPLATE_STRING_IF_INVALID = '#### INVALID STRING ####'41TEMPLATE_STRING_IF_INVALID = '#### INVALID STRING ####'
3842
3943
=== modified file 'src/maas/settings.py'
--- src/maas/settings.py 2016-03-25 14:52:23 +0000
+++ src/maas/settings.py 2016-03-31 23:44:54 +0000
@@ -78,6 +78,11 @@
78# machinery. TODO: Use the signals manager instead.78# machinery. TODO: Use the signals manager instead.
79DHCP_CONNECT = True79DHCP_CONNECT = True
8080
81# Should the PROXY features be enabled? Having this config option is a
82# debugging/testing feature to be able to quickly disconnect the PROXY
83# machinery. TODO: Use the signals manager instead.
84PROXY_CONNECT = True
85
81# The MAAS CLI.86# The MAAS CLI.
82MAAS_CLI = 'sudo maas-region'87MAAS_CLI = 'sudo maas-region'
8388
8489
=== renamed file 'src/maasserver/migrations/builtin/maasserver/0046_add_subnet_allow_proxy.py' => 'src/maasserver/migrations/builtin/maasserver/0048_add_subnet_allow_proxy.py'
--- src/maasserver/migrations/builtin/maasserver/0046_add_subnet_allow_proxy.py 2016-03-31 23:44:54 +0000
+++ src/maasserver/migrations/builtin/maasserver/0048_add_subnet_allow_proxy.py 2016-03-31 23:44:54 +0000
@@ -1,13 +1,16 @@
1# -*- coding: utf-8 -*-1# -*- coding: utf-8 -*-
2from __future__ import unicode_literals2from __future__ import unicode_literals
33
4from django.db import migrations, models4from django.db import (
5 migrations,
6 models,
7)
58
69
7class Migration(migrations.Migration):10class Migration(migrations.Migration):
811
9 dependencies = [12 dependencies = [
10 ('maasserver', '0045_add_node_to_filesystem'),13 ('maasserver', '0047_fix_spelling_of_degraded'),
11 ]14 ]
1215
13 operations = [16 operations = [
1417
=== added file 'src/maasserver/proxyconfig.py'
--- src/maasserver/proxyconfig.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/proxyconfig.py 2016-03-31 23:44:54 +0000
@@ -0,0 +1,86 @@
1# Copyright 2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Proxy config management module."""
5
6__all__ = [
7 'proxy_update_config',
8 ]
9
10import datetime
11import os
12import socket
13import sys
14
15from django.conf import settings
16from maasserver.models.subnet import Subnet
17from maasserver.service_monitor import service_monitor
18from maasserver.utils.orm import transactional
19from maasserver.utils.threads import deferToDatabase
20from provisioningserver.logger import get_maas_logger
21from provisioningserver.utils import locate_template
22from provisioningserver.utils.fs import atomic_write
23from provisioningserver.utils.twisted import asynchronous
24import tempita
25from twisted.internet.defer import succeed
26
27
28maaslog = get_maas_logger("dns")
29MAAS_PROXY_CONF_NAME = 'maas-proxy.conf'
30MAAS_PROXY_CONF_TEMPLATE = 'maas-proxy.conf.template'
31
32
33def is_proxy_enabled():
34 """Is MAAS configured to manage the PROXY?"""
35 return settings.PROXY_CONNECT
36
37
38class ProxyConfigFail(Exception):
39 """Raised if there is a problem with the proxy configuration."""
40
41
42def get_proxy_config_dir():
43 """Location of bind configuration files."""
44 setting = os.getenv("MAAS_PROXY_CONFIG_DIR", "/var/lib/maas")
45 if isinstance(setting, bytes):
46 fsenc = sys.getfilesystemencoding()
47 return setting.decode(fsenc)
48 else:
49 return setting
50
51
52@asynchronous
53def proxy_update_config(reload_proxy=True):
54 """Regenerate the proxy configuration file."""
55
56 @transactional
57 def write_config():
58 allowed_subnets = Subnet.objects.filter(allow_proxy=True)
59 cidrs = [subnet.cidr for subnet in allowed_subnets]
60 context = {
61 'allowed': allowed_subnets,
62 'modified': str(datetime.date.today()),
63 'fqdn': socket.getfqdn(),
64 'cidrs': cidrs,
65 }
66 template_path = locate_template('proxy', MAAS_PROXY_CONF_TEMPLATE)
67 template = tempita.Template.from_filename(
68 template_path, encoding="UTF-8")
69 try:
70 content = template.substitute(context)
71 except NameError as error:
72 raise ProxyConfigFail(*error.args)
73 # Squid prefers ascii.
74 content = content.encode("ascii")
75 target_path = os.sep.join(
76 [get_proxy_config_dir(), MAAS_PROXY_CONF_NAME])
77 atomic_write(content, target_path, overwrite=True, mode=0o644)
78
79 if is_proxy_enabled():
80 d = deferToDatabase(write_config)
81 if reload_proxy:
82 d.addCallback(
83 lambda _: service_monitor.reloadService("proxy", if_on=True))
84 return d
85 else:
86 return succeed(None)
087
=== modified file 'src/maasserver/region_controller.py'
--- src/maasserver/region_controller.py 2016-03-28 20:03:45 +0000
+++ src/maasserver/region_controller.py 2016-03-31 23:44:54 +0000
@@ -11,6 +11,12 @@
11 'sys_dns'. Any time a message is recieved on that channel the DNS is marked11 'sys_dns'. Any time a message is recieved on that channel the DNS is marked
12 as requiring an update. Once marked for update the DNS configuration is12 as requiring an update. Once marked for update the DNS configuration is
13 updated and bind9 is told to reload.13 updated and bind9 is told to reload.
14
15Proxy:
16 The regiond process listens for messages from Postgres on channel
17 'sys_proxy'. Any time a message is recieved on that channel the maas-proxy
18 is marked as requiring an update. Once marked for update the proxy
19 configuration is updated and maas-proxy is told to reload.
14"""20"""
1521
16__all__ = [22__all__ = [
@@ -18,6 +24,7 @@
18]24]
1925
20from maasserver.dns.config import dns_update_all_zones26from maasserver.dns.config import dns_update_all_zones
27from maasserver.proxyconfig import proxy_update_config
21from maasserver.utils.orm import transactional28from maasserver.utils.orm import transactional
22from maasserver.utils.threads import deferToDatabase29from maasserver.utils.threads import deferToDatabase
23from provisioningserver.utils.twisted import (30from provisioningserver.utils.twisted import (
@@ -26,6 +33,7 @@
26)33)
27from twisted.application.service import Service34from twisted.application.service import Service
28from twisted.internet import reactor35from twisted.internet import reactor
36from twisted.internet.defer import DeferredList
29from twisted.internet.task import LoopingCall37from twisted.internet.task import LoopingCall
30from twisted.python import log38from twisted.python import log
3139
@@ -51,6 +59,7 @@
51 self.processing.clock = self.clock59 self.processing.clock = self.clock
52 self.processingDefer = None60 self.processingDefer = None
53 self.needsDNSUpdate = False61 self.needsDNSUpdate = False
62 self.needsProxyUpdate = False
54 self.postgresListener = postgresListener63 self.postgresListener = postgresListener
5564
56 @asynchronous(timeout=FOREVER)65 @asynchronous(timeout=FOREVER)
@@ -58,15 +67,18 @@
58 """Start listening for messages."""67 """Start listening for messages."""
59 super(RegionControllerService, self).startService()68 super(RegionControllerService, self).startService()
60 self.postgresListener.register("sys_dns", self.markDNSForUpdate)69 self.postgresListener.register("sys_dns", self.markDNSForUpdate)
70 self.postgresListener.register("sys_proxy", self.markProxyForUpdate)
6171
62 # Update DNS on first start.72 # Update DNS and proxy on first start.
63 self.markDNSForUpdate(None, None)73 self.markDNSForUpdate(None, None)
74 self.markProxyForUpdate(None, None)
6475
65 @asynchronous(timeout=FOREVER)76 @asynchronous(timeout=FOREVER)
66 def stopService(self):77 def stopService(self):
67 """Close the controller."""78 """Close the controller."""
68 super(RegionControllerService, self).stopService()79 super(RegionControllerService, self).stopService()
69 self.postgresListener.unregister("sys_dns", self.markDNSForUpdate)80 self.postgresListener.unregister("sys_dns", self.markDNSForUpdate)
81 self.postgresListener.unregister("sys_proxy", self.markProxyForUpdate)
70 if self.processingDefer is not None:82 if self.processingDefer is not None:
71 self.processingDefer, d = None, self.processingDefer83 self.processingDefer, d = None, self.processingDefer
72 self.processing.stop()84 self.processing.stop()
@@ -77,13 +89,19 @@
77 self.needsDNSUpdate = True89 self.needsDNSUpdate = True
78 self.startProcessing()90 self.startProcessing()
7991
92 def markProxyForUpdate(self, channel, message):
93 """Called when the `sys_proxy` message is received."""
94 self.needsProxyUpdate = True
95 self.startProcessing()
96
80 def startProcessing(self):97 def startProcessing(self):
81 """Start the process looping call."""98 """Start the process looping call."""
82 if not self.processing.running:99 if not self.processing.running:
83 self.processingDefer = self.processing.start(0.1, now=False)100 self.processingDefer = self.processing.start(0.1, now=False)
84101
85 def process(self):102 def process(self):
86 """Process the DNS update."""103 """Process the DNS and/or proxy update."""
104 defers = []
87 if self.needsDNSUpdate:105 if self.needsDNSUpdate:
88 self.needsDNSUpdate = False106 self.needsDNSUpdate = False
89 d = deferToDatabase(transactional(dns_update_all_zones))107 d = deferToDatabase(transactional(dns_update_all_zones))
@@ -93,8 +111,20 @@
93 d.addErrback(111 d.addErrback(
94 log.err,112 log.err,
95 "Failed configuring DNS.")113 "Failed configuring DNS.")
96 return d114 defers.append(d)
97 else:115 if self.needsProxyUpdate:
116 self.needsProxyUpdate = False
117 d = proxy_update_config(reload_proxy=True)
118 d.addCallback(
119 lambda _: log.msg(
120 "Successfully configured proxy."))
121 d.addErrback(
122 log.err,
123 "Failed configuring proxy.")
124 defers.append(d)
125 if len(defers) == 0:
98 # Nothing more to do.126 # Nothing more to do.
99 self.processing.stop()127 self.processing.stop()
100 self.processingDefer = None128 self.processingDefer = None
129 else:
130 return DeferredList(defers)
101131
=== added file 'src/maasserver/tests/test_proxyconfig.py'
--- src/maasserver/tests/test_proxyconfig.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/tests/test_proxyconfig.py 2016-03-31 23:44:54 +0000
@@ -0,0 +1,111 @@
1# Copyright 2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the proxyconfig."""
5
6__all__ = []
7
8import os
9
10from crochet import wait_for
11from django.conf import settings
12from fixtures import EnvironmentVariableFixture
13from maasserver import proxyconfig
14from maasserver.testing.factory import factory
15from maasserver.testing.testcase import (
16 MAASServerTestCase,
17 MAASTransactionServerTestCase,
18)
19from maasserver.utils.orm import transactional
20from maasserver.utils.threads import deferToDatabase
21from maastesting.matchers import (
22 MockCalledOnceWith,
23 MockNotCalled,
24)
25from testtools.matchers import (
26 Contains,
27 FileContains,
28 Not,
29)
30from twisted.internet.defer import inlineCallbacks
31
32
33wait_for_reactor = wait_for(30) # 30 seconds.
34
35
36class TestGetConfigDir(MAASServerTestCase):
37 """Tests for `maasserver.proxyconfig.get_proxy_config_dir`."""
38
39 def test_returns_default(self):
40 self.assertEquals(
41 "/var/lib/maas", proxyconfig.get_proxy_config_dir())
42
43 def test_env_overrides_default(self):
44 os.environ['MAAS_PROXY_CONFIG_DIR'] = factory.make_name('env')
45 self.assertEquals(
46 os.environ['MAAS_PROXY_CONFIG_DIR'],
47 proxyconfig.get_proxy_config_dir())
48 del(os.environ['MAAS_PROXY_CONFIG_DIR'])
49
50
51class TestProxyUpdateConfig(MAASTransactionServerTestCase):
52 """Tests for `maasserver.proxyconfig`."""
53
54 def setUp(self):
55 super(TestProxyUpdateConfig, self).setUp()
56 self.tmpdir = self.make_dir()
57 self.service_monitor = self.patch(proxyconfig, "service_monitor")
58 self.useFixture(
59 EnvironmentVariableFixture('MAAS_PROXY_CONFIG_DIR', self.tmpdir))
60
61 @transactional
62 def make_subnet(self, allow_proxy=True):
63 return factory.make_Subnet(allow_proxy=allow_proxy)
64
65 @wait_for_reactor
66 @inlineCallbacks
67 def test__only_enabled_subnets_are_present(self):
68 self.patch(settings, "PROXY_CONNECT", True)
69 disabled = yield deferToDatabase(self.make_subnet, allow_proxy=False)
70 enabled = yield deferToDatabase(self.make_subnet)
71 yield proxyconfig.proxy_update_config(reload_proxy=False)
72 # enabled's cidr must be present
73 matcher = Contains("acl localnet src %s" % enabled.cidr)
74 self.assertThat(
75 "%s/%s" % (self.tmpdir, proxyconfig.MAAS_PROXY_CONF_NAME),
76 FileContains(matcher=matcher))
77 # disabled's cidr must not be present
78 matcher = Not(Contains("acl localnet src %s" % disabled.cidr))
79 self.assertThat(
80 "%s/%s" % (self.tmpdir, proxyconfig.MAAS_PROXY_CONF_NAME),
81 FileContains(matcher=matcher))
82
83 @wait_for_reactor
84 @inlineCallbacks
85 def test__calls_reloadService(self):
86 self.patch(settings, "PROXY_CONNECT", True)
87 yield deferToDatabase(self.make_subnet)
88 yield proxyconfig.proxy_update_config()
89 self.assertThat(
90 self.service_monitor.reloadService,
91 MockCalledOnceWith("proxy", if_on=True))
92
93 @wait_for_reactor
94 @inlineCallbacks
95 def test__doesnt_call_reloadService_when_PROXY_CONNECT_False(self):
96 self.patch(settings, "PROXY_CONNECT", False)
97 yield deferToDatabase(self.make_subnet)
98 yield proxyconfig.proxy_update_config()
99 self.assertThat(
100 self.service_monitor.reloadService,
101 MockNotCalled())
102
103 @wait_for_reactor
104 @inlineCallbacks
105 def test__doesnt_call_reloadService_when_reload_proxy_False(self):
106 self.patch(settings, "PROXY_CONNECT", True)
107 yield deferToDatabase(self.make_subnet)
108 yield proxyconfig.proxy_update_config(reload_proxy=False)
109 self.assertThat(
110 self.service_monitor.reloadService,
111 MockNotCalled())
0112
=== modified file 'src/maasserver/tests/test_region_controller.py'
--- src/maasserver/tests/test_region_controller.py 2016-03-28 20:03:45 +0000
+++ src/maasserver/tests/test_region_controller.py 2016-03-31 23:44:54 +0000
@@ -12,16 +12,22 @@
12from maasserver.testing.testcase import MAASServerTestCase12from maasserver.testing.testcase import MAASServerTestCase
13from maastesting.matchers import (13from maastesting.matchers import (
14 MockCalledOnceWith,14 MockCalledOnceWith,
15 MockCallsMatch,
15 MockNotCalled,16 MockNotCalled,
16)17)
17from mock import (18from mock import (
18 ANY,19 ANY,
20 call,
19 MagicMock,21 MagicMock,
20 sentinel,22 sentinel,
21)23)
22from testtools.matchers import MatchesStructure24from testtools.matchers import MatchesStructure
23from twisted.internet import reactor25from twisted.internet import reactor
24from twisted.internet.defer import inlineCallbacks26from twisted.internet.defer import (
27 fail,
28 inlineCallbacks,
29 succeed,
30)
2531
2632
27wait_for_reactor = wait_for(30) # 30 seconds.33wait_for_reactor = wait_for(30) # 30 seconds.
@@ -45,22 +51,34 @@
45 service.startService()51 service.startService()
46 self.assertThat(52 self.assertThat(
47 listener.register,53 listener.register,
48 MockCalledOnceWith("sys_dns", service.markDNSForUpdate))54 MockCallsMatch(
55 call("sys_dns", service.markDNSForUpdate),
56 call("sys_proxy", service.markProxyForUpdate)))
4957
50 def test_startService_sets_needsDNSUpdate_calls_startProcessing(self):58 def test_startService_calls_markDNSForUpdate(self):
51 listener = MagicMock()59 listener = MagicMock()
52 service = RegionControllerService(listener)60 service = RegionControllerService(listener)
53 mock_markDNSForUpdate = self.patch(service, "markDNSForUpdate")61 mock_markDNSForUpdate = self.patch(service, "markDNSForUpdate")
54 service.startService()62 service.startService()
55 self.assertThat(mock_markDNSForUpdate, MockCalledOnceWith(None, None))63 self.assertThat(mock_markDNSForUpdate, MockCalledOnceWith(None, None))
5664
65 def test_startService_calls_markProxyForUpdate(self):
66 listener = MagicMock()
67 service = RegionControllerService(listener)
68 mock_markProxyForUpdate = self.patch(service, "markProxyForUpdate")
69 service.startService()
70 self.assertThat(
71 mock_markProxyForUpdate, MockCalledOnceWith(None, None))
72
57 def test_stopService_calls_unregister_on_the_listener(self):73 def test_stopService_calls_unregister_on_the_listener(self):
58 listener = MagicMock()74 listener = MagicMock()
59 service = RegionControllerService(listener)75 service = RegionControllerService(listener)
60 service.stopService()76 service.stopService()
61 self.assertThat(77 self.assertThat(
62 listener.unregister,78 listener.unregister,
63 MockCalledOnceWith("sys_dns", service.markDNSForUpdate))79 MockCallsMatch(
80 call("sys_dns", service.markDNSForUpdate),
81 call("sys_proxy", service.markProxyForUpdate)))
6482
65 @wait_for_reactor83 @wait_for_reactor
66 @inlineCallbacks84 @inlineCallbacks
@@ -71,7 +89,7 @@
71 yield service.stopService()89 yield service.stopService()
72 self.assertIsNone(service.processingDefer)90 self.assertIsNone(service.processingDefer)
7391
74 def test_markDNSForUpdate_sets_needsDNSUpdate_and_starts_processing(self):92 def test_markDNSForUpdate_sets_needsDNSUpdate_and_starts_process(self):
75 listener = MagicMock()93 listener = MagicMock()
76 service = RegionControllerService(listener)94 service = RegionControllerService(listener)
77 mock_startProcessing = self.patch(service, "startProcessing")95 mock_startProcessing = self.patch(service, "startProcessing")
@@ -79,6 +97,14 @@
79 self.assertTrue(service.needsDNSUpdate)97 self.assertTrue(service.needsDNSUpdate)
80 self.assertThat(mock_startProcessing, MockCalledOnceWith())98 self.assertThat(mock_startProcessing, MockCalledOnceWith())
8199
100 def test_markProxyForUpdate_sets_needsProxyUpdate_and_starts_process(self):
101 listener = MagicMock()
102 service = RegionControllerService(listener)
103 mock_startProcessing = self.patch(service, "startProcessing")
104 service.markProxyForUpdate(None, None)
105 self.assertTrue(service.needsProxyUpdate)
106 self.assertThat(mock_startProcessing, MockCalledOnceWith())
107
82 def test_startProcessing_doesnt_call_start_when_looping_call_running(self):108 def test_startProcessing_doesnt_call_start_when_looping_call_running(self):
83 service = RegionControllerService(sentinel.listener)109 service = RegionControllerService(sentinel.listener)
84 mock_start = self.patch(service.processing, "start")110 mock_start = self.patch(service.processing, "start")
@@ -107,6 +133,17 @@
107133
108 @wait_for_reactor134 @wait_for_reactor
109 @inlineCallbacks135 @inlineCallbacks
136 def test_process_doesnt_proxy_update_config_when_nothing_to_process(self):
137 service = RegionControllerService(sentinel.listener)
138 service.needsProxyUpdate = False
139 mock_proxy_update_config = self.patch(
140 region_controller, "proxy_update_config")
141 service.startProcessing()
142 yield service.processingDefer
143 self.assertThat(mock_proxy_update_config, MockNotCalled())
144
145 @wait_for_reactor
146 @inlineCallbacks
110 def test_process_stops_processing(self):147 def test_process_stops_processing(self):
111 service = RegionControllerService(sentinel.listener)148 service = RegionControllerService(sentinel.listener)
112 service.needsDNSUpdate = False149 service.needsDNSUpdate = False
@@ -132,6 +169,24 @@
132169
133 @wait_for_reactor170 @wait_for_reactor
134 @inlineCallbacks171 @inlineCallbacks
172 def test_process_updates_proxy(self):
173 service = RegionControllerService(sentinel.listener)
174 service.needsProxyUpdate = True
175 mock_proxy_update_config = self.patch(
176 region_controller, "proxy_update_config")
177 mock_proxy_update_config.return_value = succeed(None)
178 mock_msg = self.patch(
179 region_controller.log, "msg")
180 service.startProcessing()
181 yield service.processingDefer
182 self.assertThat(
183 mock_proxy_update_config, MockCalledOnceWith(reload_proxy=True))
184 self.assertThat(
185 mock_msg,
186 MockCalledOnceWith("Successfully configured proxy."))
187
188 @wait_for_reactor
189 @inlineCallbacks
135 def test_process_updates_zones_logs_failure(self):190 def test_process_updates_zones_logs_failure(self):
136 service = RegionControllerService(sentinel.listener)191 service = RegionControllerService(sentinel.listener)
137 service.needsDNSUpdate = True192 service.needsDNSUpdate = True
@@ -146,3 +201,39 @@
146 self.assertThat(201 self.assertThat(
147 mock_err,202 mock_err,
148 MockCalledOnceWith(ANY, "Failed configuring DNS."))203 MockCalledOnceWith(ANY, "Failed configuring DNS."))
204
205 @wait_for_reactor
206 @inlineCallbacks
207 def test_process_updates_proxy_logs_failure(self):
208 service = RegionControllerService(sentinel.listener)
209 service.needsProxyUpdate = True
210 mock_proxy_update_config = self.patch(
211 region_controller, "proxy_update_config")
212 mock_proxy_update_config.return_value = fail(factory.make_exception())
213 mock_err = self.patch(
214 region_controller.log, "err")
215 service.startProcessing()
216 yield service.processingDefer
217 self.assertThat(
218 mock_proxy_update_config, MockCalledOnceWith(reload_proxy=True))
219 self.assertThat(
220 mock_err,
221 MockCalledOnceWith(ANY, "Failed configuring proxy."))
222
223 @wait_for_reactor
224 @inlineCallbacks
225 def test_process_updates_bind_and_proxy(self):
226 service = RegionControllerService(sentinel.listener)
227 service.needsDNSUpdate = True
228 service.needsProxyUpdate = True
229 mock_dns_update_all_zones = self.patch(
230 region_controller, "dns_update_all_zones")
231 mock_proxy_update_config = self.patch(
232 region_controller, "proxy_update_config")
233 mock_proxy_update_config.return_value = succeed(None)
234 service.startProcessing()
235 yield service.processingDefer
236 self.assertThat(
237 mock_dns_update_all_zones, MockCalledOnceWith())
238 self.assertThat(
239 mock_proxy_update_config, MockCalledOnceWith(reload_proxy=True))
149240
=== modified file 'src/maasserver/triggers/system.py'
--- src/maasserver/triggers/system.py 2016-03-30 13:57:14 +0000
+++ src/maasserver/triggers/system.py 2016-03-31 23:44:54 +0000
@@ -880,6 +880,21 @@
880 """)880 """)
881881
882882
883# Triggered when a subnet is updated. Increments notifies that proxy needs to
884# be updated. Only watches changes on the cidr and allow_proxy.
885PROXY_SUBNET_UPDATE = dedent("""\
886 CREATE OR REPLACE FUNCTION sys_proxy_subnet_update()
887 RETURNS trigger as $$
888 BEGIN
889 IF OLD.cidr != NEW.cidr OR OLD.allow_proxy != NEW.allow_proxy THEN
890 PERFORM pg_notify('sys_proxy', '');
891 END IF;
892 RETURN NEW;
893 END;
894 $$ LANGUAGE plpgsql;
895 """)
896
897
883def render_sys_dns_procedure(proc_name, on_delete=False):898def render_sys_dns_procedure(proc_name, on_delete=False):
884 """Render a database procedure with name `proc_name` that increments899 """Render a database procedure with name `proc_name` that increments
885 the zone serial and notifies that a DNS update is needed.900 the zone serial and notifies that a DNS update is needed.
@@ -899,6 +914,23 @@
899 """ % (proc_name, 'NEW' if not on_delete else 'OLD'))914 """ % (proc_name, 'NEW' if not on_delete else 'OLD'))
900915
901916
917def render_sys_proxy_procedure(proc_name, on_delete=False):
918 """Render a database procedure with name `proc_name` that notifies that a
919 proxy update is needed.
920
921 :param proc_name: Name of the procedure.
922 :param on_delete: True when procedure will be used as a delete trigger.
923 """
924 return dedent("""\
925 CREATE OR REPLACE FUNCTION %s() RETURNS trigger AS $$
926 BEGIN
927 PERFORM pg_notify('sys_proxy', '');
928 RETURN %s;
929 END;
930 $$ LANGUAGE plpgsql;
931 """ % (proc_name, 'NEW' if not on_delete else 'OLD'))
932
933
902@transactional934@transactional
903def register_system_triggers():935def register_system_triggers():
904 """Register all system triggers into the database."""936 """Register all system triggers into the database."""
@@ -1115,3 +1147,21 @@
1115 "maasserver_config", "sys_dns_config_insert", "insert")1147 "maasserver_config", "sys_dns_config_insert", "insert")
1116 register_trigger(1148 register_trigger(
1117 "maasserver_config", "sys_dns_config_update", "update")1149 "maasserver_config", "sys_dns_config_update", "update")
1150
1151 # Proxy
1152
1153 ## Subnet
1154 register_procedure(
1155 render_sys_proxy_procedure("sys_proxy_subnet_insert"))
1156 register_trigger(
1157 "maasserver_subnet",
1158 "sys_proxy_subnet_insert", "insert")
1159 register_procedure(PROXY_SUBNET_UPDATE)
1160 register_trigger(
1161 "maasserver_subnet",
1162 "sys_proxy_subnet_update", "update")
1163 register_procedure(
1164 render_sys_proxy_procedure("sys_proxy_subnet_delete", on_delete=True))
1165 register_trigger(
1166 "maasserver_subnet",
1167 "sys_proxy_subnet_delete", "delete")
11181168
=== modified file 'src/maasserver/triggers/tests/test_system.py'
--- src/maasserver/triggers/tests/test_system.py 2016-03-29 20:35:17 +0000
+++ src/maasserver/triggers/tests/test_system.py 2016-03-31 23:44:54 +0000
@@ -50,11 +50,17 @@
50 "dnsdata_sys_dns_dnsdata_insert",50 "dnsdata_sys_dns_dnsdata_insert",
51 "dnsdata_sys_dns_dnsdata_update",51 "dnsdata_sys_dns_dnsdata_update",
52 "dnsdata_sys_dns_dnsdata_delete",52 "dnsdata_sys_dns_dnsdata_delete",
53 "subnet_sys_dns_subnet_insert",
54 "subnet_sys_dns_subnet_update",
55 "subnet_sys_dns_subnet_delete",
53 "node_sys_dns_node_update",56 "node_sys_dns_node_update",
54 "node_sys_dns_node_delete",57 "node_sys_dns_node_delete",
55 "interface_sys_dns_interface_update",58 "interface_sys_dns_interface_update",
56 "config_sys_dns_config_insert",59 "config_sys_dns_config_insert",
57 "config_sys_dns_config_update",60 "config_sys_dns_config_update",
61 "subnet_sys_proxy_subnet_insert",
62 "subnet_sys_proxy_subnet_update",
63 "subnet_sys_proxy_subnet_delete",
58 ]64 ]
59 sql, args = psql_array(triggers, sql_type="text")65 sql, args = psql_array(triggers, sql_type="text")
60 with closing(connection.cursor()) as cursor:66 with closing(connection.cursor()) as cursor:
6167
=== modified file 'src/maasserver/triggers/tests/test_system_listener.py'
--- src/maasserver/triggers/tests/test_system_listener.py 2016-03-30 13:57:14 +0000
+++ src/maasserver/triggers/tests/test_system_listener.py 2016-03-31 23:44:54 +0000
@@ -2966,3 +2966,79 @@
2966 yield self.assertZoneSerialIncrement(zone_serial)2966 yield self.assertZoneSerialIncrement(zone_serial)
2967 finally:2967 finally:
2968 yield listener.stopService()2968 yield listener.stopService()
2969
2970
2971class TestProxySubnetListener(
2972 MAASTransactionServerTestCase, TransactionalHelpersMixin):
2973 """End-to-end test for the proxy triggers code."""
2974
2975 @wait_for_reactor
2976 @inlineCallbacks
2977 def test_sends_message_for_subnet_insert(self):
2978 yield deferToDatabase(register_system_triggers)
2979 dv = DeferredValue()
2980 listener = self.make_listener_without_delay()
2981 listener.register(
2982 "sys_proxy", lambda *args: dv.set(args))
2983 yield listener.startService()
2984 try:
2985 yield deferToDatabase(self.create_subnet)
2986 yield dv.get(timeout=2)
2987 finally:
2988 yield listener.stopService()
2989
2990 @wait_for_reactor
2991 @inlineCallbacks
2992 def test_sends_message_for_subnet_cidr_update(self):
2993 yield deferToDatabase(register_system_triggers)
2994 subnet = yield deferToDatabase(self.create_subnet)
2995 dv = DeferredValue()
2996 listener = self.make_listener_without_delay()
2997 listener.register(
2998 "sys_proxy", lambda *args: dv.set(args))
2999 yield listener.startService()
3000 try:
3001 network = factory.make_ip4_or_6_network()
3002 yield deferToDatabase(self.update_subnet, subnet.id, {
3003 "cidr": str(network.cidr),
3004 "gateway_ip": factory.pick_ip_in_network(network),
3005 "dns_servers": [],
3006 })
3007 yield dv.get(timeout=2)
3008 finally:
3009 yield listener.stopService()
3010
3011 @wait_for_reactor
3012 @inlineCallbacks
3013 def test_sends_message_for_subnet_allow_proxy_update(self):
3014 yield deferToDatabase(register_system_triggers)
3015 subnet = yield deferToDatabase(
3016 self.create_subnet, {"allow_proxy": False})
3017 dv = DeferredValue()
3018 listener = self.make_listener_without_delay()
3019 listener.register(
3020 "sys_proxy", lambda *args: dv.set(args))
3021 yield listener.startService()
3022 try:
3023 yield deferToDatabase(self.update_subnet, subnet.id, {
3024 "allow_proxy": True,
3025 })
3026 yield dv.get(timeout=2)
3027 finally:
3028 yield listener.stopService()
3029
3030 @wait_for_reactor
3031 @inlineCallbacks
3032 def test_sends_message_for_subnet_delete(self):
3033 yield deferToDatabase(register_system_triggers)
3034 subnet = yield deferToDatabase(self.create_subnet)
3035 dv = DeferredValue()
3036 listener = self.make_listener_without_delay()
3037 listener.register(
3038 "sys_proxy", lambda *args: dv.set(args))
3039 yield listener.startService()
3040 try:
3041 yield deferToDatabase(self.delete_subnet, subnet.id)
3042 yield dv.get(timeout=2)
3043 finally:
3044 yield listener.stopService()
29693045
=== added directory 'src/provisioningserver/templates/proxy'
=== added file 'src/provisioningserver/templates/proxy/maas-proxy.conf.template'
--- src/provisioningserver/templates/proxy/maas-proxy.conf.template 1970-01-01 00:00:00 +0000
+++ src/provisioningserver/templates/proxy/maas-proxy.conf.template 2016-03-31 23:44:54 +0000
@@ -0,0 +1,45 @@
1# DO NOT EDIT. This file is automatically created by MAAS.
2# Last updated at {{modified}}.
3
4# Inspired by UDS's conference proxy
5
6acl maas_proxy_manager proto cache_object
7acl localhost src 127.0.0.1/32 ::1
8acl to_localhost dst 127.0.0.0/8 0.0.0.0/32 ::1
9{{for cidr in cidrs}}
10acl localnet src {{cidr}}
11{{endfor}}
12acl SSL_ports port 443
13acl Safe_ports port 80 # http
14acl Safe_ports port 21 # ftp
15acl Safe_ports port 443 # https
16acl Safe_ports port 1025-65535 # unregistered ports
17acl CONNECT method CONNECT
18http_access allow maas_proxy_manager localhost
19http_access deny maas_proxy_manager
20http_access deny !Safe_ports
21http_access deny CONNECT !SSL_ports
22http_access allow localnet
23http_access allow localhost
24http_access deny all
25http_port 3128 transparent
26http_port 8000
27coredump_dir /var/spool/maas-proxy
28refresh_pattern ^ftp: 1440 20% 10080
29refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
30refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims
31refresh_pattern \/InRelease$ 0 0% 0 refresh-ims
32refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
33refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims
34refresh_pattern . 0 20% 4320
35forwarded_for delete
36visible_hostname {{fqdn}}
37cache_mem 512 MB
38minimum_object_size 0 MB
39maximum_object_size 1024 MB
40maximum_object_size_in_memory 100 MB
41cache_dir aufs /var/spool/maas-proxy 40000 16 256
42# use different logs
43cache_access_log /var/log/maas/proxy/access.log
44cache_log /var/log/maas/proxy/cache.log
45cache_store_log /var/log/maas/proxy/store.log