]>
git.ipfire.org Git - thirdparty/systemd.git/blob - test/networkd-test.py
3 # networkd integration test
4 # This uses temporary configuration in /run and temporary veth devices, and
5 # does not write anything on disk or change any system configuration;
6 # but it assumes (and checks at the beginning) that networkd is not currently
9 # This can be run on a normal installation, in QEMU, nspawn (with
10 # --private-network), LXD (with "--config raw.lxc=lxc.aa_profile=unconfined"),
11 # or LXC system containers. You need at least the "ip" tool from the iproute
12 # package; it is recommended to install dnsmasq too to get full test coverage.
14 # ATTENTION: This uses the *installed* networkd, not the one from the built
17 # (C) 2015 Canonical Ltd.
18 # Author: Martin Pitt <martin.pitt@ubuntu.com>
20 # systemd is free software; you can redistribute it and/or modify it
21 # under the terms of the GNU Lesser General Public License as published by
22 # the Free Software Foundation; either version 2.1 of the License, or
23 # (at your option) any later version.
25 # systemd is distributed in the hope that it will be useful, but
26 # WITHOUT ANY WARRANTY; without even the implied warranty of
27 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 # Lesser General Public License for more details.
30 # You should have received a copy of the GNU Lesser General Public License
31 # along with systemd; If not, see <http://www.gnu.org/licenses/>.
42 networkd_active
= subprocess
.call(['systemctl', 'is-active', '--quiet',
43 'systemd-networkd']) == 0
44 have_dnsmasq
= shutil
.which('dnsmasq')
46 RESOLV_CONF
= '/run/systemd/resolve/resolv.conf'
49 @unittest.skipIf(networkd_active
,
50 'networkd is already active')
53 self
.iface
= 'test_eth42'
54 self
.if_router
= 'router_eth42'
55 self
.workdir_obj
= tempfile
.TemporaryDirectory()
56 self
.workdir
= self
.workdir_obj
.name
57 self
.config
= '/run/systemd/network/test_eth42.network'
59 # avoid "Failed to open /dev/tty" errors in containers
60 os
.environ
['SYSTEMD_LOG_TARGET'] = 'journal'
62 # determine path to systemd-networkd-wait-online
63 for p
in ['/usr/lib/systemd/systemd-networkd-wait-online',
64 '/lib/systemd/systemd-networkd-wait-online']:
66 self
.networkd_wait_online
= p
69 self
.fail('systemd-networkd-wait-online not found')
71 # get current journal cursor
72 out
= subprocess
.check_output(['journalctl', '-b', '--quiet',
73 '--no-pager', '-n0', '--show-cursor'],
74 universal_newlines
=True)
75 self
.assertTrue(out
.startswith('-- cursor:'))
76 self
.journal_cursor
= out
.split()[-1]
80 subprocess
.call(['systemctl', 'stop', 'systemd-networkd'])
81 subprocess
.call(['ip', 'link', 'del', 'dummy0'],
82 stderr
=subprocess
.DEVNULL
)
84 def writeConfig(self
, fname
, contents
):
85 os
.makedirs(os
.path
.dirname(fname
), exist_ok
=True)
86 with
open(fname
, 'w') as f
:
88 self
.addCleanup(os
.remove
, fname
)
90 def show_journal(self
, unit
):
91 '''Show journal of given unit since start of the test'''
93 print('---- %s ----' % unit
)
95 subprocess
.call(['journalctl', '-b', '--no-pager', '--quiet',
96 '--cursor', self
.journal_cursor
, '-u', unit
])
98 def create_iface(self
, ipv6
=False):
99 '''Create test interface with DHCP server behind it'''
101 raise NotImplementedError('must be implemented by a subclass')
103 def shutdown_iface(self
):
104 '''Remove test interface and stop DHCP server'''
106 raise NotImplementedError('must be implemented by a subclass')
108 def print_server_log(self
):
109 '''Print DHCP server log for debugging failures'''
111 raise NotImplementedError('must be implemented by a subclass')
113 def do_test(self
, coldplug
=True, ipv6
=False, extra_opts
='',
114 online_timeout
=10, dhcp_mode
='yes'):
115 subprocess
.check_call(['systemctl', 'start', 'systemd-resolved'])
116 self
.writeConfig(self
.config
, '''\
121 %s''' % (self
.iface
, dhcp_mode
, extra_opts
))
124 # create interface first, then start networkd
125 self
.create_iface(ipv6
=ipv6
)
126 subprocess
.check_call(['systemctl', 'start', 'systemd-networkd'])
127 elif coldplug
is not None:
128 # start networkd first, then create interface
129 subprocess
.check_call(['systemctl', 'start', 'systemd-networkd'])
130 self
.create_iface(ipv6
=ipv6
)
132 # "None" means test sets up interface by itself
133 subprocess
.check_call(['systemctl', 'start', 'systemd-networkd'])
136 subprocess
.check_call([self
.networkd_wait_online
, '--interface',
137 self
.iface
, '--timeout=%i' % online_timeout
])
140 # check iface state and IP 6 address; FIXME: we need to wait a bit
141 # longer, as the iface is "configured" already with IPv4 *or*
142 # IPv6, but we want to wait for both
143 for timeout
in range(10):
144 out
= subprocess
.check_output(['ip', 'a', 'show', 'dev', self
.iface
])
145 if b
'state UP' in out
and b
'inet6 2600' in out
and b
'inet 192.168' in out
:
149 self
.fail('timed out waiting for IPv6 configuration')
151 self
.assertRegex(out
, b
'inet6 2600::.* scope global .*dynamic')
152 self
.assertRegex(out
, b
'inet6 fe80::.* scope link')
154 # should have link-local address on IPv6 only
155 out
= subprocess
.check_output(['ip', '-6', 'a', 'show', 'dev', self
.iface
])
156 self
.assertRegex(out
, b
'inet6 fe80::.* scope link')
157 self
.assertNotIn(b
'scope global', out
)
159 # should have IPv4 address
160 out
= subprocess
.check_output(['ip', '-4', 'a', 'show', 'dev', self
.iface
])
161 self
.assertIn(b
'state UP', out
)
162 self
.assertRegex(out
, b
'inet 192.168.5.\d+/.* scope global dynamic')
164 # check networkctl state
165 out
= subprocess
.check_output(['networkctl'])
166 self
.assertRegex(out
, ('%s\s+ether\s+routable\s+unmanaged' % self
.if_router
).encode())
167 self
.assertRegex(out
, ('%s\s+ether\s+routable\s+configured' % self
.iface
).encode())
169 out
= subprocess
.check_output(['networkctl', 'status', self
.iface
])
170 self
.assertRegex(out
, b
'Type:\s+ether')
171 self
.assertRegex(out
, b
'State:\s+routable.*configured')
172 self
.assertRegex(out
, b
'Address:\s+192.168.5.\d+')
174 self
.assertRegex(out
, b
'2600::')
176 self
.assertNotIn(b
'2600::', out
)
177 self
.assertRegex(out
, b
'fe80::')
178 self
.assertRegex(out
, b
'Gateway:\s+192.168.5.1')
179 self
.assertRegex(out
, b
'DNS:\s+192.168.5.1')
180 except (AssertionError, subprocess
.CalledProcessError
):
181 # show networkd status, journal, and DHCP server log on failure
182 with
open(self
.config
) as f
:
183 print('\n---- %s ----\n%s' % (self
.config
, f
.read()))
184 print('---- interface status ----')
186 subprocess
.call(['ip', 'a', 'show', 'dev', self
.iface
])
187 print('---- networkctl status %s ----' % self
.iface
)
189 subprocess
.call(['networkctl', 'status', self
.iface
])
190 self
.show_journal('systemd-networkd.service')
191 self
.print_server_log()
194 for timeout
in range(50):
195 with
open(RESOLV_CONF
) as f
:
197 if 'nameserver 192.168.5.1\n' in contents
:
201 self
.fail('nameserver 192.168.5.1 not found in ' + RESOLV_CONF
)
203 if coldplug
is False:
204 # check post-down.d hook
205 self
.shutdown_iface()
207 def test_coldplug_dhcp_yes_ip4(self
):
208 # we have a 12s timeout on RA, so we need to wait longer
209 self
.do_test(coldplug
=True, ipv6
=False, online_timeout
=15)
211 def test_coldplug_dhcp_yes_ip4_no_ra(self
):
212 # with disabling RA explicitly things should be fast
213 self
.do_test(coldplug
=True, ipv6
=False,
214 extra_opts
='IPv6AcceptRA=False')
216 def test_coldplug_dhcp_ip4_only(self
):
217 # we have a 12s timeout on RA, so we need to wait longer
218 self
.do_test(coldplug
=True, ipv6
=False, dhcp_mode
='ipv4',
221 def test_coldplug_dhcp_ip4_only_no_ra(self
):
222 # with disabling RA explicitly things should be fast
223 self
.do_test(coldplug
=True, ipv6
=False, dhcp_mode
='ipv4',
224 extra_opts
='IPv6AcceptRA=False')
226 def test_coldplug_dhcp_ip6(self
):
227 self
.do_test(coldplug
=True, ipv6
=True)
229 def test_hotplug_dhcp_ip4(self
):
230 # With IPv4 only we have a 12s timeout on RA, so we need to wait longer
231 self
.do_test(coldplug
=False, ipv6
=False, online_timeout
=15)
233 def test_hotplug_dhcp_ip6(self
):
234 self
.do_test(coldplug
=False, ipv6
=True)
236 def test_route_only_dns(self
):
237 self
.writeConfig('/run/systemd/network/myvpn.netdev', '''\
241 MACAddress=12:34:56:78:9a:bc''')
242 self
.writeConfig('/run/systemd/network/myvpn.network', '''\
246 Address=192.168.42.100
248 Domains= ~company''')
250 self
.do_test(coldplug
=True, ipv6
=False,
251 extra_opts
='IPv6AcceptRouterAdvertisements=False')
253 with
open(RESOLV_CONF
) as f
:
255 # ~company is not a search domain, only a routing domain
256 self
.assertNotRegex(contents
, 'search.*company')
257 # our global server should appear
258 self
.assertIn('nameserver 192.168.5.1\n', contents
)
259 # should not have domain-restricted server as global server
260 self
.assertNotIn('nameserver 192.168.42.1\n', contents
)
262 def test_route_only_dns_all_domains(self
):
263 with
open('/run/systemd/network/myvpn.netdev', 'w') as f
:
267 MACAddress=12:34:56:78:9a:bc''')
268 with
open('/run/systemd/network/myvpn.network', 'w') as f
:
272 Address=192.168.42.100
274 Domains= ~company ~.''')
275 self
.addCleanup(os
.remove
, '/run/systemd/network/myvpn.netdev')
276 self
.addCleanup(os
.remove
, '/run/systemd/network/myvpn.network')
278 self
.do_test(coldplug
=True, ipv6
=False,
279 extra_opts
='IPv6AcceptRouterAdvertisements=False')
281 with
open(RESOLV_CONF
) as f
:
284 # ~company is not a search domain, only a routing domain
285 self
.assertNotRegex(contents
, 'search.*company')
287 # our global server should appear
288 self
.assertIn('nameserver 192.168.5.1\n', contents
)
289 # should have company server as global server due to ~.
290 self
.assertIn('nameserver 192.168.42.1\n', contents
)
293 @unittest.skipUnless(have_dnsmasq
, 'dnsmasq not installed')
294 class DnsmasqClientTest(ClientTestBase
, unittest
.TestCase
):
295 '''Test networkd client against dnsmasq'''
300 self
.iface_mac
= 'de:ad:be:ef:47:11'
302 def create_iface(self
, ipv6
=False, dnsmasq_opts
=None):
303 '''Create test interface with DHCP server behind it'''
306 subprocess
.check_call(['ip', 'link', 'add', 'name', self
.iface
,
307 'address', self
.iface_mac
,
308 'type', 'veth', 'peer', 'name', self
.if_router
])
310 # give our router an IP
311 subprocess
.check_call(['ip', 'a', 'flush', 'dev', self
.if_router
])
312 subprocess
.check_call(['ip', 'a', 'add', '192.168.5.1/24', 'dev', self
.if_router
])
314 subprocess
.check_call(['ip', 'a', 'add', '2600::1/64', 'dev', self
.if_router
])
315 subprocess
.check_call(['ip', 'link', 'set', self
.if_router
, 'up'])
318 self
.dnsmasq_log
= os
.path
.join(self
.workdir
, 'dnsmasq.log')
319 lease_file
= os
.path
.join(self
.workdir
, 'dnsmasq.leases')
321 extra_opts
= ['--enable-ra', '--dhcp-range=2600::10,2600::20']
325 extra_opts
+= dnsmasq_opts
326 self
.dnsmasq
= subprocess
.Popen(
327 ['dnsmasq', '--keep-in-foreground', '--log-queries',
328 '--log-facility=' + self
.dnsmasq_log
, '--conf-file=/dev/null',
329 '--dhcp-leasefile=' + lease_file
, '--bind-interfaces',
330 '--interface=' + self
.if_router
, '--except-interface=lo',
331 '--dhcp-range=192.168.5.10,192.168.5.200'] + extra_opts
)
333 def shutdown_iface(self
):
334 '''Remove test interface and stop DHCP server'''
337 subprocess
.check_call(['ip', 'link', 'del', 'dev', self
.if_router
])
338 self
.if_router
= None
344 def print_server_log(self
):
345 '''Print DHCP server log for debugging failures'''
347 with
open(self
.dnsmasq_log
) as f
:
348 sys
.stdout
.write('\n\n---- dnsmasq log ----\n%s\n------\n\n' % f
.read())
350 def test_resolved_domain_restricted_dns(self
):
351 '''resolved: domain-restricted DNS servers'''
353 # create interface for generic connections; this will map all DNS names
355 self
.create_iface(dnsmasq_opts
=['--address=/#/192.168.42.1'])
356 self
.writeConfig('/run/systemd/network/general.network', '''\
361 IPv6AcceptRA=False''' % self
.iface
)
363 # create second device/dnsmasq for a .company/.lab VPN interface
364 # static IPs for simplicity
365 subprocess
.check_call(['ip', 'link', 'add', 'name', 'testvpnclient', 'type',
366 'veth', 'peer', 'name', 'testvpnrouter'])
367 self
.addCleanup(subprocess
.call
, ['ip', 'link', 'del', 'dev', 'testvpnrouter'])
368 subprocess
.check_call(['ip', 'a', 'flush', 'dev', 'testvpnrouter'])
369 subprocess
.check_call(['ip', 'a', 'add', '10.241.3.1/24', 'dev', 'testvpnrouter'])
370 subprocess
.check_call(['ip', 'link', 'set', 'testvpnrouter', 'up'])
372 vpn_dnsmasq_log
= os
.path
.join(self
.workdir
, 'dnsmasq-vpn.log')
373 vpn_dnsmasq
= subprocess
.Popen(
374 ['dnsmasq', '--keep-in-foreground', '--log-queries',
375 '--log-facility=' + vpn_dnsmasq_log
, '--conf-file=/dev/null',
376 '--dhcp-leasefile=/dev/null', '--bind-interfaces',
377 '--interface=testvpnrouter', '--except-interface=lo',
378 '--address=/math.lab/10.241.3.3', '--address=/cantina.company/10.241.4.4'])
379 self
.addCleanup(vpn_dnsmasq
.wait
)
380 self
.addCleanup(vpn_dnsmasq
.kill
)
382 self
.writeConfig('/run/systemd/network/vpn.network', '''\
387 Address=10.241.3.2/24
389 Domains= ~company ~lab''')
391 subprocess
.check_call(['systemctl', 'start', 'systemd-networkd'])
392 subprocess
.check_call([self
.networkd_wait_online
, '--interface', self
.iface
,
393 '--interface=testvpnclient', '--timeout=20'])
395 # ensure we start fresh with every test
396 subprocess
.check_call(['systemctl', 'restart', 'systemd-resolved'])
398 # test vpnclient specific domains; these should *not* be answered by
400 out
= subprocess
.check_output(['systemd-resolve', 'math.lab'])
401 self
.assertIn(b
'math.lab: 10.241.3.3', out
)
402 out
= subprocess
.check_output(['systemd-resolve', 'kettle.cantina.company'])
403 self
.assertIn(b
'kettle.cantina.company: 10.241.4.4', out
)
405 # test general domains
406 out
= subprocess
.check_output(['systemd-resolve', 'megasearch.net'])
407 self
.assertIn(b
'megasearch.net: 192.168.42.1', out
)
409 with
open(self
.dnsmasq_log
) as f
:
410 general_log
= f
.read()
411 with
open(vpn_dnsmasq_log
) as f
:
414 # VPN domains should only be sent to VPN DNS
415 self
.assertRegex(vpn_log
, 'query.*math.lab')
416 self
.assertRegex(vpn_log
, 'query.*cantina.company')
417 self
.assertNotIn('lab', general_log
)
418 self
.assertNotIn('company', general_log
)
420 # general domains should not be sent to the VPN DNS
421 self
.assertRegex(general_log
, 'query.*megasearch.net')
422 self
.assertNotIn('megasearch.net', vpn_log
)
424 def test_transient_hostname(self
):
425 '''networkd sets transient hostname from DHCP'''
427 orig_hostname
= socket
.gethostname()
428 self
.addCleanup(socket
.sethostname
, orig_hostname
)
429 # temporarily move /etc/hostname away; restart hostnamed to pick it up
430 if os
.path
.exists('/etc/hostname'):
431 subprocess
.check_call(['mount', '--bind', '/dev/null', '/etc/hostname'])
432 self
.addCleanup(subprocess
.call
, ['umount', '/etc/hostname'])
433 subprocess
.check_call(['systemctl', 'stop', 'systemd-hostnamed.service'])
435 self
.create_iface(dnsmasq_opts
=['--dhcp-host=%s,192.168.5.210,testgreen' % self
.iface_mac
])
436 self
.do_test(coldplug
=None, extra_opts
='IPv6AcceptRA=False', dhcp_mode
='ipv4')
438 # should have received the fixed IP above
439 out
= subprocess
.check_output(['ip', '-4', 'a', 'show', 'dev', self
.iface
])
440 self
.assertRegex(out
, b
'inet 192.168.5.210/24 .* scope global dynamic')
441 # should have set transient hostname in hostnamed
442 self
.assertIn(b
'testgreen', subprocess
.check_output(['hostnamectl']))
443 # and also applied to the system
444 self
.assertEqual(socket
.gethostname(), 'testgreen')
446 def test_transient_hostname_with_static(self
):
447 '''transient hostname is not applied if static hostname exists'''
449 orig_hostname
= socket
.gethostname()
450 self
.addCleanup(socket
.sethostname
, orig_hostname
)
451 if not os
.path
.exists('/etc/hostname'):
452 self
.writeConfig('/etc/hostname', orig_hostname
)
453 subprocess
.check_call(['systemctl', 'stop', 'systemd-hostnamed.service'])
455 self
.create_iface(dnsmasq_opts
=['--dhcp-host=%s,192.168.5.210,testgreen' % self
.iface_mac
])
456 self
.do_test(coldplug
=None, extra_opts
='IPv6AcceptRA=False', dhcp_mode
='ipv4')
458 # should have received the fixed IP above
459 out
= subprocess
.check_output(['ip', '-4', 'a', 'show', 'dev', self
.iface
])
460 self
.assertRegex(out
, b
'inet 192.168.5.210/24 .* scope global dynamic')
461 # static hostname wins over transient one, thus *not* applied
462 self
.assertEqual(socket
.gethostname(), orig_hostname
)
465 class NetworkdClientTest(ClientTestBase
, unittest
.TestCase
):
466 '''Test networkd client against networkd server'''
472 def create_iface(self
, ipv6
=False, dhcpserver_opts
=None):
473 '''Create test interface with DHCP server behind it'''
475 # run "router-side" networkd in own mount namespace to shield it from
476 # "client-side" configuration and networkd
477 (fd
, script
) = tempfile
.mkstemp(prefix
='networkd-router.sh')
478 self
.addCleanup(os
.remove
, script
)
479 with os
.fdopen(fd
, 'w+') as f
:
482 mkdir -p /run/systemd/network
483 mkdir -p /run/systemd/netif
484 mount -t tmpfs none /run/systemd/network
485 mount -t tmpfs none /run/systemd/netif
486 [ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus
487 # create router/client veth pair
488 cat << EOF > /run/systemd/network/test.netdev
497 cat << EOF > /run/systemd/network/test.network
502 Address=192.168.5.1/24
513 # run networkd as in systemd-networkd.service
514 exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ { s/^.*=//; p}')
515 ''' % {'ifr': self
.if_router
, 'ifc': self
.iface
, 'addr6': ipv6
and 'Address=2600::1/64' or '',
516 'dhopts': dhcpserver_opts
or ''})
520 subprocess
.check_call(['systemd-run', '--unit=networkd-test-router.service',
521 '-p', 'InaccessibleDirectories=-/etc/systemd/network',
522 '-p', 'InaccessibleDirectories=-/run/systemd/network',
523 '-p', 'InaccessibleDirectories=-/run/systemd/netif',
524 '--service-type=notify', script
])
526 # wait until devices got created
527 for timeout
in range(50):
528 out
= subprocess
.check_output(['ip', 'a', 'show', 'dev', self
.if_router
])
529 if b
'state UP' in out
and b
'scope global' in out
:
533 def shutdown_iface(self
):
534 '''Remove test interface and stop DHCP server'''
537 subprocess
.check_call(['systemctl', 'stop', 'networkd-test-router.service'])
538 # ensure failed transient unit does not stay around
539 subprocess
.call(['systemctl', 'reset-failed', 'networkd-test-router.service'])
540 subprocess
.call(['ip', 'link', 'del', 'dev', self
.if_router
])
541 self
.if_router
= None
543 def print_server_log(self
):
544 '''Print DHCP server log for debugging failures'''
546 self
.show_journal('networkd-test-router.service')
548 @unittest.skip('networkd does not have DHCPv6 server support')
549 def test_hotplug_dhcp_ip6(self
):
552 @unittest.skip('networkd does not have DHCPv6 server support')
553 def test_coldplug_dhcp_ip6(self
):
556 def test_search_domains(self
):
558 # we don't use this interface for this test
559 self
.if_router
= None
561 self
.writeConfig('/run/systemd/network/test.netdev', '''\
565 MACAddress=12:34:56:78:9a:bc''')
566 self
.writeConfig('/run/systemd/network/test.network', '''\
570 Address=192.168.42.100
572 Domains= one two three four five six seven eight nine ten''')
574 subprocess
.check_call(['systemctl', 'start', 'systemd-networkd'])
576 for timeout
in range(50):
577 with
open(RESOLV_CONF
) as f
:
579 if ' one' in contents
:
582 self
.assertRegex(contents
, 'search .*one two three four')
583 self
.assertNotIn('seven\n', contents
)
584 self
.assertIn('# Too many search domains configured, remaining ones ignored.\n', contents
)
586 def test_search_domains_too_long(self
):
588 # we don't use this interface for this test
589 self
.if_router
= None
591 name_prefix
= 'a' * 60
593 self
.writeConfig('/run/systemd/network/test.netdev', '''\
597 MACAddress=12:34:56:78:9a:bc''')
598 self
.writeConfig('/run/systemd/network/test.network', '''\
602 Address=192.168.42.100
604 Domains={p}0 {p}1 {p}2 {p}3 {p}4'''.format(p
=name_prefix
))
606 subprocess
.check_call(['systemctl', 'start', 'systemd-networkd'])
608 for timeout
in range(50):
609 with
open(RESOLV_CONF
) as f
:
611 if ' one' in contents
:
614 self
.assertRegex(contents
, 'search .*{p}0 {p}1 {p}2'.format(p
=name_prefix
))
615 self
.assertIn('# Total length of all search domains is too long, remaining ones ignored.', contents
)
617 def test_dropin(self
):
618 # we don't use this interface for this test
619 self
.if_router
= None
621 self
.writeConfig('/run/systemd/network/test.netdev', '''\
625 MACAddress=12:34:56:78:9a:bc''')
626 self
.writeConfig('/run/systemd/network/test.network', '''\
630 Address=192.168.42.100
632 self
.writeConfig('/run/systemd/network/test.network.d/dns.conf', '''\
636 subprocess
.check_call(['systemctl', 'start', 'systemd-networkd'])
638 for timeout
in range(50):
639 with
open(RESOLV_CONF
) as f
:
641 if ' 127.0.0.1' in contents
:
644 self
.assertIn('nameserver 192.168.42.1\n', contents
)
645 self
.assertIn('nameserver 127.0.0.1\n', contents
)
647 def test_dhcp_timezone(self
):
648 '''networkd sets time zone from DHCP'''
651 out
= subprocess
.check_output(['busctl', 'get-property', 'org.freedesktop.timedate1',
652 '/org/freedesktop/timedate1', 'org.freedesktop.timedate1', 'Timezone'])
653 assert out
.startswith(b
's "')
655 assert out
.endswith(b
'"')
656 return out
[3:-1].decode()
658 orig_timezone
= get_tz()
659 self
.addCleanup(subprocess
.call
, ['timedatectl', 'set-timezone', orig_timezone
])
661 self
.create_iface(dhcpserver_opts
='EmitTimezone=yes\nTimezone=Pacific/Honolulu')
662 self
.do_test(coldplug
=None, extra_opts
='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode
='ipv4')
664 # should have applied the received timezone
666 self
.assertEqual(get_tz(), 'Pacific/Honolulu')
667 except AssertionError:
668 self
.show_journal('systemd-networkd.service')
669 self
.show_journal('systemd-hostnamed.service')
673 if __name__
== '__main__':
674 unittest
.main(testRunner
=unittest
.TextTestRunner(stream
=sys
.stdout
,