Merge ~andrey-fedoseev/launchpad-buildd:backend-open into launchpad-buildd:master

Proposed by Andrey Fedoseev
Status: Merged
Approved by: Andrey Fedoseev
Approved revision: 37ae20af627f8b0056b988972b42845888510e5a
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~andrey-fedoseev/launchpad-buildd:backend-open
Merge into: launchpad-buildd:master
Diff against target: 540 lines (+146/-105)
11 files modified
debian/changelog (+5/-0)
lpbuildd/binarypackage.py (+3/-3)
lpbuildd/ci.py (+3/-8)
lpbuildd/target/apt.py (+18/-24)
lpbuildd/target/backend.py (+25/-0)
lpbuildd/target/build_oci.py (+1/-4)
lpbuildd/target/build_snap.py (+4/-6)
lpbuildd/target/lxd.py (+20/-37)
lpbuildd/target/run_ci.py (+5/-10)
lpbuildd/target/tests/test_backend.py (+39/-0)
lpbuildd/target/tests/test_lxd.py (+23/-13)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+432731@code.launchpad.net

Commit message

Use `Backend.open` to modify files in target environments

instead of using a local temporary file and `copy_out` / `copy_in` methods

Description of the change

A new method `open` is added to the base `Backend` class

The method access to the files in the target environment via a file-like object.

Under the hood it uses a temporary file on the host system and `copy_in` / `copy_out` methods to transfer the file to / from the target environment.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

Nice simplification, thanks!

review: Approve
Revision history for this message
Andrey Fedoseev (andrey-fedoseev) wrote :

Colin,

Please see my comments below

Revision history for this message
Colin Watson (cjwatson) wrote :

