Skip to main content

fuchsia_storage_benchmarks/
block_devices.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use async_trait::async_trait;
6use block_matcher::{BlockDeviceMatcher, Guid, create_random_guid, find_block_device};
7use fidl::endpoints::{
8    DiscoverableProtocolMarker as _, Proxy, create_proxy, create_request_stream,
9};
10use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
11use fidl_fuchsia_io as fio;
12use fidl_fuchsia_storage_block::BlockMarker;
13use fidl_fuchsia_storage_partitions as fpartitions;
14use fs_management::Fvm;
15use fs_management::filesystem::{
16    BlockConnector, DirBasedBlockConnector, ServingMultiVolumeFilesystem,
17};
18use fs_management::format::constants::{
19    BENCHMARK_FVM_TYPE_GUID, BENCHMARK_FVM_VOLUME_NAME, PAD_RW_PARTITION_LABEL,
20};
21use fuchsia_async as fasync;
22use fuchsia_component::client::{Service, connect_to_protocol, connect_to_protocol_at_dir_root};
23use std::sync::Arc;
24use storage_benchmarks::block_device::BlockDevice;
25use storage_benchmarks::{BlockDeviceConfig, BlockDeviceFactory};
26
27const BENCHMARK_FVM_SIZE_BYTES: u64 = 160 * 1024 * 1024;
28// 8MiB is the default slice size; use it so the test FVM partition matches the performance of the
29// system FVM partition (so they are interchangeable).
30// Note that this only affects the performance of minfs and blobfs, since these two filesystems are
31// the only ones that dynamically allocate from FVM.
32const BENCHMARK_FVM_SLICE_SIZE_BYTES: u64 = 8 * 1024 * 1024;
33
34// On systems which don't have FVM (i.e. Fxblob), we create an FVM partition the test can use, with
35// this GUID.  See connect_to_test_fvm for details.
36
37const BENCHMARK_TYPE_GUID: &Guid = &[
38    0x67, 0x45, 0x23, 0x01, 0xab, 0x89, 0xef, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
39];
40const BENCHMARK_VOLUME_NAME: &str = "benchmark";
41
42/// Returns the exposed directory of the volume, as well as the task running Crypt for the volume
43/// (if configured in `config`).
44pub async fn create_fvm_volume(
45    fvm: &fidl_fuchsia_fs_startup::VolumesProxy,
46    instance_guid: [u8; 16],
47    config: &BlockDeviceConfig,
48) -> (fio::DirectoryProxy, Option<fasync::Task<()>>) {
49    loop {
50        let (crypt, crypt_task) = if config.use_zxcrypt {
51            let (crypt, stream) = create_request_stream::<fidl_fuchsia_fxfs::CryptMarker>();
52            let task = fasync::Task::spawn(async {
53                if let Err(err) =
54                    zxcrypt_crypt::run_crypt_service(crypt_policy::Policy::Null, stream).await
55                {
56                    log::error!(err:?; "Crypt service failure");
57                }
58            });
59            (Some(crypt), Some(task))
60        } else {
61            (None, None)
62        };
63
64        let (volume_dir, server_end) = create_proxy::<fio::DirectoryMarker>();
65        let res = fvm
66            .create(
67                BENCHMARK_VOLUME_NAME,
68                server_end,
69                CreateOptions {
70                    initial_size: config.volume_size,
71                    type_guid: Some(BENCHMARK_TYPE_GUID.clone()),
72                    guid: Some(instance_guid),
73                    ..Default::default()
74                },
75                MountOptions { crypt, ..Default::default() },
76            )
77            .await
78            .expect("FIDL error")
79            .map_err(zx::Status::from_raw);
80
81        // If the FVM already exists, remove it and try again.
82        if res == Err(zx::Status::ALREADY_EXISTS) {
83            fvm.remove(BENCHMARK_VOLUME_NAME)
84                .await
85                .expect("FIDL error")
86                .map_err(zx::Status::from_raw)
87                .expect("Failed to remove volume");
88            continue;
89        }
90
91        res.expect("Failed to create volume");
92        return (volume_dir, crypt_task);
93    }
94}
95
96/// A factory for volumes which the benchmarks run in.  If the system has an FVM, benchmarks run in
97/// a volume in the system FVM.  Otherwise, they run out of a partition in the system GPT (and might
98/// still include a hermetic FVM instance, if the benchmark needs it to run).
99pub enum BenchmarkVolumeFactory {
100    SystemFvm(
101        Box<dyn Send + Sync + Fn() -> fidl_fuchsia_fs_startup::VolumesProxy>,
102        Box<dyn Send + Sync + Fn() -> fio::DirectoryProxy>,
103    ),
104    SystemGpt(Arc<fpartitions::PartitionServiceProxy>),
105}
106
107struct RawBlockDeviceInGpt(Arc<fpartitions::PartitionServiceProxy>);
108
109impl BlockDevice for RawBlockDeviceInGpt {
110    fn connector(&self) -> Box<dyn BlockConnector> {
111        Box::new(self.0.clone())
112    }
113}
114
115#[async_trait]
116impl BlockDeviceFactory for BenchmarkVolumeFactory {
117    async fn create_block_device(&self, config: &BlockDeviceConfig) -> Box<dyn BlockDevice> {
118        let instance_guid = create_random_guid();
119        match self {
120            Self::SystemFvm(volumes_connector, _) => {
121                let volumes = volumes_connector();
122                Box::new(Self::create_fvm_volume(volumes, instance_guid, config).await)
123            }
124            Self::SystemGpt(partition_service) => {
125                if config.requires_fvm {
126                    Box::new(
127                        Self::create_fvm_instance_and_volume(
128                            partition_service.clone(),
129                            instance_guid,
130                            config,
131                        )
132                        .await,
133                    )
134                } else {
135                    Box::new(RawBlockDeviceInGpt(partition_service.clone()))
136                }
137            }
138        }
139    }
140}
141
142impl BenchmarkVolumeFactory {
143    /// Creates a factory for volumes in which benchmarks should run in based on the provided
144    /// configuration.  Uses various capabilities in the incoming namespace of the process.
145    pub async fn from_config(fxfs_blob: bool) -> BenchmarkVolumeFactory {
146        if fxfs_blob {
147            let partitions = Service::open(fpartitions::PartitionServiceMarker).unwrap();
148            let manager = connect_to_protocol::<fpartitions::PartitionsManagerMarker>().unwrap();
149            let instance =
150                BenchmarkVolumeFactory::connect_to_test_partition(partitions, manager).await;
151            assert!(
152                instance.is_some(),
153                "Failed to open or create testing FVM in GPT.  \
154                    Perhaps the system doesn't have a GPT-formatted block device?"
155            );
156            instance.unwrap()
157        } else {
158            let volumes_connector = Box::new(move || {
159                connect_to_protocol::<fidl_fuchsia_fs_startup::VolumesMarker>().unwrap()
160            });
161            let volumes_dir_connector = {
162                Box::new(move || {
163                    fuchsia_fs::directory::open_in_namespace("volumes", fio::PERM_READABLE).unwrap()
164                })
165            };
166            BenchmarkVolumeFactory::connect_to_system_fvm(volumes_connector, volumes_dir_connector)
167                .unwrap()
168        }
169    }
170
171    /// Connects to the system FVM component.
172    pub fn connect_to_system_fvm(
173        volumes_connector: Box<dyn Send + Sync + Fn() -> fidl_fuchsia_fs_startup::VolumesProxy>,
174        volumes_dir_connector: Box<dyn Send + Sync + Fn() -> fio::DirectoryProxy>,
175    ) -> Option<BenchmarkVolumeFactory> {
176        Some(BenchmarkVolumeFactory::SystemFvm(volumes_connector, volumes_dir_connector))
177    }
178
179    // Creates and connects to the partition reserved for benchmarks, or adds it to the GPT if
180    // absent.  The partition will be unformatted and should be reformatted explicitly before being
181    // used for a benchmark.
182    pub async fn connect_to_test_partition(
183        service: Service<fpartitions::PartitionServiceMarker>,
184        manager: fpartitions::PartitionsManagerProxy,
185    ) -> Option<BenchmarkVolumeFactory> {
186        let connector = if let Some(connector) = find_block_device(
187            &[
188                BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
189                BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
190            ],
191            service.clone().enumerate().await.expect("Failed to enumerate partitions").into_iter(),
192        )
193        .await
194        .expect("Error while searching for benchmark-fvm")
195        {
196            // If the test partition already exists, just use it.
197            connector
198        } else if let Some(connector) = find_block_device(
199            &[BlockDeviceMatcher::Name(PAD_RW_PARTITION_LABEL)],
200            service.clone().enumerate().await.expect("Failed to enumerate partitions").into_iter(),
201        )
202        .await
203        .expect("Error while searching for pad_rw")
204        {
205            // New partitions can't be created on sorrel but we can use the pad_rw partition.
206            connector
207        } else {
208            // Otherwise, create the test partition in the GPT.
209            let info =
210                manager.get_block_info().await.expect("FIDL error").expect("get_block_info failed");
211            let transaction = manager
212                .create_transaction()
213                .await
214                .expect("FIDL error")
215                .map_err(zx::Status::from_raw)
216                .expect("create_transaction failed");
217            let request = fpartitions::PartitionsManagerAddPartitionRequest {
218                transaction: Some(transaction.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap()),
219                name: Some(BENCHMARK_FVM_VOLUME_NAME.to_string()),
220                type_guid: Some(fidl_fuchsia_storage_block::Guid {
221                    value: BENCHMARK_FVM_TYPE_GUID.clone(),
222                }),
223                num_blocks: Some(BENCHMARK_FVM_SIZE_BYTES / info.1 as u64),
224                ..Default::default()
225            };
226            manager
227                .add_partition(request)
228                .await
229                .expect("FIDL error")
230                .map_err(zx::Status::from_raw)
231                .expect("add_partition failed");
232            manager
233                .commit_transaction(transaction)
234                .await
235                .expect("FIDL error")
236                .map_err(zx::Status::from_raw)
237                .expect("add_partition failed");
238            let service_instances =
239                service.enumerate().await.expect("Failed to enumerate partitions");
240            log::info!("len {}", service_instances.len());
241            find_block_device(
242                &[
243                    BlockDeviceMatcher::Name(BENCHMARK_FVM_VOLUME_NAME),
244                    BlockDeviceMatcher::TypeGuid(&BENCHMARK_FVM_TYPE_GUID),
245                ],
246                service_instances.into_iter(),
247            )
248            .await
249            .expect("Failed to find block device")?
250        };
251
252        Some(BenchmarkVolumeFactory::SystemGpt(Arc::new(connector)))
253    }
254
255    #[cfg(test)]
256    pub async fn contains_fvm_volume(&self, name: &str) -> bool {
257        match self {
258            Self::SystemFvm(_, volumes_dir_connector) => {
259                let dir = volumes_dir_connector();
260                fuchsia_fs::directory::dir_contains(&dir, name).await.unwrap()
261            }
262            // If we're using a system GPT, the FVM instance is created on the fly, so volumes are
263            // too.
264            _ => false,
265        }
266    }
267
268    async fn create_fvm_volume(
269        volumes: fidl_fuchsia_fs_startup::VolumesProxy,
270        instance_guid: [u8; 16],
271        config: &BlockDeviceConfig,
272    ) -> FvmVolume {
273        let (volume_dir, crypt_task) = create_fvm_volume(&volumes, instance_guid, config).await;
274        let volumes = volumes.into_client_end().unwrap().into_sync_proxy();
275        FvmVolume {
276            destroy_fn: Some(Box::new(move || {
277                volumes
278                    .remove(BENCHMARK_VOLUME_NAME, zx::MonotonicInstant::INFINITE)
279                    .unwrap()
280                    .map_err(zx::Status::from_raw)
281            })),
282            volume_dir: Some(volume_dir),
283            fvm_instance: None,
284            block_path: format!("svc/{}", BlockMarker::PROTOCOL_NAME),
285            crypt_task,
286        }
287    }
288
289    async fn create_fvm_instance_and_volume(
290        partition: Arc<fpartitions::PartitionServiceProxy>,
291        instance_guid: [u8; 16],
292        config: &BlockDeviceConfig,
293    ) -> FvmVolume {
294        let mut fs = fs_management::filesystem::Filesystem::from_boxed_config(
295            Box::new(partition),
296            Box::new(Fvm { slice_size: BENCHMARK_FVM_SLICE_SIZE_BYTES, ..Fvm::default() }),
297        );
298        fs.format().await.expect("Failed to format FVM");
299        let fvm_instance = fs.serve_multi_volume().await.expect("Failed to serve FVM");
300        let volumes = connect_to_protocol_at_dir_root::<fidl_fuchsia_fs_startup::VolumesMarker>(
301            fvm_instance.exposed_dir(),
302        )
303        .unwrap();
304
305        let (volume_dir, crypt_task) = create_fvm_volume(&volumes, instance_guid, config).await;
306        FvmVolume {
307            destroy_fn: None,
308            volume_dir: Some(volume_dir),
309            fvm_instance: Some(fvm_instance),
310            block_path: format!("svc/{}", BlockMarker::PROTOCOL_NAME),
311            crypt_task,
312        }
313    }
314}
315
316/// A block device created on top of an FVM instance.
317pub struct FvmVolume {
318    destroy_fn: Option<Box<dyn Send + Sync + FnOnce() -> Result<(), zx::Status>>>,
319    fvm_instance: Option<ServingMultiVolumeFilesystem>,
320    volume_dir: Option<fio::DirectoryProxy>,
321    crypt_task: Option<fasync::Task<()>>,
322    // The path in `volume_dir` to connect to when opening a new Block connection.
323    block_path: String,
324}
325
326impl BlockDevice for FvmVolume {
327    fn connector(&self) -> Box<dyn BlockConnector> {
328        let volume_dir = fuchsia_fs::directory::clone(self.volume_dir.as_ref().unwrap()).unwrap();
329        Box::new(DirBasedBlockConnector::new(volume_dir, self.block_path.clone()))
330    }
331}
332
333impl Drop for FvmVolume {
334    fn drop(&mut self) {
335        self.volume_dir = None;
336        self.fvm_instance = None;
337        self.crypt_task = None;
338        if let Some(destroy_fn) = self.destroy_fn.take() {
339            destroy_fn().expect("Failed to destroy FVM volume");
340        }
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347    use crate::testing::{RAMDISK_FVM_SLICE_SIZE, RamdiskFactory};
348    use block_client::RemoteBlockClient;
349    use fidl_fuchsia_fs_startup::VolumesMarker;
350    use fs_management::Gpt;
351    use ramdevice_client::{RamdiskClient, RamdiskClientBuilder};
352    use std::sync::Arc;
353    use vmo_backed_block_server::VmoBackedServer;
354
355    const BLOCK_SIZE: u64 = 4 * 1024;
356    const BLOCK_COUNT: u64 = 1024;
357    // We need more blocks for the GPT version of the test, since the library will by default
358    // allocate 128MiB for the embedded FVM.  This is big enough for a 192MiB device.
359    const GPT_BLOCK_COUNT: u64 = 49152;
360
361    #[fuchsia::test]
362    async fn ramdisk_create_block_device_with_zxcrypt() {
363        let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
364        let _ = ramdisk_factory
365            .create_block_device(&BlockDeviceConfig {
366                requires_fvm: true,
367                use_zxcrypt: true,
368                volume_size: None,
369            })
370            .await;
371    }
372
373    #[fuchsia::test]
374    async fn ramdisk_create_block_device_without_zxcrypt() {
375        let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
376        let _ = ramdisk_factory
377            .create_block_device(&BlockDeviceConfig {
378                requires_fvm: true,
379                use_zxcrypt: false,
380                volume_size: None,
381            })
382            .await;
383    }
384
385    #[fuchsia::test]
386    async fn ramdisk_create_block_device_without_volume_size() {
387        let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
388        let ramdisk = ramdisk_factory
389            .create_block_device(&BlockDeviceConfig {
390                requires_fvm: true,
391                use_zxcrypt: false,
392                volume_size: None,
393            })
394            .await;
395        let volume_info = ramdisk
396            .connector()
397            .connect_block()
398            .unwrap()
399            .into_proxy()
400            .get_volume_info()
401            .await
402            .unwrap();
403        zx::ok(volume_info.0).unwrap();
404        let volume_info = volume_info.2.unwrap();
405        assert_eq!(volume_info.partition_slice_count, 1);
406    }
407
408    #[fuchsia::test]
409    async fn ramdisk_create_block_device_with_volume_size() {
410        let ramdisk_factory = RamdiskFactory::new(BLOCK_SIZE, BLOCK_COUNT);
411        let ramdisk = ramdisk_factory
412            .create_block_device(&BlockDeviceConfig {
413                requires_fvm: false,
414                use_zxcrypt: false,
415                volume_size: Some(RAMDISK_FVM_SLICE_SIZE as u64 * 3),
416            })
417            .await;
418        let volume_info = ramdisk
419            .connector()
420            .connect_block()
421            .unwrap()
422            .into_proxy()
423            .get_volume_info()
424            .await
425            .unwrap();
426        zx::ok(volume_info.0).unwrap();
427        let volume_info = volume_info.2.unwrap();
428        assert_eq!(volume_info.partition_slice_count, 3);
429    }
430
431    async fn init_gpt(block_size: u32, block_count: u64) -> zx::Vmo {
432        let vmo = zx::Vmo::create(block_size as u64 * block_count).unwrap();
433        let server = VmoBackedServer::from_vmo(
434            block_size,
435            vmo.create_child(zx::VmoChildOptions::REFERENCE, 0, 0).unwrap(),
436        )
437        .expect("Failed to create VmoBackedServer");
438        let (client, server_end) =
439            fidl::endpoints::create_proxy::<fidl_fuchsia_storage_block::BlockMarker>();
440
441        let _task =
442            fasync::Task::spawn(async move { server.serve(server_end.into_stream()).await });
443        let client = Arc::new(RemoteBlockClient::new(client).await.unwrap());
444        gpt::Gpt::format(client.clone(), vec![gpt::PartitionInfo::nil(); 128])
445            .await
446            .expect("format failed");
447        vmo
448    }
449
450    struct FvmTestConfig {
451        fxblob_enabled: bool,
452    }
453
454    /// Retains test state.
455    ///
456    /// The ramdisk is running in an isolated devmgr instance, but the volume managers are
457    /// running in child components.
458    struct TestState(
459        #[allow(dead_code)] RamdiskClient,
460        #[allow(dead_code)] ServingMultiVolumeFilesystem,
461    );
462
463    async fn initialize(config: FvmTestConfig) -> (TestState, BenchmarkVolumeFactory) {
464        if config.fxblob_enabled {
465            // Initialize a new GPT.
466            let vmo = init_gpt(BLOCK_SIZE as u32, GPT_BLOCK_COUNT).await;
467            let ramdisk = RamdiskClientBuilder::new_with_vmo(vmo, Some(BLOCK_SIZE))
468                .build()
469                .await
470                .expect("Failed to create ramdisk");
471
472            let gpt = fs_management::filesystem::Filesystem::from_boxed_config(
473                ramdisk.connector().unwrap(),
474                Box::new(Gpt::dynamic_child()),
475            )
476            .serve_multi_volume()
477            .await
478            .expect("Failed to serve GPT");
479            let partitions =
480                Service::open_from_dir(gpt.exposed_dir(), fpartitions::PartitionServiceMarker)
481                    .unwrap();
482            let manager = connect_to_protocol_at_dir_root::<fpartitions::PartitionsManagerMarker>(
483                gpt.exposed_dir(),
484            )
485            .unwrap();
486            let fvm = BenchmarkVolumeFactory::connect_to_test_partition(partitions, manager)
487                .await
488                .expect("Failed to connect to FVM");
489            (TestState(ramdisk, gpt), fvm)
490        } else {
491            // Initialize a new FVM.
492            let ramdisk = RamdiskClientBuilder::new(BLOCK_SIZE, BLOCK_COUNT)
493                .build()
494                .await
495                .expect("Failed to create ramdisk");
496            let mut fs = fs_management::filesystem::Filesystem::from_boxed_config(
497                ramdisk.connector().unwrap(),
498                Box::new(Fvm { slice_size: RAMDISK_FVM_SLICE_SIZE, ..Fvm::dynamic_child() }),
499            );
500            fs.format().await.expect("Failed to format FVM");
501            let fvm_component = match fs.serve_multi_volume().await {
502                Ok(fvm_component) => fvm_component,
503                Err(_) => loop {},
504            };
505            let volumes_connector = {
506                let exposed_dir =
507                    fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
508                Box::new(move || {
509                    connect_to_protocol_at_dir_root::<VolumesMarker>(&exposed_dir).unwrap()
510                })
511            };
512            let volumes_dir_connector = {
513                let exposed_dir =
514                    fuchsia_fs::directory::clone(fvm_component.exposed_dir()).unwrap();
515                Box::new(move || {
516                    fuchsia_fs::directory::open_directory_async(
517                        &exposed_dir,
518                        "volumes",
519                        fio::PERM_READABLE,
520                    )
521                    .unwrap()
522                })
523            };
524            let fvm = BenchmarkVolumeFactory::connect_to_system_fvm(
525                volumes_connector,
526                volumes_dir_connector,
527            );
528            (TestState(ramdisk, fvm_component), fvm.unwrap())
529        }
530    }
531
532    async fn benchmark_volume_factory_can_find_fvm_instance(config: FvmTestConfig) {
533        let (_state, volume_factory) = initialize(config).await;
534
535        // Verify that a volume can be created.
536        volume_factory
537            .create_block_device(&BlockDeviceConfig {
538                requires_fvm: true,
539                use_zxcrypt: false,
540                volume_size: None,
541            })
542            .await;
543    }
544
545    #[fuchsia::test]
546    async fn benchmark_volume_factory_can_find_fvm_instance_fvm() {
547        benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig { fxblob_enabled: false })
548            .await;
549    }
550
551    #[fuchsia::test]
552    async fn benchmark_volume_factory_can_find_fvm_instance_gpt() {
553        benchmark_volume_factory_can_find_fvm_instance(FvmTestConfig { fxblob_enabled: true })
554            .await;
555    }
556
557    async fn dropping_an_fvm_volume_removes_the_volume(config: FvmTestConfig) {
558        let (_state, volume_factory) = initialize(config).await;
559        {
560            let _volume = volume_factory
561                .create_block_device(&BlockDeviceConfig {
562                    requires_fvm: true,
563                    use_zxcrypt: false,
564                    volume_size: None,
565                })
566                .await;
567            assert!(volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
568        };
569        assert!(!volume_factory.contains_fvm_volume(BENCHMARK_VOLUME_NAME).await);
570    }
571
572    #[fuchsia::test]
573    async fn dropping_an_fvm_volume_removes_the_volume_fvm() {
574        dropping_an_fvm_volume_removes_the_volume(FvmTestConfig { fxblob_enabled: false }).await;
575    }
576
577    async fn benchmark_volume_factory_create_block_device_with_zxcrypt(config: FvmTestConfig) {
578        let (_state, volume_factory) = initialize(config).await;
579        let _ = volume_factory
580            .create_block_device(&BlockDeviceConfig {
581                requires_fvm: true,
582                use_zxcrypt: true,
583                volume_size: None,
584            })
585            .await;
586    }
587
588    #[fuchsia::test]
589    async fn benchmark_volume_factory_create_block_device_with_zxcrypt_fvm() {
590        benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
591            fxblob_enabled: false,
592        })
593        .await;
594    }
595
596    #[fuchsia::test]
597    async fn benchmark_volume_factory_create_block_device_with_zxcrypt_gpt() {
598        benchmark_volume_factory_create_block_device_with_zxcrypt(FvmTestConfig {
599            fxblob_enabled: true,
600        })
601        .await;
602    }
603}