Merge ~nick-moffitt/ubuntu-mirror-charm:xinetd into ubuntu-mirror-charm:master

Proposed by Nick Moffitt
Status: Merged
Approved by: Nick Moffitt
Approved revision: 6d5b0fb890e915e3afec960f0d98f9edcfbc6cc4
Merged at revision: 80397809817f1fb5844c463e72e504f03e520274
Proposed branch: ~nick-moffitt/ubuntu-mirror-charm:xinetd
Merge into: ubuntu-mirror-charm:master
Diff against target: 224 lines (+139/-6)
6 files modified
config.yaml (+4/-0)
hooks/Config.py (+3/-0)
hooks/hooks.py (+8/-6)
templates/rsync-default.tmpl (+4/-0)
templates/rsync-xinetd.tmpl (+12/-0)
tests/unit/test_charm.py (+108/-0)
Reviewer Review Type Date Requested Status
Tom Haddon Approve
David Lawson (community) Approve
Review via email: mp+394826@code.launchpad.net

Commit message

Allow rsync on xinetd instead of systemd RT#128036

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Nick Moffitt (nick-moffitt) wrote :

Most of this comes from https://git.launchpad.net/ubuntu-mirror-charm/commit/?id=32ee49e93ef57fb0af74b6020eb7f12fc7903889 but I have added a flag to let you switch to xinetd on post-15.04 systems.

Revision history for this message
David Lawson (deej) wrote :

LGTM

review: Approve
Revision history for this message
Tom Haddon (mthaddon) wrote :

We've discussed some re-ordering and adding unit test coverage for this change, so I'll take another look once you've had a chance to address those.

Revision history for this message
Tom Haddon (mthaddon) wrote :

