← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad-buildd:cpuinfo-workaround into launchpad-buildd:master

 

Colin Watson has proposed merging ~cjwatson/launchpad-buildd:cpuinfo-workaround into launchpad-buildd:master.

Commit message:
Work around hangs in Rust-based snap builds for armhf

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad-buildd/+git/launchpad-buildd/+merge/428923

LXCFS provides `/proc/cpuinfo` as a FUSE overlay inside containers to filter visible CPUs based on cgroups.  On armhf, this results in the wrong CPU features being visible to guests, because LXCFS itself is an arm64 binary.  This causes Rust-based snap builds to hang on armhf, because the combination of armhf userspace and a `/proc/cpuinfo` that lacks the `neon` feature causes them to attempt to run ARMv6 code.

For now, work around this as suggested by the LXD maintainer by unmounting `/proc/cpuinfo` inside the container.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad-buildd:cpuinfo-workaround into launchpad-buildd:master.
diff --git a/debian/changelog b/debian/changelog
index f19778c..c9f4cc4 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+launchpad-buildd (220) UNRELEASED; urgency=medium
+
+  * Work around https://github.com/lxc/lxcfs/issues/553 by unmounting
+    /proc/cpuinfo in LXD-based armhf builds.
+
+ -- Colin Watson <cjwatson@xxxxxxxxxx>  Thu, 25 Aug 2022 17:58:34 +0100
+
 launchpad-buildd (219) focal; urgency=medium
 
   * Provide additional package repositories for CI builds rather than replacing
diff --git a/lpbuildd/target/lxd.py b/lpbuildd/target/lxd.py
index 0307a06..fbb6f14 100644
--- a/lpbuildd/target/lxd.py
+++ b/lpbuildd/target/lxd.py
@@ -501,6 +501,16 @@ class LXD(Backend):
             ["ln", "-s", "/dev/null",
              "/etc/systemd/system/snapd.refresh.timer"])
 
+        if self.arch == "armhf":
+            # Work around https://github.com/lxc/lxcfs/issues/553.  In
+            # principle that could result in over-reporting the number of
+            # available CPU cores, but that isn't a concern in
+            # launchpad-buildd.
+            try:
+                self.run(["umount", "/proc/cpuinfo"])
+            except subprocess.CalledProcessError:
+                pass
+
     def run(self, args, cwd=None, env=None, input_text=None, get_output=False,
             echo=False, return_process=False, **kwargs):
         """See `Backend`."""
diff --git a/lpbuildd/target/tests/test_lxd.py b/lpbuildd/target/tests/test_lxd.py
index 93c854d..8258d0f 100644
--- a/lpbuildd/target/tests/test_lxd.py
+++ b/lpbuildd/target/tests/test_lxd.py
@@ -43,6 +43,7 @@ from lpbuildd.target.lxd import (
     policy_rc_d,
     )
 from lpbuildd.target.tests.testfixtures import CarefulFakeProcessFixture
+from lpbuildd.util import get_arch_bits
 
 
 LXD_RUNNING = 103
@@ -372,7 +373,9 @@ class TestLXD(TestCase):
             print("host resolv.conf", file=f)
         os.chmod("/etc/resolv.conf", 0o644)
 
-    def test_start(self, with_dm0=True):
+    # XXX cjwatson 2022-08-25: Refactor this to use some more sensible kind
+    # of test parameterization.
+    def test_start(self, arch="amd64", with_dm0=True, unmounts_cpuinfo=False):
         self.fakeFS()
         DM_BLOCK_MAJOR = random.randrange(128, 255)
         if with_dm0:
@@ -388,7 +391,7 @@ class TestLXD(TestCase):
         container.start.side_effect = (
             lambda wait=False: setattr(container, "status_code", LXD_RUNNING))
         files_api = container.api.files
-        files_api._api_endpoint = "/1.0/containers/lp-xenial-amd64/files"
+        files_api._api_endpoint = f"/1.0/containers/lp-xenial-{arch}/files"
         files_api.session.get.side_effect = FakeSessionGet({
             "/etc/hosts": [b"127.0.0.1\tlocalhost\n"],
             })
@@ -412,7 +415,7 @@ class TestLXD(TestCase):
         processes_fixture.add(lambda _: {}, name="lxc")
         processes_fixture.add(
             FakeHostname("example", "example.buildd"), name="hostname")
-        LXD("1", "xenial", "amd64").start()
+        LXD("1", "xenial", arch).start()
 
         self.assert_correct_profile()
 
@@ -420,7 +423,8 @@ class TestLXD(TestCase):
         iptables = ["sudo", "iptables", "-w"]
         iptables_comment = [
             "-m", "comment", "--comment", "managed by launchpad-buildd"]
-        lxc = ["lxc", "exec", "lp-xenial-amd64", "--", "linux64"]
+        setarch_cmd = "linux64" if get_arch_bits(arch) == 64 else "linux32"
+        lxc = ["lxc", "exec", f"lp-xenial-{arch}", "--", setarch_cmd]
         expected_args = [
             Equals(ip + ["link", "add", "dev", "lpbuilddbr0",
                          "type", "bridge"]),
@@ -480,17 +484,19 @@ class TestLXD(TestCase):
                 ["ln", "-s", "/dev/null",
                  "/etc/systemd/system/snapd.refresh.timer"]),
             ])
+        if unmounts_cpuinfo:
+            expected_args.append(Equals(lxc + ["umount", "/proc/cpuinfo"]))
         self.assertThat(
             [proc._args["args"] for proc in processes_fixture.procs],
             MatchesListwise(expected_args))
 
         client.containers.create.assert_called_once_with({
-            "name": "lp-xenial-amd64",
+            "name": f"lp-xenial-{arch}",
             "profiles": ["lpbuildd"],
-            "source": {"type": "image", "alias": "lp-xenial-amd64"},
+            "source": {"type": "image", "alias": f"lp-xenial-{arch}"},
             }, wait=True)
         files_api.session.get.assert_any_call(
-            "/1.0/containers/lp-xenial-amd64/files",
+            f"/1.0/containers/lp-xenial-{arch}/files",
             params={"path": "/etc/hosts"}, stream=True)
         files_api.post.assert_any_call(
             params={"path": "/etc/hosts"},
@@ -511,7 +517,7 @@ class TestLXD(TestCase):
             data=policy_rc_d.encode("UTF-8"),
             headers={"X-LXD-uid": "0", "X-LXD-gid": "0", "X-LXD-mode": "0755"})
         files_api.session.get.assert_any_call(
-            "/1.0/containers/lp-xenial-amd64/files",
+            f"/1.0/containers/lp-xenial-{arch}/files",
             params={"path": "/etc/init/mounted-dev.conf"}, stream=True)
         self.assertNotIn(
             "/etc/init/mounted-dev.override",
@@ -525,7 +531,7 @@ class TestLXD(TestCase):
         self.assertEqual(LXD_RUNNING, container.status_code)
 
     def test_start_no_dm0(self):
-        self.test_start(False)
+        self.test_start(with_dm0=False)
 
     def test_start_missing_etc_hosts(self):
         self.fakeFS()
@@ -598,6 +604,9 @@ class TestLXD(TestCase):
                 """).encode("UTF-8"),
             headers={"X-LXD-uid": "0", "X-LXD-gid": "0", "X-LXD-mode": "0644"})
 
+    def test_start_armhf_unmounts_cpuinfo(self):
+        self.test_start(arch="armhf", unmounts_cpuinfo=True)
+
     def test_run(self):
         processes_fixture = self.useFixture(FakeProcesses())
         processes_fixture.add(lambda _: {}, name="lxc")