|
6 | 6 | import os
|
7 | 7 | import subprocess
|
8 | 8 | import time
|
| 9 | +from abc import ABC, abstractmethod |
9 | 10 | from enum import Enum
|
10 | 11 | from pathlib import Path
|
11 | 12 | from subprocess import check_output
|
@@ -46,78 +47,142 @@ def partuuid_and_disk_path(rootfs_ubuntu_22, disk_path):
|
46 | 47 | return (partuuid, disk_path)
|
47 | 48 |
|
48 | 49 |
|
49 |
| -CROSVM_CTR_SOCKET_NAME = "crosvm_ctr.socket" |
50 |
| - |
51 |
| - |
52 |
| -def spawn_vhost_user_backend( |
53 |
| - vm, |
54 |
| - host_mem_path, |
55 |
| - socket_path, |
56 |
| - readonly=False, |
57 |
| - backend=VhostUserBlkBackendType.CROSVM, |
58 |
| -): |
59 |
| - """Spawn vhost-user-blk backend.""" |
60 |
| - |
61 |
| - uid = vm.jailer.uid |
62 |
| - gid = vm.jailer.gid |
63 |
| - host_vhost_user_socket_path = Path(vm.chroot()) / socket_path.strip("/") |
64 |
| - |
65 |
| - if backend == VhostUserBlkBackendType.QEMU: |
| 50 | +class VhostUserBlkBackend(ABC): |
| 51 | + """vhost-user-blk backend base class""" |
| 52 | + |
| 53 | + @classmethod |
| 54 | + def get_all_subclasses(cls): |
| 55 | + """Get all subclasses of the class.""" |
| 56 | + subclasses = {} |
| 57 | + for subclass in cls.__subclasses__(): |
| 58 | + subclasses[subclass.__name__] = subclass |
| 59 | + subclasses.update(subclass.get_all_subclasses()) |
| 60 | + return subclasses |
| 61 | + |
| 62 | + @classmethod |
| 63 | + def with_backend(cls, backend: VhostUserBlkBackendType, *args, **kwargs): |
| 64 | + """Get a backend of a specific type.""" |
| 65 | + subclasses = cls.get_all_subclasses() |
| 66 | + return subclasses[backend.value + cls.__name__](*args, **kwargs) |
| 67 | + |
| 68 | + def __init__( |
| 69 | + self, |
| 70 | + host_mem_path, |
| 71 | + chroot, |
| 72 | + backend_id, |
| 73 | + readonly, |
| 74 | + ): |
| 75 | + self.host_mem_path = host_mem_path |
| 76 | + self.socket_path = Path(chroot) / f"{backend_id}_vhost_user.sock" |
| 77 | + self.readonly = readonly |
| 78 | + self.proc = None |
| 79 | + |
| 80 | + def spawn(self, uid, gid): |
| 81 | + """ |
| 82 | + Spawn a backend. |
| 83 | +
|
| 84 | + Return socket path in the jail that can be used with FC API. |
| 85 | + """ |
| 86 | + assert not self.proc, "backend already spawned" |
| 87 | + args = self._spawn_cmd() |
| 88 | + proc = subprocess.Popen(args) |
| 89 | + |
| 90 | + # Give the backend time to initialise. |
| 91 | + time.sleep(1) |
| 92 | + |
| 93 | + assert proc is not None and proc.poll() is None, "backend is not up" |
| 94 | + assert self.socket_path.exists() |
| 95 | + |
| 96 | + os.chown(self.socket_path, uid, gid) |
| 97 | + |
| 98 | + self.proc = proc |
| 99 | + |
| 100 | + return str(Path("/") / os.path.basename(self.socket_path)) |
| 101 | + |
| 102 | + @abstractmethod |
| 103 | + def _spawn_cmd(self): |
| 104 | + """Return a spawn command for the backend""" |
| 105 | + return "" |
| 106 | + |
| 107 | + @abstractmethod |
| 108 | + def resize(self, new_size): |
| 109 | + """Resize the vhost-user-backed drive""" |
| 110 | + |
| 111 | + def pin(self, cpu_id: int): |
| 112 | + """Pin the vhost-user backend to a CPU list.""" |
| 113 | + return utils.ProcessManager.set_cpu_affinity(self.proc.pid, [cpu_id]) |
| 114 | + |
| 115 | + def kill(self): |
| 116 | + """Kill the backend""" |
| 117 | + if self.proc.poll() is None: |
| 118 | + self.proc.terminate() |
| 119 | + self.proc.wait() |
| 120 | + os.remove(self.socket_path) |
| 121 | + assert not os.path.exists(self.socket_path) |
| 122 | + |
| 123 | + |
| 124 | +class QemuVhostUserBlkBackend(VhostUserBlkBackend): |
| 125 | + """vhost-user-blk backend implementaiton for Qemu backend""" |
| 126 | + |
| 127 | + def _spawn_cmd(self): |
66 | 128 | args = [
|
67 | 129 | "vhost-user-blk",
|
68 | 130 | "--socket-path",
|
69 |
| - host_vhost_user_socket_path, |
| 131 | + self.socket_path, |
70 | 132 | "--blk-file",
|
71 |
| - host_mem_path, |
| 133 | + self.host_mem_path, |
72 | 134 | ]
|
73 |
| - if readonly: |
| 135 | + if self.readonly: |
74 | 136 | args.append("--read-only")
|
75 |
| - elif backend == VhostUserBlkBackendType.CROSVM: |
76 |
| - crosvm_ctr_socket_path = Path(vm.chroot()) / CROSVM_CTR_SOCKET_NAME.strip("/") |
77 |
| - ro = ",ro" if readonly else "" |
| 137 | + return args |
| 138 | + |
| 139 | + def resize(self, new_size): |
| 140 | + raise NotImplementedError("not supported for Qemu backend") |
| 141 | + |
| 142 | + |
| 143 | +class CrosvmVhostUserBlkBackend(VhostUserBlkBackend): |
| 144 | + """vhost-user-blk backend implementaiton for crosvm backend""" |
| 145 | + |
| 146 | + def __init__( |
| 147 | + self, |
| 148 | + host_mem_path, |
| 149 | + chroot, |
| 150 | + backend_id, |
| 151 | + readonly=False, |
| 152 | + ): |
| 153 | + super().__init__( |
| 154 | + host_mem_path, |
| 155 | + chroot, |
| 156 | + backend_id, |
| 157 | + readonly, |
| 158 | + ) |
| 159 | + self.ctr_socket_path = Path(chroot) / f"{backend_id}_ctr.sock" |
| 160 | + |
| 161 | + def _spawn_cmd(self): |
| 162 | + ro = ",ro" if self.readonly else "" |
78 | 163 | args = [
|
79 | 164 | "crosvm",
|
80 | 165 | "--log-level",
|
81 | 166 | "off",
|
82 | 167 | "devices",
|
83 | 168 | "--disable-sandbox",
|
84 | 169 | "--control-socket",
|
85 |
| - crosvm_ctr_socket_path, |
| 170 | + self.ctr_socket_path, |
86 | 171 | "--block",
|
87 |
| - f"vhost={host_vhost_user_socket_path},path={host_mem_path}{ro}", |
| 172 | + f"vhost={self.socket_path},path={self.host_mem_path}{ro}", |
88 | 173 | ]
|
89 |
| - if os.path.exists(crosvm_ctr_socket_path): |
90 |
| - os.remove(crosvm_ctr_socket_path) |
91 |
| - else: |
92 |
| - assert False, f"unknown vhost-user-blk backend `{backend}`" |
93 |
| - proc = subprocess.Popen(args) |
| 174 | + return args |
94 | 175 |
|
95 |
| - # Give the backend time to initialise. |
96 |
| - time.sleep(1) |
| 176 | + def resize(self, new_size): |
| 177 | + assert self.proc, "backend is not spawned" |
| 178 | + assert self.ctr_socket_path.exists() |
97 | 179 |
|
98 |
| - assert proc is not None and proc.poll() is None, "backend is not up" |
| 180 | + utils.run_cmd( |
| 181 | + f"crosvm disk resize 0 {new_size * 1024 * 1024} {self.ctr_socket_path}" |
| 182 | + ) |
99 | 183 |
|
100 |
| - with utils.chroot(vm.chroot()): |
101 |
| - # The backend will create the socket path with root rights. |
102 |
| - # Change rights to the jailer's. |
103 |
| - os.chown(socket_path, uid, gid) |
104 |
| - |
105 |
| - return proc |
106 |
| - |
107 |
| - |
108 |
| -def resize_vhost_user_drive(vm, new_size): |
109 |
| - """ |
110 |
| - Resize vhost-user-blk drive and send config change notification. |
111 |
| -
|
112 |
| - This only works with the crosvm vhost-user-blk backend. |
113 |
| - New size is in MB. |
114 |
| - """ |
115 |
| - |
116 |
| - crosvm_ctr_socket_path = Path(vm.chroot()) / CROSVM_CTR_SOCKET_NAME.strip("/") |
117 |
| - assert os.path.exists( |
118 |
| - crosvm_ctr_socket_path |
119 |
| - ), "crosvm backend must be spawned first" |
120 |
| - |
121 |
| - utils.run_cmd( |
122 |
| - f"crosvm disk resize 0 {new_size * 1024 * 1024} {crosvm_ctr_socket_path}" |
123 |
| - ) |
| 184 | + def kill(self): |
| 185 | + super().kill() |
| 186 | + assert self.proc.poll() is not None |
| 187 | + os.remove(self.ctr_socket_path) |
| 188 | + assert not os.path.exists(self.ctr_socket_path) |
0 commit comments