LGTM, thx

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision 80397809817f1fb5844c463e72e504f03e520274

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/config.yaml b/config.yaml
2index dccc75f..f2b4616 100644
3--- a/config.yaml
4+++ b/config.yaml
5@@ -668,6 +668,10 @@ options:
6 default: "/var/log/rsyncd"
7 type: string
8 description: "Rsync log directory"
9+ rsync_use_xinetd:
10+ default: false
11+ type: boolean
12+ description: "Whether to launch rsyncd from xinetd (if true) or systemd (if false)."
13 apache_logdir:
14 default: "/var/log/apache2"
15 type: string
16diff --git a/hooks/Config.py b/hooks/Config.py
17index 95c1ff2..03f840a 100755
18--- a/hooks/Config.py
19+++ b/hooks/Config.py
20@@ -281,6 +281,9 @@ class Config:
21 def rsyncd_dir(self):
22 return "/etc/rsync-juju.d"
23
24+ def rsync_use_xinetd(self):
25+ return config("rsync_use_xinetd")
26+
27 def script_dir(self):
28 return str(config("script_dir"))
29
30diff --git a/hooks/hooks.py b/hooks/hooks.py
31index 5bf370c..f5f9339 100755
32--- a/hooks/hooks.py
33+++ b/hooks/hooks.py
34@@ -422,11 +422,7 @@ def configure_rsync_server(conf, hostname):
35
36 # Write the motd file
37 mkdir(os.path.dirname(motdfile))
38- if os.path.isfile(motdfile):
39- os.unlink(motdfile)
40- with open(motdfile, "w") as f:
41- f.write(conf.rsync_motd())
42- os.chmod(motdfile, 0o444)
43+ write_file(motdfile, conf.rsync_motd(), perms=0o444)
44
45 # Make sure the logdir exists
46 mkdir(tmpl_data["logdir"])
47@@ -437,9 +433,13 @@ def configure_rsync_server(conf, hostname):
48 file_from_template("rsync-base-cfg.tmpl", "/etc/rsyncd.conf", tmpl_data)
49 file_from_template("rsync-mirrors-cfg.tmpl", os.path.join(conf.rsyncd_dir(), "010-ubuntu-mirror.conf"), tmpl_data)
50
51+ # xinetd is opt-in, now.
52+ if conf.rsync_use_xinetd():
53+ file_from_template("rsync-default.tmpl", "/etc/default/rsync", tmpl_data)
54+ file_from_template("rsync-xinetd.tmpl", "/etc/xinetd.d/rsync", tmpl_data)
55 # Need to tell systemd to enable rsync and then start it
56 # but only in 15.04 or greater per https://wiki.ubuntu.com/SystemdForUpstartUsers
57- if float(platform.linux_distribution()[1]) >= 15.04:
58+ elif float(platform.linux_distribution()[1]) >= 15.04:
59 # We disable rsync and run rsync over systemd sockets to allow limiting
60 # connections per source/IP
61 check_call('systemctl disable rsync'.split())
62@@ -453,6 +453,8 @@ def configure_rsync_server(conf, hostname):
63 check_call('systemctl daemon-reload'.split())
64 check_call('systemctl enable rsyncd.socket'.split())
65 check_call('systemctl start rsyncd.socket'.split())
66+ # pre-15.04 installations will need to explicitly turn on xinetd,
67+ # or continue with previous upstart behaviours.
68
69 log("CHARM: Finished configuring rsync server")
70
71diff --git a/templates/rsync-default.tmpl b/templates/rsync-default.tmpl
72new file mode 100644
73index 0000000..0cf560c
74--- /dev/null
75+++ b/templates/rsync-default.tmpl
76@@ -0,0 +1,4 @@
77+RSYNC_ENABLE=xinetd
78+RSYNC_CONFIG_FILE=/etc/rsyncd.conf
79+RSYNC_OPTS=''
80+RSYNC_NICE=''
81diff --git a/templates/rsync-xinetd.tmpl b/templates/rsync-xinetd.tmpl
82new file mode 100644
83index 0000000..3910ca9
84--- /dev/null
85+++ b/templates/rsync-xinetd.tmpl
86@@ -0,0 +1,12 @@
87+service rsync
88+{
89+ socket_type = stream
90+ bind = ::
91+ wait = no
92+ user = root
93+ nice = 5
94+ server = /usr/bin/rsync
95+ server_args = --daemon
96+ instances = ${max_connections}
97+ per_source = ${max_connections_per_source}
98+}
99diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
100index 1622eb1..b92bd34 100644
101--- a/tests/unit/test_charm.py
102+++ b/tests/unit/test_charm.py
103@@ -80,6 +80,24 @@ MERGED_TRIGGERS = {
104
105 MERGEABLE_TRIGGERS_SSH_AUTHORIZED_KEYS = 'no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="( /srv/ubuntu-mirror/bin/mirror-1stage.sh releases ; /srv/ubuntu-mirror/bin/mirror-1stage.sh cdimage ) &",from="91.189.90.151,91.189.89.127,10.22.96.61,10.22.96.161" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA7TIXsws/s48o4N1MA+TpaudHS8XVF79JujYQGTumroZZqbsr04lbg1FaJ9HyS9P31e8lP2bmB5rexJaMyfAxDTV+DtrHXmeLi/XcGSOXoliOa/j3eQmdZyYme9z+Tfp8s3S6nxuDP3BRKkOuIzOPgPO5fr8AH7y83v3fyHi+H0sbm6agX2b+xV9oP8+DEgSxPNfZEVTgpdJ7/ULwAl68mWQW0w6VTVx0CMYF+cvcorcZMGmy+THzk8WH2XRalA5HqeiBtS7vJ+yFHJ5WbQOEUeR6gHRcrogR4Yd10eFWUNAFyvt53zF7W91PEgnJWjcCUN/uctybAFSgDK1UB6TPkQ== cjwatson@little\n' # noqa: E501
106
107+XINETD_CONFIG = {
108+ 'rsync_use_xinetd': True,
109+ 'role_map': json.dumps({
110+ 'localhost': ['cdimage', 'releases'],
111+ })
112+}
113+
114+SYSTEMD_CONFIG = {
115+ 'rsync_use_xinetd': False,
116+ 'role_map': json.dumps({
117+ 'localhost': ['cdimage', 'releases'],
118+ })
119+}
120+JUJU_HEADER = """#-------------------------------------------------#
121+# This file is Juju managed - do not edit by hand #
122+#-------------------------------------------------#
123+"""
124+
125
126 class TestUbuntuMirrorCharm(unittest.TestCase):
127 def setUp(self):
128@@ -252,6 +270,96 @@ class TestUbuntuMirrorCharm(unittest.TestCase):
129 perms=0o444,
130 )
131
132+ # Cheetah warns about using the Python version of NameMapper, so
133+ # we ignore it here. Matching more closely by the message doesn't
134+ # seem to work, probably because it begins with a newline and
135+ # Python's warnings matching code anchors the message regexp.
136+ @pytest.mark.filterwarnings("ignore::UserWarning:Cheetah")
137+ @patch('hooks.charm_dir')
138+ @patch('hooks.check_call')
139+ @patch('hooks.mkdir')
140+ @patch('hooks.write_file')
141+ @patch('hooks.os.path.isfile')
142+ @patch('hooks.os.chmod')
143+ @patch('shutil.copyfile')
144+ @patch('platform.linux_distribution')
145+ @patch('Config.config')
146+ def test_configure_rsync_server(self, _config, _distro, _copyfile, _chmod,
147+ _isfile, _write_file, _mkdir, _check_call, _charm_dir):
148+ _distro.return_value = ('Ubuntu', '20.04', 'focal')
149+ _mkdir.return_value = None
150+ _isfile.return_value = False
151+ _charm_dir.return_value = "."
152+ config = CallableDict(self.default_config)
153+ config.update(MERGEABLE_TRIGGERS_CONFIG)
154+ config.update(SYSTEMD_CONFIG)
155+ _config.side_effect = config # i.e. we want our CallableDict to supply the charm config
156+ conf = Config.Config()
157+
158+ hooks.configure_rsync_server(conf, 'localhost')
159+
160+ self.assertEqual(_write_file.call_count, 4)
161+ _write_file.assert_any_call("/etc/rsyncd/motd", "This is an Ubuntu mirror - treat it kindly\n", perms=0o444)
162+ _write_file.assert_any_call("/etc/systemd/system/rsyncd.socket",
163+ JUJU_HEADER + """[Unit]
164+Description=rsync daemon (socket)
165+Conflicts=rsyncd.service
166+
167+[Socket]
168+ListenStream=873
169+Accept=yes
170+MaxConnections=65
171+MaxConnectionsPerSource=5
172+KeepAlive=true
173+
174+[Install]
175+WantedBy=sockets.target
176+""", perms=0o444)
177+
178+ _copyfile.assert_called_with("./files/rsyncd-systemd.service", "/etc/systemd/system/rsyncd@.service")
179+ _check_call.assert_called_with(['systemctl', 'start', 'rsyncd.socket'])
180+
181+ # Cheetah warns about using the Python version of NameMapper, so
182+ # we ignore it here. Matching more closely by the message doesn't
183+ # seem to work, probably because it begins with a newline and
184+ # Python's warnings matching code anchors the message regexp.
185+ @pytest.mark.filterwarnings("ignore::UserWarning:Cheetah")
186+ @patch('hooks.charm_dir')
187+ @patch('hooks.check_call')
188+ @patch('hooks.mkdir')
189+ @patch('hooks.write_file')
190+ @patch('hooks.os.path.isfile')
191+ @patch('hooks.os.chmod')
192+ @patch('Config.config')
193+ def test_xinetd_rsync_server(self, _config, _chmod, _isfile, _write_file, _mkdir, _check_call, _charm_dir):
194+ _mkdir.return_value = None
195+ _isfile.return_value = False
196+ _charm_dir.return_value = "."
197+ config = CallableDict(self.default_config)
198+ config.update(MERGEABLE_TRIGGERS_CONFIG)
199+ config.update(XINETD_CONFIG)
200+ _config.side_effect = config # i.e. we want our CallableDict to supply the charm config
201+ conf = Config.Config()
202+
203+ hooks.configure_rsync_server(conf, 'localhost')
204+ _write_file.assert_any_call("/etc/rsyncd/motd", "This is an Ubuntu mirror - treat it kindly\n", perms=0o444)
205+ self.assertEqual(_write_file.call_count, 5) # We render the two
206+ _check_call.assert_not_called() # No systemctl commands are run
207+ _write_file.assert_any_call("/etc/xinetd.d/rsync",
208+ JUJU_HEADER + """service rsync
209+{
210+ socket_type = stream
211+ bind = ::
212+ wait = no
213+ user = root
214+ nice = 5
215+ server = /usr/bin/rsync
216+ server_args = --daemon
217+ instances = 65
218+ per_source = 5
219+}
220+""", perms=0o444)
221+
222 def test_ensure_symlink(self):
223 try:
224 tmpdir = mkdtemp() # Python 2 doesn't have tempfile.TemporaryDirectory

Subscribers

People subscribed via source and target branches