All your replies make sense. Go ahead (except for adding the copyright/licensing comment to `lpbuildd/target/tests/test_backend.py`). Thanks!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 0ce11f2..fba13f2 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,9 +1,14 @@
6 launchpad-buildd (224) UNRELEASED; urgency=medium
7
8+ [ Colin Watson ]
9 * Allow configuring builders to use a different ClamAV database URL.
10 * Require the LXD snap to be installed, rather than depending on the lxd
11 package (which no longer exists in jammy).
12
13+ [ Andrey Fedoseev ]
14+ * `open` method is added to the backends providing access to the files
15+ in target environments via a file-like object.
16+
17 -- Colin Watson <cjwatson@ubuntu.com> Wed, 26 Oct 2022 08:55:39 +0200
18
19 launchpad-buildd (223) focal; urgency=medium
20diff --git a/lpbuildd/binarypackage.py b/lpbuildd/binarypackage.py
21index c3fef09..3498e58 100644
22--- a/lpbuildd/binarypackage.py
23+++ b/lpbuildd/binarypackage.py
24@@ -156,11 +156,11 @@ class BinaryPackageBuildManager(DebianBuildManager):
25 self.archive_purpose))
26 if self.build_debug_symbols:
27 currently_building_contents += 'Build-Debug-Symbols: yes\n'
28- with tempfile.NamedTemporaryFile(mode='w+') as currently_building:
29+ with self.backend.open(
30+ '/CurrentlyBuilding', mode='w+'
31+ ) as currently_building:
32 currently_building.write(currently_building_contents)
33- currently_building.flush()
34 os.fchmod(currently_building.fileno(), 0o644)
35- self.backend.copy_in(currently_building.name, '/CurrentlyBuilding')
36
37 args = ["sbuild-package", self._buildid, self.arch_tag]
38 args.append(self.suite)
39diff --git a/lpbuildd/ci.py b/lpbuildd/ci.py
40index 04aa5f8..601d8b4 100644
41--- a/lpbuildd/ci.py
42+++ b/lpbuildd/ci.py
43@@ -6,7 +6,6 @@ from configparser import (
44 NoSectionError,
45 )
46 import os
47-import tempfile
48 import yaml
49
50 from twisted.internet import defer
51@@ -168,14 +167,10 @@ class CIBuildManager(BuildManagerProxyMixin, DebianBuildManager):
52 ["--plugin-setting", f"{key}={value}"])
53 if self.secrets is not None:
54 text = yaml.dump(self.secrets)
55- with tempfile.NamedTemporaryFile(mode="w") as f:
56+ with self.backend.open(
57+ "/build/.launchpad-secrets.yaml", mode="w"
58+ ) as f:
59 f.write(text)
60- f.flush()
61- path_to_secrets = f.name
62- self.backend.copy_in(
63- source_path=path_to_secrets,
64- target_path="/build/.launchpad-secrets.yaml"
65- )
66 args.extend(
67 ["--secrets", "/build/.launchpad-secrets.yaml"])
68 if self.scan_malware:
69diff --git a/lpbuildd/target/apt.py b/lpbuildd/target/apt.py
70index ebd4683..a411806 100644
71--- a/lpbuildd/target/apt.py
72+++ b/lpbuildd/target/apt.py
73@@ -5,7 +5,6 @@ import logging
74 import os
75 import subprocess
76 import sys
77-import tempfile
78 from textwrap import dedent
79 import time
80
81@@ -30,49 +29,44 @@ class OverrideSourcesList(Operation):
82
83 def run(self):
84 logger.info("Overriding sources.list in build-%s", self.args.build_id)
85- with tempfile.NamedTemporaryFile(mode="w+") as sources_list:
86+ with self.backend.open(
87+ "/etc/apt/sources.list", mode="w+"
88+ ) as sources_list:
89 for archive in self.args.archives:
90 print(archive, file=sources_list)
91- sources_list.flush()
92 os.fchmod(sources_list.fileno(), 0o644)
93- self.backend.copy_in(sources_list.name, "/etc/apt/sources.list")
94- with tempfile.NamedTemporaryFile(mode="w+") as apt_retries_conf:
95+ with self.backend.open(
96+ "/etc/apt/apt.conf.d/99retries", mode="w+"
97+ ) as apt_retries_conf:
98 print('Acquire::Retries "3";', file=apt_retries_conf)
99- apt_retries_conf.flush()
100 os.fchmod(apt_retries_conf.fileno(), 0o644)
101- self.backend.copy_in(
102- apt_retries_conf.name, "/etc/apt/apt.conf.d/99retries")
103 # Versions of APT that support phased updates do this automatically
104 # if running in a chroot, but builds may be running in a LXD
105 # container instead.
106- with tempfile.NamedTemporaryFile(mode="w+") as apt_phasing_conf:
107+ with self.backend.open(
108+ "/etc/apt/apt.conf.d/99phasing", mode="w+"
109+ ) as apt_phasing_conf:
110 print('APT::Get::Always-Include-Phased-Updates "true";',
111 file=apt_phasing_conf)
112- apt_phasing_conf.flush()
113 os.fchmod(apt_phasing_conf.fileno(), 0o644)
114- self.backend.copy_in(
115- apt_phasing_conf.name, "/etc/apt/apt.conf.d/99phasing")
116 if self.args.apt_proxy_url is not None:
117- with tempfile.NamedTemporaryFile(mode="w+") as apt_proxy_conf:
118+ with self.backend.open(
119+ "/etc/apt/apt.conf.d/99proxy", mode="w+"
120+ ) as apt_proxy_conf:
121 print(
122 f'Acquire::http::Proxy "{self.args.apt_proxy_url}";',
123 file=apt_proxy_conf)
124- apt_proxy_conf.flush()
125 os.fchmod(apt_proxy_conf.fileno(), 0o644)
126- self.backend.copy_in(
127- apt_proxy_conf.name, "/etc/apt/apt.conf.d/99proxy")
128 for pocket in ("proposed", "backports"):
129- with tempfile.NamedTemporaryFile(mode="w+") as preferences:
130+ with self.backend.open(
131+ f"/etc/apt/preferences.d/{pocket}.pref", mode="w+"
132+ ) as preferences:
133 print(dedent(f"""\
134 Package: *
135 Pin: release a=*-{pocket}
136 Pin-Priority: 500
137 """), file=preferences, end="")
138- preferences.flush()
139 os.fchmod(preferences.fileno(), 0o644)
140- self.backend.copy_in(
141- preferences.name,
142- f"/etc/apt/preferences.d/{pocket}.pref")
143 return 0
144
145
146@@ -91,7 +85,9 @@ class AddTrustedKeys(Operation):
147 gpg_cmd = [
148 "gpg", "--ignore-time-conflict", "--no-options", "--no-keyring",
149 ]
150- with tempfile.NamedTemporaryFile(mode="wb+") as keyring:
151+ with self.backend.open(
152+ "/etc/apt/trusted.gpg.d/launchpad-buildd.gpg", mode="wb+"
153+ ) as keyring:
154 subprocess.check_call(
155 gpg_cmd + ["--dearmor"], stdin=self.input_file, stdout=keyring)
156 keyring.seek(0)
157@@ -100,8 +96,6 @@ class AddTrustedKeys(Operation):
158 ["--show-keys", "--keyid-format", "long", "--fingerprint"],
159 stdin=keyring, stdout=self.show_keys_file)
160 os.fchmod(keyring.fileno(), 0o644)
161- self.backend.copy_in(
162- keyring.name, "/etc/apt/trusted.gpg.d/launchpad-buildd.gpg")
163 return 0
164
165
166diff --git a/lpbuildd/target/backend.py b/lpbuildd/target/backend.py
167index 69746bb..a920cf1 100644
168--- a/lpbuildd/target/backend.py
169+++ b/lpbuildd/target/backend.py
170@@ -3,6 +3,10 @@
171
172 import os.path
173 import subprocess
174+import tempfile
175+from contextlib import contextmanager
176+from pathlib import Path
177+from shutil import rmtree
178
179
180 class BackendException(Exception):
181@@ -189,6 +193,27 @@ class Backend:
182 """Remove the backend."""
183 subprocess.check_call(["sudo", "rm", "-rf", self.build_path])
184
185+ @contextmanager
186+ def open(self, path: str, mode="r", **kwargs):
187+ """
188+ Provides access to the files in the target environment via a
189+ file-like object.
190+
191+ The arguments are the same as those of the built-in `open` function.
192+ """
193+ tmp_dir = tempfile.mkdtemp()
194+ tmp_path = os.path.join(tmp_dir, Path(path).name)
195+ if self.path_exists(path):
196+ self.copy_out(path, tmp_path)
197+ tmp_file = open(tmp_path, mode=mode, **kwargs)
198+ try:
199+ yield tmp_file
200+ finally:
201+ tmp_file.close()
202+ if mode not in ("r", "rb", "rt"):
203+ self.copy_in(tmp_path, path)
204+ rmtree(tmp_dir)
205+
206
207 def make_backend(name, build_id, series=None, arch=None):
208 if name == "chroot":
209diff --git a/lpbuildd/target/build_oci.py b/lpbuildd/target/build_oci.py
210index 5241f9f..ecbda0d 100644
211--- a/lpbuildd/target/build_oci.py
212+++ b/lpbuildd/target/build_oci.py
213@@ -3,7 +3,6 @@
214
215 import logging
216 import os.path
217-import tempfile
218 from textwrap import dedent
219
220 from lpbuildd.target.backend import check_path_escape
221@@ -56,10 +55,8 @@ class BuildOCI(BuilderProxyOperationMixin, VCSOperationMixin,
222 Environment="{setting.upper()}={self.args.proxy_url}"
223 """)
224 file_path = f"/etc/systemd/system/docker.service.d/{setting}.conf"
225- with tempfile.NamedTemporaryFile(mode="w+") as systemd_file:
226+ with self.backend.open(file_path, mode="w+") as systemd_file:
227 systemd_file.write(contents)
228- systemd_file.flush()
229- self.backend.copy_in(systemd_file.name, file_path)
230
231 def install(self):
232 logger.info("Running install phase...")
233diff --git a/lpbuildd/target/build_snap.py b/lpbuildd/target/build_snap.py
234index 30c5ecc..615c2d9 100644
235--- a/lpbuildd/target/build_snap.py
236+++ b/lpbuildd/target/build_snap.py
237@@ -5,7 +5,6 @@ import argparse
238 import json
239 import logging
240 import os.path
241-import tempfile
242 from textwrap import dedent
243 from urllib.parse import urlparse
244
245@@ -92,13 +91,12 @@ class BuildSnap(BuilderProxyOperationMixin, VCSOperationMixin,
246 svn_servers += f"http-proxy-username = {proxy.username}\n"
247 if proxy.password:
248 svn_servers += f"http-proxy-password = {proxy.password}\n"
249- with tempfile.NamedTemporaryFile(mode="w+") as svn_servers_file:
250+ self.backend.run(["mkdir", "-p", "/root/.subversion"])
251+ with self.backend.open(
252+ "/root/.subversion/servers", mode="w+"
253+ ) as svn_servers_file:
254 svn_servers_file.write(svn_servers)
255- svn_servers_file.flush()
256 os.fchmod(svn_servers_file.fileno(), 0o644)
257- self.backend.run(["mkdir", "-p", "/root/.subversion"])
258- self.backend.copy_in(
259- svn_servers_file.name, "/root/.subversion/servers")
260
261 def install(self):
262 logger.info("Running install phase...")
263diff --git a/lpbuildd/target/lxd.py b/lpbuildd/target/lxd.py
264index d58d9ab..63f36d5 100644
265--- a/lpbuildd/target/lxd.py
266+++ b/lpbuildd/target/lxd.py
267@@ -9,7 +9,6 @@ import re
268 import stat
269 import subprocess
270 import tarfile
271-import tempfile
272 from textwrap import dedent
273 import time
274
275@@ -376,22 +375,16 @@ class LXD(Backend):
276 ["hostname"], universal_newlines=True).rstrip("\n")
277 fqdn = subprocess.check_output(
278 ["hostname", "--fqdn"], universal_newlines=True).rstrip("\n")
279- with tempfile.NamedTemporaryFile(mode="w+") as hosts_file:
280- try:
281- self.copy_out("/etc/hosts", hosts_file.name)
282- except LXDException:
283- hosts_file.seek(0, os.SEEK_SET)
284- hosts_file.write(fallback_hosts)
285+ with self.open("/etc/hosts", mode="a") as hosts_file:
286 hosts_file.seek(0, os.SEEK_END)
287+ if not hosts_file.tell():
288+ # /etc/hosts is missing or empty
289+ hosts_file.write(fallback_hosts)
290 print(f"\n127.0.1.1\t{fqdn} {hostname}", file=hosts_file)
291- hosts_file.flush()
292 os.fchmod(hosts_file.fileno(), 0o644)
293- self.copy_in(hosts_file.name, "/etc/hosts")
294- with tempfile.NamedTemporaryFile(mode="w+") as hostname_file:
295+ with self.open("/etc/hostname", mode="w+") as hostname_file:
296 print(hostname, file=hostname_file)
297- hostname_file.flush()
298 os.fchmod(hostname_file.fileno(), 0o644)
299- self.copy_in(hostname_file.name, "/etc/hostname")
300
301 resolv_conf = "/etc/resolv.conf"
302
303@@ -403,23 +396,17 @@ class LXD(Backend):
304
305 self.copy_in(resolv_conf, "/etc/resolv.conf")
306
307- with tempfile.NamedTemporaryFile(mode="w+") as policy_rc_d_file:
308+ with self.open(
309+ "/usr/local/sbin/policy-rc.d", mode="w+"
310+ ) as policy_rc_d_file:
311 policy_rc_d_file.write(policy_rc_d)
312- policy_rc_d_file.flush()
313 os.fchmod(policy_rc_d_file.fileno(), 0o755)
314- self.copy_in(policy_rc_d_file.name, "/usr/local/sbin/policy-rc.d")
315 # For targets that use Upstart, prevent the mounted-dev job from
316 # creating devices. Most of the devices it creates are unnecessary
317 # in a container, and creating loop devices will race with our own
318 # code to do so.
319- with tempfile.NamedTemporaryFile(mode="w+") as mounted_dev_file:
320- try:
321- self.copy_out(
322- "/etc/init/mounted-dev.conf", mounted_dev_file.name)
323- except LXDException:
324- pass
325- else:
326- mounted_dev_file.seek(0, os.SEEK_SET)
327+ if self.path_exists("/etc/init/mounted-dev.conf"):
328+ with self.open("/etc/init/mounted-dev.conf") as mounted_dev_file:
329 script = ""
330 in_script = False
331 for line in mounted_dev_file:
332@@ -431,15 +418,13 @@ class LXD(Backend):
333 elif line.strip() == "script":
334 script += line
335 in_script = True
336- if script:
337- mounted_dev_file.seek(0, os.SEEK_SET)
338- mounted_dev_file.truncate()
339- mounted_dev_file.write(script)
340- mounted_dev_file.flush()
341- os.fchmod(mounted_dev_file.fileno(), 0o644)
342- self.copy_in(
343- mounted_dev_file.name,
344- "/etc/init/mounted-dev.override")
345+
346+ if script:
347+ with self.open(
348+ "/etc/init/mounted-dev.override", mode="w"
349+ ) as mounted_dev_override_file:
350+ mounted_dev_override_file.write(script)
351+ os.fchmod(mounted_dev_override_file.fileno(), 0o644)
352
353 # Start the container and wait for it to start.
354 container.start(wait=True)
355@@ -481,16 +466,14 @@ class LXD(Backend):
356 # directory until the container has started. We can get away with
357 # this for the time being because snapd isn't in the buildd chroots.
358 self.run(["mkdir", "-p", "/etc/systemd/system/snapd.service.d"])
359- with tempfile.NamedTemporaryFile(mode="w+") as no_cdn_file:
360+ with self.open(
361+ "/etc/systemd/system/snapd.service.d/no-cdn.conf", mode="w+"
362+ ) as no_cdn_file:
363 print(dedent("""\
364 [Service]
365 Environment=SNAPPY_STORE_NO_CDN=1
366 """), file=no_cdn_file, end="")
367- no_cdn_file.flush()
368 os.fchmod(no_cdn_file.fileno(), 0o644)
369- self.copy_in(
370- no_cdn_file.name,
371- "/etc/systemd/system/snapd.service.d/no-cdn.conf")
372
373 # Refreshing snaps from a timer unit during a build isn't
374 # appropriate. Mask this, but manually so that we don't depend on
375diff --git a/lpbuildd/target/run_ci.py b/lpbuildd/target/run_ci.py
376index 4ebf765..28dc2e7 100644
377--- a/lpbuildd/target/run_ci.py
378+++ b/lpbuildd/target/run_ci.py
379@@ -3,7 +3,6 @@
380
381 import logging
382 import os
383-import tempfile
384
385 from lpbuildd.target.build_snap import SnapChannelsAction
386 from lpbuildd.target.operation import Operation
387@@ -77,16 +76,12 @@ class RunCIPrepare(BuilderProxyOperationMixin, VCSOperationMixin,
388 # services, which is convenient since it allows us to ensure
389 # that ClamAV's database is up to date before proceeding.
390 if self.args.clamav_database_url:
391- freshclam_path = "/etc/clamav/freshclam.conf"
392- with tempfile.NamedTemporaryFile(mode="w+") as freshclam_file:
393- self.backend.copy_out(freshclam_path, freshclam_file.name)
394- freshclam_file.seek(0, os.SEEK_END)
395- print(
396- f"PrivateMirror {self.args.clamav_database_url}",
397- file=freshclam_file,
398+ with self.backend.open(
399+ "/etc/clamav/freshclam.conf", mode="a"
400+ ) as freshclam_file:
401+ freshclam_file.write(
402+ f"PrivateMirror {self.args.clamav_database_url}\n"
403 )
404- freshclam_file.flush()
405- self.backend.copy_in(freshclam_file.name, freshclam_path)
406 kwargs = {}
407 env = self.build_proxy_environment(proxy_url=self.args.proxy_url)
408 if env:
409diff --git a/lpbuildd/target/tests/test_backend.py b/lpbuildd/target/tests/test_backend.py
410new file mode 100644
411index 0000000..ff617e3
412--- /dev/null
413+++ b/lpbuildd/target/tests/test_backend.py
414@@ -0,0 +1,39 @@
415+# Copyright 2022 Canonical Ltd. This software is licensed under the
416+# GNU Affero General Public License version 3 (see the file LICENSE).
417+from unittest.mock import patch, ANY
418+
419+from testtools import TestCase
420+from fixtures import TempDir
421+
422+from lpbuildd.tests.fakebuilder import UncontainedBackend
423+
424+
425+class TestBackend(TestCase):
426+
427+ def test_open(self):
428+ backend = UncontainedBackend("1")
429+ backend_root = self.useFixture(TempDir())
430+ target_path = backend_root.join("test.txt")
431+
432+ with patch.object(
433+ backend, "copy_in", wraps=backend.copy_in
434+ ) as copy_in, patch.object(
435+ backend, "copy_out", wraps=backend.copy_out
436+ ) as copy_out:
437+
438+ with backend.open(target_path, "w") as f:
439+ f.write("text")
440+
441+ copy_out.assert_not_called()
442+ copy_in.assert_called_once_with(ANY, target_path)
443+
444+ self.assertTrue(backend.path_exists(target_path))
445+
446+ copy_in.reset_mock()
447+ copy_out.reset_mock()
448+
449+ with backend.open(target_path, "r") as f:
450+ self.assertEqual(f.read(), "text")
451+
452+ copy_in.assert_not_called()
453+ copy_out.assert_called_once_with(target_path, ANY)
454diff --git a/lpbuildd/target/tests/test_lxd.py b/lpbuildd/target/tests/test_lxd.py
455index ecf3ddd..73f4d94 100644
456--- a/lpbuildd/target/tests/test_lxd.py
457+++ b/lpbuildd/target/tests/test_lxd.py
458@@ -391,9 +391,10 @@ class TestLXD(TestCase):
459 lambda wait=False: setattr(container, "status_code", LXD_RUNNING))
460 files_api = container.api.files
461 files_api._api_endpoint = f"/1.0/containers/lp-xenial-{arch}/files"
462- files_api.session.get.side_effect = FakeSessionGet({
463+ existing_files = {
464 "/etc/hosts": [b"127.0.0.1\tlocalhost\n"],
465- })
466+ }
467+ files_api.session.get.side_effect = FakeSessionGet(existing_files)
468 processes_fixture = self.useFixture(FakeProcesses())
469
470 def fake_sudo(args):
471@@ -414,7 +415,13 @@ class TestLXD(TestCase):
472 processes_fixture.add(lambda _: {}, name="lxc")
473 processes_fixture.add(
474 FakeHostname("example", "example.buildd"), name="hostname")
475- LXD("1", "xenial", arch).start()
476+
477+ with mock.patch.object(
478+ LXD,
479+ "path_exists",
480+ side_effect=lambda path: path in existing_files
481+ ):
482+ LXD("1", "xenial", arch).start()
483
484 self.assert_correct_profile()
485
486@@ -515,9 +522,6 @@ class TestLXD(TestCase):
487 params={"path": "/usr/local/sbin/policy-rc.d"},
488 data=policy_rc_d.encode("UTF-8"),
489 headers={"X-LXD-uid": "0", "X-LXD-gid": "0", "X-LXD-mode": "0755"})
490- files_api.session.get.assert_any_call(
491- f"/1.0/containers/lp-xenial-{arch}/files",
492- params={"path": "/etc/init/mounted-dev.conf"}, stream=True)
493 self.assertNotIn(
494 "/etc/init/mounted-dev.override",
495 [kwargs["params"]["path"]
496@@ -551,11 +555,10 @@ class TestLXD(TestCase):
497 processes_fixture.add(lambda _: {}, name="lxc")
498 processes_fixture.add(
499 FakeHostname("example", "example.buildd"), name="hostname")
500- LXD("1", "xenial", "amd64").start()
501
502- files_api.session.get.assert_any_call(
503- "/1.0/containers/lp-xenial-amd64/files",
504- params={"path": "/etc/hosts"}, stream=True)
505+ with mock.patch.object(LXD, "path_exists", return_value=False):
506+ LXD("1", "xenial", "amd64").start()
507+
508 files_api.post.assert_any_call(
509 params={"path": "/etc/hosts"},
510 data=(
511@@ -576,7 +579,7 @@ class TestLXD(TestCase):
512 lambda wait=False: setattr(container, "status_code", LXD_RUNNING))
513 files_api = container.api.files
514 files_api._api_endpoint = "/1.0/containers/lp-trusty-amd64/files"
515- files_api.session.get.side_effect = FakeSessionGet({
516+ existing_files = {
517 "/etc/init/mounted-dev.conf": [dedent("""\
518 start on mounted MOUNTPOINT=/dev
519 script
520@@ -584,11 +587,18 @@ class TestLXD(TestCase):
521 /sbin/MAKEDEV std fd ppp tun
522 end script
523 task
524- """).encode("UTF-8")]})
525+ """).encode("UTF-8")]}
526+ files_api.session.get.side_effect = FakeSessionGet(existing_files)
527 processes_fixture = self.useFixture(FakeProcesses())
528 processes_fixture.add(lambda _: {}, name="sudo")
529 processes_fixture.add(lambda _: {}, name="lxc")
530- LXD("1", "trusty", "amd64").start()
531+
532+ with mock.patch.object(
533+ LXD,
534+ "path_exists",
535+ side_effect=lambda path: path in existing_files
536+ ):
537+ LXD("1", "trusty", "amd64").start()
538
539 files_api.session.get.assert_any_call(
540 "/1.0/containers/lp-trusty-amd64/files",

Subscribers

People subscribed via source and target branches