1*d87b258bSHyman Huang#!/usr/bin/env python3
2*d87b258bSHyman Huang# group: rw auto
3*d87b258bSHyman Huang#
4*d87b258bSHyman Huang# Test LUKS volume with detached header
5*d87b258bSHyman Huang#
6*d87b258bSHyman Huang# Copyright (C) 2024 SmartX Inc.
7*d87b258bSHyman Huang#
8*d87b258bSHyman Huang# Authors:
9*d87b258bSHyman Huang#     Hyman Huang <yong.huang@smartx.com>
10*d87b258bSHyman Huang#
11*d87b258bSHyman Huang# This program is free software; you can redistribute it and/or modify
12*d87b258bSHyman Huang# it under the terms of the GNU General Public License as published by
13*d87b258bSHyman Huang# the Free Software Foundation; either version 2 of the License, or
14*d87b258bSHyman Huang# (at your option) any later version.
15*d87b258bSHyman Huang#
16*d87b258bSHyman Huang# This program is distributed in the hope that it will be useful,
17*d87b258bSHyman Huang# but WITHOUT ANY WARRANTY; without even the implied warranty of
18*d87b258bSHyman Huang# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19*d87b258bSHyman Huang# GNU General Public License for more details.
20*d87b258bSHyman Huang#
21*d87b258bSHyman Huang# You should have received a copy of the GNU General Public License
22*d87b258bSHyman Huang# along with this program.  If not, see <http://www.gnu.org/licenses/>.
23*d87b258bSHyman Huang#
24*d87b258bSHyman Huang
25*d87b258bSHyman Huangimport os
26*d87b258bSHyman Huangimport json
27*d87b258bSHyman Huangimport iotests
28*d87b258bSHyman Huangfrom iotests import (
29*d87b258bSHyman Huang    imgfmt,
30*d87b258bSHyman Huang    qemu_img_create,
31*d87b258bSHyman Huang    qemu_img_info,
32*d87b258bSHyman Huang    QMPTestCase,
33*d87b258bSHyman Huang)
34*d87b258bSHyman Huang
35*d87b258bSHyman Huang
36*d87b258bSHyman Huangimage_size = 128 * 1024 * 1024
37*d87b258bSHyman Huang
38*d87b258bSHyman Huangluks_img = os.path.join(iotests.test_dir, "luks.img")
39*d87b258bSHyman Huangdetached_header_img1 = os.path.join(iotests.test_dir, "detached_header.img1")
40*d87b258bSHyman Huangdetached_header_img2 = os.path.join(iotests.test_dir, "detached_header.img2")
41*d87b258bSHyman Huangdetached_payload_raw_img = os.path.join(
42*d87b258bSHyman Huang    iotests.test_dir, "detached_payload_raw.img"
43*d87b258bSHyman Huang)
44*d87b258bSHyman Huangdetached_payload_qcow2_img = os.path.join(
45*d87b258bSHyman Huang    iotests.test_dir, "detached_payload_qcow2.img"
46*d87b258bSHyman Huang)
47*d87b258bSHyman Huangdetached_header_raw_img = "json:" + json.dumps(
48*d87b258bSHyman Huang    {
49*d87b258bSHyman Huang        "driver": "luks",
50*d87b258bSHyman Huang        "file": {"filename": detached_payload_raw_img},
51*d87b258bSHyman Huang        "header": {
52*d87b258bSHyman Huang            "filename": detached_header_img1,
53*d87b258bSHyman Huang        },
54*d87b258bSHyman Huang    }
55*d87b258bSHyman Huang)
56*d87b258bSHyman Huangdetached_header_qcow2_img = "json:" + json.dumps(
57*d87b258bSHyman Huang    {
58*d87b258bSHyman Huang        "driver": "luks",
59*d87b258bSHyman Huang        "file": {"filename": detached_payload_qcow2_img},
60*d87b258bSHyman Huang        "header": {"filename": detached_header_img2},
61*d87b258bSHyman Huang    }
62*d87b258bSHyman Huang)
63*d87b258bSHyman Huang
64*d87b258bSHyman Huangsecret_obj = "secret,id=sec0,data=foo"
65*d87b258bSHyman Huangluks_opts = "key-secret=sec0"
66*d87b258bSHyman Huang
67*d87b258bSHyman Huang
68*d87b258bSHyman Huangclass TestDetachedLUKSHeader(QMPTestCase):
69*d87b258bSHyman Huang    def setUp(self) -> None:
70*d87b258bSHyman Huang        self.vm = iotests.VM()
71*d87b258bSHyman Huang        self.vm.add_object(secret_obj)
72*d87b258bSHyman Huang        self.vm.launch()
73*d87b258bSHyman Huang
74*d87b258bSHyman Huang        # 1. Create the normal LUKS disk with 128M size
75*d87b258bSHyman Huang        self.vm.blockdev_create(
76*d87b258bSHyman Huang            {"driver": "file", "filename": luks_img, "size": 0}
77*d87b258bSHyman Huang        )
78*d87b258bSHyman Huang        self.vm.qmp_log(
79*d87b258bSHyman Huang            "blockdev-add",
80*d87b258bSHyman Huang            driver="file",
81*d87b258bSHyman Huang            filename=luks_img,
82*d87b258bSHyman Huang            node_name="luks-1-storage",
83*d87b258bSHyman Huang        )
84*d87b258bSHyman Huang        result = self.vm.blockdev_create(
85*d87b258bSHyman Huang            {
86*d87b258bSHyman Huang                "driver": imgfmt,
87*d87b258bSHyman Huang                "file": "luks-1-storage",
88*d87b258bSHyman Huang                "key-secret": "sec0",
89*d87b258bSHyman Huang                "size": image_size,
90*d87b258bSHyman Huang                "iter-time": 10,
91*d87b258bSHyman Huang            }
92*d87b258bSHyman Huang        )
93*d87b258bSHyman Huang        # None is expected
94*d87b258bSHyman Huang        self.assertEqual(result, None)
95*d87b258bSHyman Huang
96*d87b258bSHyman Huang        # 2. Create the LUKS disk with detached header (raw)
97*d87b258bSHyman Huang
98*d87b258bSHyman Huang        # Create detached LUKS header
99*d87b258bSHyman Huang        self.vm.blockdev_create(
100*d87b258bSHyman Huang            {"driver": "file", "filename": detached_header_img1, "size": 0}
101*d87b258bSHyman Huang        )
102*d87b258bSHyman Huang        self.vm.qmp_log(
103*d87b258bSHyman Huang            "blockdev-add",
104*d87b258bSHyman Huang            driver="file",
105*d87b258bSHyman Huang            filename=detached_header_img1,
106*d87b258bSHyman Huang            node_name="luks-2-header-storage",
107*d87b258bSHyman Huang        )
108*d87b258bSHyman Huang
109*d87b258bSHyman Huang        # Create detached LUKS raw payload
110*d87b258bSHyman Huang        self.vm.blockdev_create(
111*d87b258bSHyman Huang            {"driver": "file", "filename": detached_payload_raw_img, "size": 0}
112*d87b258bSHyman Huang        )
113*d87b258bSHyman Huang        self.vm.qmp_log(
114*d87b258bSHyman Huang            "blockdev-add",
115*d87b258bSHyman Huang            driver="file",
116*d87b258bSHyman Huang            filename=detached_payload_raw_img,
117*d87b258bSHyman Huang            node_name="luks-2-payload-storage",
118*d87b258bSHyman Huang        )
119*d87b258bSHyman Huang
120*d87b258bSHyman Huang        # Format LUKS disk with detached header
121*d87b258bSHyman Huang        result = self.vm.blockdev_create(
122*d87b258bSHyman Huang            {
123*d87b258bSHyman Huang                "driver": imgfmt,
124*d87b258bSHyman Huang                "header": "luks-2-header-storage",
125*d87b258bSHyman Huang                "file": "luks-2-payload-storage",
126*d87b258bSHyman Huang                "key-secret": "sec0",
127*d87b258bSHyman Huang                "preallocation": "full",
128*d87b258bSHyman Huang                "size": image_size,
129*d87b258bSHyman Huang                "iter-time": 10,
130*d87b258bSHyman Huang            }
131*d87b258bSHyman Huang        )
132*d87b258bSHyman Huang        self.assertEqual(result, None)
133*d87b258bSHyman Huang
134*d87b258bSHyman Huang        self.vm.shutdown()
135*d87b258bSHyman Huang
136*d87b258bSHyman Huang        # 3. Create the LUKS disk with detached header (qcow2)
137*d87b258bSHyman Huang
138*d87b258bSHyman Huang        # Create detached LUKS header using qemu-img
139*d87b258bSHyman Huang        res = qemu_img_create(
140*d87b258bSHyman Huang            "-f",
141*d87b258bSHyman Huang            "luks",
142*d87b258bSHyman Huang            "--object",
143*d87b258bSHyman Huang            secret_obj,
144*d87b258bSHyman Huang            "-o",
145*d87b258bSHyman Huang            luks_opts,
146*d87b258bSHyman Huang            "-o",
147*d87b258bSHyman Huang            "detached-header=true",
148*d87b258bSHyman Huang            detached_header_img2,
149*d87b258bSHyman Huang        )
150*d87b258bSHyman Huang        assert res.returncode == 0
151*d87b258bSHyman Huang
152*d87b258bSHyman Huang        # Create detached LUKS qcow2 payload
153*d87b258bSHyman Huang        res = qemu_img_create(
154*d87b258bSHyman Huang            "-f", "qcow2", detached_payload_qcow2_img, str(image_size)
155*d87b258bSHyman Huang        )
156*d87b258bSHyman Huang        assert res.returncode == 0
157*d87b258bSHyman Huang
158*d87b258bSHyman Huang    def tearDown(self) -> None:
159*d87b258bSHyman Huang        os.remove(luks_img)
160*d87b258bSHyman Huang        os.remove(detached_header_img1)
161*d87b258bSHyman Huang        os.remove(detached_header_img2)
162*d87b258bSHyman Huang        os.remove(detached_payload_raw_img)
163*d87b258bSHyman Huang        os.remove(detached_payload_qcow2_img)
164*d87b258bSHyman Huang
165*d87b258bSHyman Huang        # Check if there was any qemu-io run that failed
166*d87b258bSHyman Huang        if "Pattern verification failed" in self.vm.get_log():
167*d87b258bSHyman Huang            print("ERROR: Pattern verification failed:")
168*d87b258bSHyman Huang            print(self.vm.get_log())
169*d87b258bSHyman Huang            self.fail("qemu-io pattern verification failed")
170*d87b258bSHyman Huang
171*d87b258bSHyman Huang    def test_img_creation(self) -> None:
172*d87b258bSHyman Huang        # Check if the images created above are expected
173*d87b258bSHyman Huang
174*d87b258bSHyman Huang        data = qemu_img_info(luks_img)["format-specific"]
175*d87b258bSHyman Huang        self.assertEqual(data["type"], imgfmt)
176*d87b258bSHyman Huang        self.assertEqual(data["data"]["detached-header"], False)
177*d87b258bSHyman Huang
178*d87b258bSHyman Huang        data = qemu_img_info(detached_header_raw_img)["format-specific"]
179*d87b258bSHyman Huang        self.assertEqual(data["type"], imgfmt)
180*d87b258bSHyman Huang        self.assertEqual(data["data"]["detached-header"], True)
181*d87b258bSHyman Huang
182*d87b258bSHyman Huang        data = qemu_img_info(detached_header_qcow2_img)["format-specific"]
183*d87b258bSHyman Huang        self.assertEqual(data["type"], imgfmt)
184*d87b258bSHyman Huang        self.assertEqual(data["data"]["detached-header"], True)
185*d87b258bSHyman Huang
186*d87b258bSHyman Huang        # Check if preallocation works
187*d87b258bSHyman Huang        size = qemu_img_info(detached_payload_raw_img)["actual-size"]
188*d87b258bSHyman Huang        self.assertGreaterEqual(size, image_size)
189*d87b258bSHyman Huang
190*d87b258bSHyman Huang    def test_detached_luks_header(self) -> None:
191*d87b258bSHyman Huang        self.vm.launch()
192*d87b258bSHyman Huang
193*d87b258bSHyman Huang        # 1. Add the disk created above
194*d87b258bSHyman Huang
195*d87b258bSHyman Huang        # Add normal LUKS disk
196*d87b258bSHyman Huang        self.vm.qmp_log(
197*d87b258bSHyman Huang            "blockdev-add",
198*d87b258bSHyman Huang            driver="file",
199*d87b258bSHyman Huang            filename=luks_img,
200*d87b258bSHyman Huang            node_name="luks-1-storage",
201*d87b258bSHyman Huang        )
202*d87b258bSHyman Huang        result = self.vm.qmp_log(
203*d87b258bSHyman Huang            "blockdev-add",
204*d87b258bSHyman Huang            driver="luks",
205*d87b258bSHyman Huang            file="luks-1-storage",
206*d87b258bSHyman Huang            key_secret="sec0",
207*d87b258bSHyman Huang            node_name="luks-1-format",
208*d87b258bSHyman Huang        )
209*d87b258bSHyman Huang
210*d87b258bSHyman Huang        # Expected result{ "return": {} }
211*d87b258bSHyman Huang        self.assert_qmp(result, "return", {})
212*d87b258bSHyman Huang
213*d87b258bSHyman Huang        # Add detached LUKS header with raw payload
214*d87b258bSHyman Huang        self.vm.qmp_log(
215*d87b258bSHyman Huang            "blockdev-add",
216*d87b258bSHyman Huang            driver="file",
217*d87b258bSHyman Huang            filename=detached_header_img1,
218*d87b258bSHyman Huang            node_name="luks-header1-storage",
219*d87b258bSHyman Huang        )
220*d87b258bSHyman Huang
221*d87b258bSHyman Huang        self.vm.qmp_log(
222*d87b258bSHyman Huang            "blockdev-add",
223*d87b258bSHyman Huang            driver="file",
224*d87b258bSHyman Huang            filename=detached_payload_raw_img,
225*d87b258bSHyman Huang            node_name="luks-2-payload-raw-storage",
226*d87b258bSHyman Huang        )
227*d87b258bSHyman Huang
228*d87b258bSHyman Huang        result = self.vm.qmp_log(
229*d87b258bSHyman Huang            "blockdev-add",
230*d87b258bSHyman Huang            driver=imgfmt,
231*d87b258bSHyman Huang            header="luks-header1-storage",
232*d87b258bSHyman Huang            file="luks-2-payload-raw-storage",
233*d87b258bSHyman Huang            key_secret="sec0",
234*d87b258bSHyman Huang            node_name="luks-2-payload-raw-format",
235*d87b258bSHyman Huang        )
236*d87b258bSHyman Huang        self.assert_qmp(result, "return", {})
237*d87b258bSHyman Huang
238*d87b258bSHyman Huang        # Add detached LUKS header with qcow2 payload
239*d87b258bSHyman Huang        self.vm.qmp_log(
240*d87b258bSHyman Huang            "blockdev-add",
241*d87b258bSHyman Huang            driver="file",
242*d87b258bSHyman Huang            filename=detached_header_img2,
243*d87b258bSHyman Huang            node_name="luks-header2-storage",
244*d87b258bSHyman Huang        )
245*d87b258bSHyman Huang
246*d87b258bSHyman Huang        self.vm.qmp_log(
247*d87b258bSHyman Huang            "blockdev-add",
248*d87b258bSHyman Huang            driver="file",
249*d87b258bSHyman Huang            filename=detached_payload_qcow2_img,
250*d87b258bSHyman Huang            node_name="luks-3-payload-qcow2-storage",
251*d87b258bSHyman Huang        )
252*d87b258bSHyman Huang
253*d87b258bSHyman Huang        result = self.vm.qmp_log(
254*d87b258bSHyman Huang            "blockdev-add",
255*d87b258bSHyman Huang            driver=imgfmt,
256*d87b258bSHyman Huang            header="luks-header2-storage",
257*d87b258bSHyman Huang            file="luks-3-payload-qcow2-storage",
258*d87b258bSHyman Huang            key_secret="sec0",
259*d87b258bSHyman Huang            node_name="luks-3-payload-qcow2-format",
260*d87b258bSHyman Huang        )
261*d87b258bSHyman Huang        self.assert_qmp(result, "return", {})
262*d87b258bSHyman Huang
263*d87b258bSHyman Huang        # 2. Do I/O test
264*d87b258bSHyman Huang
265*d87b258bSHyman Huang        # Do some I/O to the image to see whether it still works
266*d87b258bSHyman Huang        # (Pattern verification will be checked by tearDown())
267*d87b258bSHyman Huang
268*d87b258bSHyman Huang        # Normal LUKS disk
269*d87b258bSHyman Huang        result = self.vm.qmp_log(
270*d87b258bSHyman Huang            "human-monitor-command",
271*d87b258bSHyman Huang            command_line='qemu-io luks-1-format "write -P 40 0 64k"',
272*d87b258bSHyman Huang        )
273*d87b258bSHyman Huang        self.assert_qmp(result, "return", "")
274*d87b258bSHyman Huang
275*d87b258bSHyman Huang        result = self.vm.qmp_log(
276*d87b258bSHyman Huang            "human-monitor-command",
277*d87b258bSHyman Huang            command_line='qemu-io luks-1-format "read -P 40 0 64k"',
278*d87b258bSHyman Huang        )
279*d87b258bSHyman Huang        self.assert_qmp(result, "return", "")
280*d87b258bSHyman Huang
281*d87b258bSHyman Huang        # Detached LUKS header with raw payload
282*d87b258bSHyman Huang        cmd = 'qemu-io luks-2-payload-raw-format "write -P 41 0 64k"'
283*d87b258bSHyman Huang        result = self.vm.qmp(
284*d87b258bSHyman Huang            "human-monitor-command",
285*d87b258bSHyman Huang            command_line=cmd
286*d87b258bSHyman Huang        )
287*d87b258bSHyman Huang        self.assert_qmp(result, "return", "")
288*d87b258bSHyman Huang
289*d87b258bSHyman Huang        cmd = 'qemu-io luks-2-payload-raw-format "read -P 41 0 64k"'
290*d87b258bSHyman Huang        result = self.vm.qmp(
291*d87b258bSHyman Huang            "human-monitor-command",
292*d87b258bSHyman Huang            command_line=cmd
293*d87b258bSHyman Huang        )
294*d87b258bSHyman Huang        self.assert_qmp(result, "return", "")
295*d87b258bSHyman Huang
296*d87b258bSHyman Huang        # Detached LUKS header with qcow2 payload
297*d87b258bSHyman Huang        cmd = 'qemu-io luks-3-payload-qcow2-format "write -P 42 0 64k"'
298*d87b258bSHyman Huang        result = self.vm.qmp(
299*d87b258bSHyman Huang            "human-monitor-command",
300*d87b258bSHyman Huang            command_line=cmd
301*d87b258bSHyman Huang        )
302*d87b258bSHyman Huang        self.assert_qmp(result, "return", "")
303*d87b258bSHyman Huang
304*d87b258bSHyman Huang        cmd = 'qemu-io luks-3-payload-qcow2-format "read -P 42 0 64k"'
305*d87b258bSHyman Huang        result = self.vm.qmp(
306*d87b258bSHyman Huang            "human-monitor-command",
307*d87b258bSHyman Huang            command_line=cmd
308*d87b258bSHyman Huang        )
309*d87b258bSHyman Huang        self.assert_qmp(result, "return", "")
310*d87b258bSHyman Huang
311*d87b258bSHyman Huang        self.vm.shutdown()
312*d87b258bSHyman Huang
313*d87b258bSHyman Huang
314*d87b258bSHyman Huangif __name__ == "__main__":
315*d87b258bSHyman Huang    # Test image creation and I/O
316*d87b258bSHyman Huang    iotests.main(supported_fmts=["luks"], supported_protocols=["file"])
317