1use crate::prelude::*;
2
3use crate::migration_data;
4use kanidm_proto::internal::{
5    DomainUpgradeCheckItem as ProtoDomainUpgradeCheckItem,
6    DomainUpgradeCheckReport as ProtoDomainUpgradeCheckReport,
7    DomainUpgradeCheckStatus as ProtoDomainUpgradeCheckStatus,
8};
9
10use super::ServerPhase;
11
12impl QueryServer {
13    #[instrument(level = "info", name = "system_initialisation", skip_all)]
14    pub async fn initialise_helper(
15        &self,
16        ts: Duration,
17        domain_target_level: DomainVersion,
18    ) -> Result<(), OperationError> {
19        let mut write_txn = self.write(ts).await?;
22
23        write_txn.upgrade_reindex(SYSTEM_INDEX_VERSION)?;
27
28        write_txn
38            .initialise_schema_core()
39            .and_then(|_| write_txn.reload())?;
40
41        let db_domain_version = match write_txn.internal_search_uuid(UUID_DOMAIN_INFO) {
44            Ok(e) => Ok(e.get_ava_single_uint32(Attribute::Version).unwrap_or(0)),
45            Err(OperationError::NoMatchingEntries) => Ok(0),
46            Err(r) => Err(r),
47        }?;
48
49        debug!(?db_domain_version, "Before setting internal domain info");
50
51        if db_domain_version == 0 {
52            debug_assert!(domain_target_level <= DOMAIN_MAX_LEVEL);
56
57            match domain_target_level {
60                DOMAIN_LEVEL_8 => write_txn.migrate_domain_7_to_8()?,
61                DOMAIN_LEVEL_9 => write_txn.migrate_domain_8_to_9()?,
62                DOMAIN_LEVEL_10 => write_txn.migrate_domain_9_to_10()?,
63                DOMAIN_LEVEL_11 => write_txn.migrate_domain_10_to_11()?,
64                DOMAIN_LEVEL_12 => write_txn.migrate_domain_11_to_12()?,
65                _ => {
66                    error!("Invalid requested domain target level for server bootstrap");
67                    debug_assert!(false);
68                    return Err(OperationError::MG0009InvalidTargetLevelForBootstrap);
69                }
70            }
71        } else {
72            write_txn.force_domain_reload();
81
82            write_txn.reload()?;
83
84            write_txn.set_phase(ServerPhase::SchemaReady);
87
88            write_txn.force_schema_reload();
97
98            write_txn.reload()?;
100
101            write_txn.set_phase(ServerPhase::DomainInfoReady);
103        }
104
105        let domain_info_version = write_txn.get_domain_version();
112        let domain_patch_level = write_txn.get_domain_patch_level();
113        let domain_development_taint = write_txn.get_domain_development_taint();
114        debug!(
115            ?db_domain_version,
116            ?domain_patch_level,
117            ?domain_development_taint,
118            "After setting internal domain info"
119        );
120
121        let mut reload_required = false;
122
123        if domain_info_version < domain_target_level {
125            write_txn
126                .internal_apply_domain_migration(domain_target_level)
127                .map(|()| {
128                    warn!("Domain level has been raised to {}", domain_target_level);
129                })?;
130            if domain_info_version != 0 {
136                reload_required = true;
137            }
138        } else if domain_development_taint {
139            write_txn.domain_remigrate(DOMAIN_PREVIOUS_TGT_LEVEL)?;
150
151            reload_required = true;
152        }
153
154        if domain_patch_level < DOMAIN_TGT_PATCH_LEVEL {
157            write_txn
158                .internal_modify_uuid(
159                    UUID_DOMAIN_INFO,
160                    &ModifyList::new_purge_and_set(
161                        Attribute::PatchLevel,
162                        Value::new_uint32(DOMAIN_TGT_PATCH_LEVEL),
163                    ),
164                )
165                .map(|()| {
166                    warn!(
167                        "Domain patch level has been raised to {}",
168                        domain_patch_level
169                    );
170                })?;
171
172            reload_required = true;
173        };
174
175        if reload_required {
179            write_txn.reload()?;
180        }
181
182        let current_devel_flag = option_env!("KANIDM_PRE_RELEASE").is_some();
185        if current_devel_flag {
186            warn!("Domain Development Taint mode is enabled");
187        }
188        if domain_development_taint != current_devel_flag {
189            write_txn.internal_modify_uuid(
190                UUID_DOMAIN_INFO,
191                &ModifyList::new_purge_and_set(
192                    Attribute::DomainDevelopmentTaint,
193                    Value::Bool(current_devel_flag),
194                ),
195            )?;
196        }
197
198        write_txn.set_phase(ServerPhase::Running);
200
201        write_txn.commit()?;
204
205        debug!("Database version check and migrations success! ☀️  ");
206        Ok(())
207    }
208}
209
210impl QueryServerWriteTransaction<'_> {
211    pub(crate) fn internal_apply_domain_migration(
214        &mut self,
215        to_level: u32,
216    ) -> Result<(), OperationError> {
217        assert!(to_level > self.get_domain_version());
218        self.internal_modify_uuid(
219            UUID_DOMAIN_INFO,
220            &ModifyList::new_purge_and_set(Attribute::Version, Value::new_uint32(to_level)),
221        )
222        .and_then(|()| self.reload())
223    }
224
225    fn internal_migrate_or_create_batch(
226        &mut self,
227        msg: &str,
228        entries: Vec<EntryInitNew>,
229    ) -> Result<(), OperationError> {
230        let r: Result<(), _> = entries
231            .into_iter()
232            .try_for_each(|entry| self.internal_migrate_or_create(entry));
233
234        if let Err(err) = r {
235            error!(?err, msg);
236            debug_assert!(false);
237        }
238
239        Ok(())
240    }
241
242    #[instrument(level = "debug", skip_all)]
243    fn internal_migrate_or_create(
253        &mut self,
254        e: Entry<EntryInit, EntryNew>,
255    ) -> Result<(), OperationError> {
256        self.internal_migrate_or_create_ignore_attrs(e, &[])
257    }
258
259    #[instrument(level = "trace", skip_all)]
263    fn internal_migrate_or_create_ignore_attrs(
264        &mut self,
265        mut e: Entry<EntryInit, EntryNew>,
266        attrs: &[Attribute],
267    ) -> Result<(), OperationError> {
268        trace!("operating on {:?}", e.get_uuid());
269
270        let Some(filt) = e.filter_from_attrs(&[Attribute::Uuid]) else {
271            return Err(OperationError::FilterGeneration);
272        };
273
274        trace!("search {:?}", filt);
275
276        let results = self.internal_search(filt.clone())?;
277
278        if results.is_empty() {
279            self.internal_create(vec![e])
281        } else if results.len() == 1 {
282            for attr in attrs.iter() {
284                e.remove_ava(attr);
285            }
286
287            match e.gen_modlist_assert(&self.schema) {
289                Ok(modlist) => {
290                    trace!(?modlist);
292                    self.internal_modify(&filt, &modlist)
293                }
294                Err(e) => Err(OperationError::SchemaViolation(e)),
295            }
296        } else {
297            admin_error!(
298                "Invalid Result Set - Expected One Entry for {:?} - {:?}",
299                filt,
300                results
301            );
302            Err(OperationError::InvalidDbState)
303        }
304    }
305
306    #[instrument(level = "info", skip_all)]
308    pub(crate) fn migrate_domain_7_to_8(&mut self) -> Result<(), OperationError> {
309        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_9 {
310            error!("Unable to raise domain level from 8 to 9.");
311            return Err(OperationError::MG0004DomainLevelInDevelopment);
312        }
313
314        self.internal_migrate_or_create_batch(
316            "phase 1 - schema attrs",
317            migration_data::dl8::phase_1_schema_attrs(),
318        )?;
319
320        self.internal_migrate_or_create_batch(
321            "phase 2 - schema classes",
322            migration_data::dl8::phase_2_schema_classes(),
323        )?;
324
325        self.reload()?;
327
328        self.reindex(false)?;
330
331        self.set_phase(ServerPhase::SchemaReady);
333
334        self.internal_migrate_or_create_batch(
335            "phase 3 - key provider",
336            migration_data::dl8::phase_3_key_provider(),
337        )?;
338
339        self.reload()?;
341
342        self.internal_migrate_or_create_batch(
343            "phase 4 - system entries",
344            migration_data::dl8::phase_4_system_entries(),
345        )?;
346
347        self.reload()?;
349
350        self.set_phase(ServerPhase::DomainInfoReady);
352
353        self.internal_migrate_or_create_batch(
355            "phase 5 - builtin admin entries",
356            migration_data::dl8::phase_5_builtin_admin_entries()?,
357        )?;
358
359        self.internal_migrate_or_create_batch(
360            "phase 6 - builtin not admin entries",
361            migration_data::dl8::phase_6_builtin_non_admin_entries()?,
362        )?;
363
364        self.internal_migrate_or_create_batch(
365            "phase 7 - builtin access control profiles",
366            migration_data::dl8::phase_7_builtin_access_control_profiles(),
367        )?;
368
369        self.reload()?;
371
372        Ok(())
373    }
374
375    #[instrument(level = "info", skip_all)]
377    pub(crate) fn migrate_domain_8_to_9(&mut self) -> Result<(), OperationError> {
378        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_9 {
379            error!("Unable to raise domain level from 8 to 9.");
380            return Err(OperationError::MG0004DomainLevelInDevelopment);
381        }
382
383        self.internal_migrate_or_create_batch(
385            "phase 1 - schema attrs",
386            migration_data::dl9::phase_1_schema_attrs(),
387        )?;
388
389        self.internal_migrate_or_create_batch(
390            "phase 2 - schema classes",
391            migration_data::dl9::phase_2_schema_classes(),
392        )?;
393
394        self.reload()?;
396
397        self.reindex(false)?;
399
400        self.set_phase(ServerPhase::SchemaReady);
402
403        self.internal_migrate_or_create_batch(
404            "phase 3 - key provider",
405            migration_data::dl9::phase_3_key_provider(),
406        )?;
407
408        self.reload()?;
410
411        self.internal_migrate_or_create_batch(
412            "phase 4 - system entries",
413            migration_data::dl9::phase_4_system_entries(),
414        )?;
415
416        self.reload()?;
418
419        self.set_phase(ServerPhase::DomainInfoReady);
421
422        self.internal_migrate_or_create_batch(
424            "phase 5 - builtin admin entries",
425            migration_data::dl9::phase_5_builtin_admin_entries()?,
426        )?;
427
428        self.internal_migrate_or_create_batch(
429            "phase 6 - builtin not admin entries",
430            migration_data::dl9::phase_6_builtin_non_admin_entries()?,
431        )?;
432
433        self.internal_migrate_or_create_batch(
434            "phase 7 - builtin access control profiles",
435            migration_data::dl9::phase_7_builtin_access_control_profiles(),
436        )?;
437
438        self.reload()?;
440
441        Ok(())
442    }
443
444    #[instrument(level = "info", skip_all)]
448    pub(crate) fn migrate_domain_patch_level_2(&mut self) -> Result<(), OperationError> {
449        admin_warn!("applying domain patch 2.");
450
451        debug_assert!(*self.phase >= ServerPhase::SchemaReady);
452
453        let idm_data = migration_data::dl9::phase_7_builtin_access_control_profiles();
454
455        idm_data
456            .into_iter()
457            .try_for_each(|entry| self.internal_migrate_or_create(entry))
458            .map_err(|err| {
459                error!(?err, "migrate_domain_patch_level_2 -> Error");
460                err
461            })?;
462
463        self.reload()?;
464
465        Ok(())
466    }
467
468    #[instrument(level = "info", skip_all)]
470    pub(crate) fn migrate_domain_9_to_10(&mut self) -> Result<(), OperationError> {
471        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_9 {
472            error!("Unable to raise domain level from 9 to 10.");
473            return Err(OperationError::MG0004DomainLevelInDevelopment);
474        }
475
476        self.internal_migrate_or_create_batch(
478            "phase 1 - schema attrs",
479            migration_data::dl10::phase_1_schema_attrs(),
480        )?;
481
482        self.internal_migrate_or_create_batch(
483            "phase 2 - schema classes",
484            migration_data::dl10::phase_2_schema_classes(),
485        )?;
486
487        self.reload()?;
489
490        self.reindex(false)?;
493
494        self.set_phase(ServerPhase::SchemaReady);
498
499        self.internal_migrate_or_create_batch(
500            "phase 3 - key provider",
501            migration_data::dl10::phase_3_key_provider(),
502        )?;
503
504        self.reload()?;
506
507        self.internal_migrate_or_create_batch(
508            "phase 4 - system entries",
509            migration_data::dl10::phase_4_system_entries(),
510        )?;
511
512        self.reload()?;
514
515        self.set_phase(ServerPhase::DomainInfoReady);
517
518        self.internal_migrate_or_create_batch(
520            "phase 5 - builtin admin entries",
521            migration_data::dl10::phase_5_builtin_admin_entries()?,
522        )?;
523
524        self.internal_migrate_or_create_batch(
525            "phase 6 - builtin not admin entries",
526            migration_data::dl10::phase_6_builtin_non_admin_entries()?,
527        )?;
528
529        self.internal_migrate_or_create_batch(
530            "phase 7 - builtin access control profiles",
531            migration_data::dl10::phase_7_builtin_access_control_profiles(),
532        )?;
533
534        self.reload()?;
535
536        debug!("START OAUTH2 MIGRATION");
539
540        let all_oauth2_rs_entries = self.internal_search(filter!(f_eq(
542            Attribute::Class,
543            EntryClass::OAuth2ResourceServer.into()
544        )))?;
545
546        if !all_oauth2_rs_entries.is_empty() {
547            let entry_iter = all_oauth2_rs_entries.iter().map(|tgt_entry| {
548                let entry_uuid = tgt_entry.get_uuid();
549                let mut modlist = ModifyList::new_list(vec![
550                    Modify::Present(Attribute::Class, EntryClass::KeyObject.to_value()),
551                    Modify::Present(Attribute::Class, EntryClass::KeyObjectJwtEs256.to_value()),
552                    Modify::Present(Attribute::Class, EntryClass::KeyObjectJweA128GCM.to_value()),
553                    Modify::Purged(Attribute::OAuth2RsTokenKey),
555                    Modify::Purged(Attribute::Es256PrivateKeyDer),
556                    Modify::Purged(Attribute::Rs256PrivateKeyDer),
557                ]);
558
559                trace!(?tgt_entry);
560
561                if let Some(es256_private_der) =
563                    tgt_entry.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer)
564                {
565                    modlist.push_mod(Modify::Present(
566                        Attribute::KeyActionImportJwsEs256,
567                        Value::PrivateBinary(es256_private_der.to_vec()),
568                    ))
569                } else {
570                    warn!("Unable to migrate es256 key");
571                }
572
573                let has_rs256 = tgt_entry
574                    .get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable)
575                    .unwrap_or(false);
576
577                if has_rs256 {
580                    modlist.push_mod(Modify::Present(
581                        Attribute::Class,
582                        EntryClass::KeyObjectJwtEs256.to_value(),
583                    ));
584
585                    if let Some(rs256_private_der) =
586                        tgt_entry.get_ava_single_private_binary(Attribute::Rs256PrivateKeyDer)
587                    {
588                        modlist.push_mod(Modify::Present(
589                            Attribute::KeyActionImportJwsRs256,
590                            Value::PrivateBinary(rs256_private_der.to_vec()),
591                        ))
592                    } else {
593                        warn!("Unable to migrate rs256 key");
594                    }
595                }
596
597                (entry_uuid, modlist)
598            });
599
600            self.internal_batch_modify(entry_iter)?;
601        }
602
603        self.reload()?;
605
606        Ok(())
609    }
610
611    #[instrument(level = "info", skip_all)]
613    pub(crate) fn migrate_domain_10_to_11(&mut self) -> Result<(), OperationError> {
614        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_10 {
615            error!("Unable to raise domain level from 10 to 11.");
616            return Err(OperationError::MG0004DomainLevelInDevelopment);
617        }
618
619        self.internal_migrate_or_create_batch(
621            "phase 1 - schema attrs",
622            migration_data::dl11::phase_1_schema_attrs(),
623        )?;
624
625        self.internal_migrate_or_create_batch(
626            "phase 2 - schema classes",
627            migration_data::dl11::phase_2_schema_classes(),
628        )?;
629
630        self.reload()?;
632
633        self.reindex(false)?;
636
637        self.set_phase(ServerPhase::SchemaReady);
641
642        self.internal_migrate_or_create_batch(
643            "phase 3 - key provider",
644            migration_data::dl11::phase_3_key_provider(),
645        )?;
646
647        self.reload()?;
649
650        self.internal_migrate_or_create_batch(
651            "phase 4 - system entries",
652            migration_data::dl11::phase_4_system_entries(),
653        )?;
654
655        self.reload()?;
657
658        self.set_phase(ServerPhase::DomainInfoReady);
660
661        self.internal_migrate_or_create_batch(
663            "phase 5 - builtin admin entries",
664            migration_data::dl11::phase_5_builtin_admin_entries()?,
665        )?;
666
667        self.internal_migrate_or_create_batch(
668            "phase 6 - builtin not admin entries",
669            migration_data::dl11::phase_6_builtin_non_admin_entries()?,
670        )?;
671
672        self.internal_migrate_or_create_batch(
673            "phase 7 - builtin access control profiles",
674            migration_data::dl11::phase_7_builtin_access_control_profiles(),
675        )?;
676
677        self.reload()?;
678
679        Ok(())
680    }
681
682    #[instrument(level = "info", skip_all)]
684    pub(crate) fn migrate_domain_11_to_12(&mut self) -> Result<(), OperationError> {
685        if !cfg!(test) && DOMAIN_TGT_LEVEL < DOMAIN_LEVEL_11 {
686            error!("Unable to raise domain level from 11 to 12.");
687            return Err(OperationError::MG0004DomainLevelInDevelopment);
688        }
689
690        Ok(())
691    }
692
693    #[instrument(level = "info", skip_all)]
694    pub(crate) fn initialise_schema_core(&mut self) -> Result<(), OperationError> {
695        admin_debug!("initialise_schema_core -> start ...");
696        let entries = self.schema.to_entries();
698
699        let r: Result<_, _> = entries.into_iter().try_for_each(|e| {
703            trace!(?e, "init schema entry");
704            self.internal_migrate_or_create(e)
705        });
706        if r.is_ok() {
707            admin_debug!("initialise_schema_core -> Ok!");
708        } else {
709            admin_error!(?r, "initialise_schema_core -> Error");
710        }
711        debug_assert!(r.is_ok());
713        r
714    }
715}
716
717impl QueryServerReadTransaction<'_> {
718    pub fn domain_upgrade_check(
720        &mut self,
721    ) -> Result<ProtoDomainUpgradeCheckReport, OperationError> {
722        let d_info = &self.d_info;
723
724        let name = d_info.d_name.clone();
725        let uuid = d_info.d_uuid;
726        let current_level = d_info.d_vers;
727        let upgrade_level = DOMAIN_TGT_NEXT_LEVEL;
728
729        let mut report_items = Vec::with_capacity(1);
730
731        if current_level <= DOMAIN_LEVEL_7 && upgrade_level >= DOMAIN_LEVEL_8 {
732            let item = self
733                .domain_upgrade_check_7_to_8_security_keys()
734                .map_err(|err| {
735                    error!(
736                        ?err,
737                        "Failed to perform domain upgrade check 7 to 8 - security-keys"
738                    );
739                    err
740                })?;
741            report_items.push(item);
742
743            let item = self
744                .domain_upgrade_check_7_to_8_oauth2_strict_redirect_uri()
745                .map_err(|err| {
746                    error!(
747                        ?err,
748                        "Failed to perform domain upgrade check 7 to 8 - oauth2-strict-redirect_uri"
749                    );
750                    err
751                })?;
752            report_items.push(item);
753        }
754
755        Ok(ProtoDomainUpgradeCheckReport {
756            name,
757            uuid,
758            current_level,
759            upgrade_level,
760            report_items,
761        })
762    }
763
764    pub(crate) fn domain_upgrade_check_7_to_8_security_keys(
765        &mut self,
766    ) -> Result<ProtoDomainUpgradeCheckItem, OperationError> {
767        let filter = filter!(f_and!([
768            f_eq(Attribute::Class, EntryClass::Account.into()),
769            f_pres(Attribute::PrimaryCredential),
770        ]));
771
772        let results = self.internal_search(filter)?;
773
774        let affected_entries = results
775            .into_iter()
776            .filter_map(|entry| {
777                if entry
778                    .get_ava_single_credential(Attribute::PrimaryCredential)
779                    .map(|cred| cred.has_securitykey())
780                    .unwrap_or_default()
781                {
782                    Some(entry.get_display_id())
783                } else {
784                    None
785                }
786            })
787            .collect::<Vec<_>>();
788
789        let status = if affected_entries.is_empty() {
790            ProtoDomainUpgradeCheckStatus::Pass7To8SecurityKeys
791        } else {
792            ProtoDomainUpgradeCheckStatus::Fail7To8SecurityKeys
793        };
794
795        Ok(ProtoDomainUpgradeCheckItem {
796            status,
797            from_level: DOMAIN_LEVEL_7,
798            to_level: DOMAIN_LEVEL_8,
799            affected_entries,
800        })
801    }
802
803    pub(crate) fn domain_upgrade_check_7_to_8_oauth2_strict_redirect_uri(
804        &mut self,
805    ) -> Result<ProtoDomainUpgradeCheckItem, OperationError> {
806        let filter = filter!(f_and!([
807            f_eq(Attribute::Class, EntryClass::OAuth2ResourceServer.into()),
808            f_andnot(f_pres(Attribute::OAuth2StrictRedirectUri)),
809        ]));
810
811        let results = self.internal_search(filter)?;
812
813        let affected_entries = results
814            .into_iter()
815            .map(|entry| entry.get_display_id())
816            .collect::<Vec<_>>();
817
818        let status = if affected_entries.is_empty() {
819            ProtoDomainUpgradeCheckStatus::Pass7To8Oauth2StrictRedirectUri
820        } else {
821            ProtoDomainUpgradeCheckStatus::Fail7To8Oauth2StrictRedirectUri
822        };
823
824        Ok(ProtoDomainUpgradeCheckItem {
825            status,
826            from_level: DOMAIN_LEVEL_7,
827            to_level: DOMAIN_LEVEL_8,
828            affected_entries,
829        })
830    }
831}
832
833#[cfg(test)]
834mod tests {
835    use crate::prelude::*;
837
838    #[qs_test]
839    async fn test_init_idempotent_schema_core(server: &QueryServer) {
840        {
841            let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
843            assert!(server_txn.initialise_schema_core().is_ok());
844        }
845        {
846            let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
847            assert!(server_txn.initialise_schema_core().is_ok());
848            assert!(server_txn.initialise_schema_core().is_ok());
849            assert!(server_txn.commit().is_ok());
850        }
851        {
852            let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
854            assert!(server_txn.initialise_schema_core().is_ok());
855        }
856        {
857            let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
859            assert!(server_txn.initialise_schema_core().is_ok());
860            assert!(server_txn.commit().is_ok());
861        }
862    }
863
864    #[qs_test(domain_level=DOMAIN_LEVEL_8)]
865    async fn test_migrations_dl8_dl9(server: &QueryServer) {
866        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
867
868        let db_domain_version = write_txn
869            .internal_search_uuid(UUID_DOMAIN_INFO)
870            .expect("unable to access domain entry")
871            .get_ava_single_uint32(Attribute::Version)
872            .expect("Attribute Version not present");
873
874        assert_eq!(db_domain_version, DOMAIN_LEVEL_8);
875
876        write_txn.commit().expect("Unable to commit");
877
878        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
885
886        write_txn
890            .internal_apply_domain_migration(DOMAIN_LEVEL_9)
891            .expect("Unable to set domain level to version 9");
892
893        write_txn.commit().expect("Unable to commit");
896    }
897
898    #[qs_test(domain_level=DOMAIN_LEVEL_9)]
899    async fn test_migrations_dl9_dl10(server: &QueryServer) {
900        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
901
902        let db_domain_version = write_txn
903            .internal_search_uuid(UUID_DOMAIN_INFO)
904            .expect("unable to access domain entry")
905            .get_ava_single_uint32(Attribute::Version)
906            .expect("Attribute Version not present");
907
908        assert_eq!(db_domain_version, DOMAIN_LEVEL_9);
909
910        write_txn.commit().expect("Unable to commit");
911
912        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
919
920        write_txn
924            .internal_apply_domain_migration(DOMAIN_LEVEL_10)
925            .expect("Unable to set domain level to version 10");
926
927        write_txn.commit().expect("Unable to commit");
930    }
931
932    #[qs_test(domain_level=DOMAIN_LEVEL_10)]
933    async fn test_migrations_dl10_dl11(server: &QueryServer) {
934        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
935
936        let db_domain_version = write_txn
937            .internal_search_uuid(UUID_DOMAIN_INFO)
938            .expect("unable to access domain entry")
939            .get_ava_single_uint32(Attribute::Version)
940            .expect("Attribute Version not present");
941
942        assert_eq!(db_domain_version, DOMAIN_LEVEL_10);
943
944        write_txn.commit().expect("Unable to commit");
945
946        let mut write_txn = server.write(duration_from_epoch_now()).await.unwrap();
953
954        write_txn
958            .internal_apply_domain_migration(DOMAIN_LEVEL_11)
959            .expect("Unable to set domain level to version 11");
960
961        write_txn.commit().expect("Unable to commit");
964    }
965}