Skip to content

Commit d2faad8

Browse files
authored
fix(nimbus): populate config_slug on mobile (#7367)
1 parent e4cca14 commit d2faad8

7 files changed

Lines changed: 302 additions & 48 deletions

File tree

components/nimbus/src/enrollment.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub enum NotEnrolledReason {
6161
/// The experiment enrollment is paused.
6262
EnrollmentsPaused,
6363
/// The experiment used a feature that was already under experiment.
64-
FeatureConflict,
64+
FeatureConflict { conflict_slug: Option<String> },
6565
/// The evaluator bucketing did not choose us.
6666
NotSelected,
6767
/// We are not being targeted for this experiment.
@@ -100,7 +100,7 @@ impl Display for NotEnrolledReason {
100100
NotEnrolledReason::DifferentAppName => "DifferentAppName",
101101
NotEnrolledReason::DifferentChannel => "DifferentChannel",
102102
NotEnrolledReason::EnrollmentsPaused => "EnrollmentsPaused",
103-
NotEnrolledReason::FeatureConflict => "FeatureConflict",
103+
NotEnrolledReason::FeatureConflict { .. } => "FeatureConflict",
104104
NotEnrolledReason::NotSelected => "NotSelected",
105105
NotEnrolledReason::NotTargeted => "NotTargeted",
106106
NotEnrolledReason::ExperimentsOptOut => "ExperimentsOptOut",
@@ -984,10 +984,11 @@ impl<'a> EnrollmentsEvolver<'a> {
984984

985985
for prev_enrollment in prev_enrollments {
986986
if matches!(
987-
prev_enrollment.status,
987+
&prev_enrollment.status,
988988
EnrollmentStatus::NotEnrolled {
989-
reason: NotEnrolledReason::FeatureConflict
989+
reason: NotEnrolledReason::FeatureConflict { conflict_slug },
990990
}
991+
if conflict_slug.is_some()
991992
) {
992993
continue;
993994
}
@@ -1066,7 +1067,9 @@ impl<'a> EnrollmentsEvolver<'a> {
10661067
next_enrollments.push(ExperimentEnrollment {
10671068
slug: slug.clone(),
10681069
status: EnrollmentStatus::NotEnrolled {
1069-
reason: NotEnrolledReason::FeatureConflict,
1070+
reason: NotEnrolledReason::FeatureConflict {
1071+
conflict_slug: Some(needed_features_in_use[0].slug.clone()),
1072+
},
10701073
},
10711074
});
10721075

@@ -1094,10 +1097,11 @@ impl<'a> EnrollmentsEvolver<'a> {
10941097

10951098
if prev_enrollment.is_none()
10961099
|| matches!(
1097-
prev_enrollment.unwrap().status,
1100+
&prev_enrollment.unwrap().status,
10981101
EnrollmentStatus::NotEnrolled {
1099-
reason: NotEnrolledReason::FeatureConflict
1102+
reason: NotEnrolledReason::FeatureConflict { conflict_slug }
11001103
}
1104+
if conflict_slug.is_some()
11011105
)
11021106
{
11031107
let next_enrollment = match self.evolve_enrollment(

components/nimbus/src/metrics.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::enrollment::ExperimentEnrollment;
1010
use crate::enrollment::PreviousGeckoPrefState;
1111
pub use crate::metrics::detail::*;
1212

13+
use crate::enrollment::NotEnrolledReason;
1314
#[derive(Serialize, Deserialize, Clone)]
1415
#[cfg_attr(test, derive(Debug))]
1516
pub struct EnrollmentStatusExtraDef {
@@ -31,6 +32,7 @@ impl From<ExperimentEnrollment> for EnrollmentStatusExtraDef {
3132
let mut branch_value: Option<String> = None;
3233
let mut reason_value: Option<String> = None;
3334
let mut error_value: Option<String> = None;
35+
let mut conflict_slug_value: Option<String> = None;
3436
match &enrollment.status {
3537
EnrollmentStatus::Enrolled { reason, branch, .. } => {
3638
branch_value = Some(branch.to_owned());
@@ -42,6 +44,9 @@ impl From<ExperimentEnrollment> for EnrollmentStatusExtraDef {
4244
}
4345
EnrollmentStatus::NotEnrolled { reason } => {
4446
reason_value = Some(reason.to_string());
47+
if let NotEnrolledReason::FeatureConflict { conflict_slug } = reason {
48+
conflict_slug_value = conflict_slug.clone();
49+
}
4550
}
4651
EnrollmentStatus::WasEnrolled { branch, .. } => branch_value = Some(branch.to_owned()),
4752
EnrollmentStatus::Error { reason } => {
@@ -50,7 +55,7 @@ impl From<ExperimentEnrollment> for EnrollmentStatusExtraDef {
5055
}
5156
EnrollmentStatusExtraDef {
5257
branch: branch_value,
53-
conflict_slug: None,
58+
conflict_slug: conflict_slug_value,
5459
error_string: error_value,
5560
reason: reason_value,
5661
slug: Some(enrollment.slug),

components/nimbus/src/stateful/enrollment.rs

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use std::iter;
66

77
use crate::enrollment::Participation;
88
use crate::enrollment::{
9-
EnrollmentChangeEvent, EnrollmentChangeEventType, EnrollmentsEvolver, ExperimentEnrollment,
9+
DisqualifiedReason, EnrolledReason, EnrollmentChangeEvent, EnrollmentChangeEventType,
10+
EnrollmentsEvolver, ExperimentEnrollment, NotEnrolledReason, PreviousGeckoPrefState,
1011
map_enrollments,
1112
};
1213
use crate::error::{Result, debug, warn};
@@ -295,3 +296,107 @@ pub fn reset_telemetry_identifiers(
295296
}
296297
Ok(events)
297298
}
299+
300+
pub mod v3 {
301+
// This module contains legacy enrollment structs that mirror the schema of enrollments stored as they were in the database as of v3. These are used for deserializing pre-migration enrollments during the migration process, and should not be used outside of that context.
302+
303+
use super::*;
304+
use serde::{Deserialize, Serialize};
305+
306+
#[derive(Deserialize, Serialize, Debug, Clone, Hash, Eq, PartialEq)]
307+
pub enum LegacyNotEnrolledReason {
308+
DifferentAppName,
309+
DifferentChannel,
310+
EnrollmentsPaused,
311+
FeatureConflict,
312+
NotSelected,
313+
NotTargeted,
314+
OptOut,
315+
}
316+
317+
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
318+
pub struct LegacyExperimentEnrollment {
319+
pub slug: String,
320+
pub status: LegacyEnrollmentStatus,
321+
}
322+
323+
#[derive(Deserialize, Serialize, Debug, Clone, Hash, Eq, PartialEq)]
324+
pub enum LegacyEnrollmentStatus {
325+
Enrolled {
326+
reason: EnrolledReason,
327+
branch: String,
328+
#[serde(skip_serializing_if = "Option::is_none")]
329+
prev_gecko_pref_states: Option<Vec<PreviousGeckoPrefState>>,
330+
},
331+
NotEnrolled {
332+
reason: LegacyNotEnrolledReason,
333+
},
334+
Disqualified {
335+
reason: DisqualifiedReason,
336+
branch: String,
337+
},
338+
WasEnrolled {
339+
branch: String,
340+
experiment_ended_at: u64,
341+
},
342+
Error {
343+
reason: String,
344+
},
345+
}
346+
347+
impl From<LegacyNotEnrolledReason> for NotEnrolledReason {
348+
#[allow(deprecated)]
349+
fn from(value: LegacyNotEnrolledReason) -> Self {
350+
match value {
351+
LegacyNotEnrolledReason::DifferentAppName => NotEnrolledReason::DifferentAppName,
352+
LegacyNotEnrolledReason::DifferentChannel => NotEnrolledReason::DifferentChannel,
353+
LegacyNotEnrolledReason::EnrollmentsPaused => NotEnrolledReason::EnrollmentsPaused,
354+
LegacyNotEnrolledReason::FeatureConflict => NotEnrolledReason::FeatureConflict {
355+
conflict_slug: None,
356+
},
357+
LegacyNotEnrolledReason::NotSelected => NotEnrolledReason::NotSelected,
358+
LegacyNotEnrolledReason::NotTargeted => NotEnrolledReason::NotTargeted,
359+
LegacyNotEnrolledReason::OptOut => NotEnrolledReason::OptOut,
360+
}
361+
}
362+
}
363+
364+
impl From<LegacyEnrollmentStatus> for EnrollmentStatus {
365+
fn from(value: LegacyEnrollmentStatus) -> Self {
366+
match value {
367+
LegacyEnrollmentStatus::Enrolled {
368+
reason,
369+
branch,
370+
prev_gecko_pref_states,
371+
} => EnrollmentStatus::Enrolled {
372+
reason,
373+
branch,
374+
prev_gecko_pref_states,
375+
},
376+
LegacyEnrollmentStatus::NotEnrolled { reason } => EnrollmentStatus::NotEnrolled {
377+
reason: reason.into(),
378+
},
379+
LegacyEnrollmentStatus::Disqualified { reason, branch } => {
380+
EnrollmentStatus::Disqualified { reason, branch }
381+
}
382+
LegacyEnrollmentStatus::WasEnrolled {
383+
branch,
384+
experiment_ended_at,
385+
} => EnrollmentStatus::WasEnrolled {
386+
branch,
387+
experiment_ended_at,
388+
},
389+
LegacyEnrollmentStatus::Error { reason } => EnrollmentStatus::Error { reason },
390+
}
391+
}
392+
}
393+
394+
impl From<LegacyExperimentEnrollment> for ExperimentEnrollment {
395+
fn from(value: LegacyExperimentEnrollment) -> Self {
396+
ExperimentEnrollment {
397+
slug: value.slug,
398+
status: value.status.into(),
399+
}
400+
}
401+
}
402+
}

components/nimbus/src/stateful/persistence.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ use std::fs;
1010
use std::path::Path;
1111
use std::sync::Arc;
1212

13+
use crate::enrollment::ExperimentEnrollment;
1314
use crate::error::{ErrorCode, NimbusError, Result, debug, info, warn};
1415
use crate::metrics::{DatabaseLoadExtraDef, DatabaseMigrationExtraDef, MetricsHandler};
16+
use crate::stateful::enrollment::v3;
1517

1618
// This uses the lmdb backend for rkv, which is unstable.
1719
// We use it for now since glean didn't seem to have trouble with it (although
@@ -27,7 +29,7 @@ use crate::metrics::{DatabaseLoadExtraDef, DatabaseMigrationExtraDef, MetricsHan
2729
pub(crate) const DB_KEY_DB_VERSION: &str = "db_version";
2830

2931
/// The current database version.
30-
pub(crate) const DB_VERSION: u16 = 3;
32+
pub(crate) const DB_VERSION: u16 = 4;
3133

3234
pub(crate) const DB_KEY_DB_WAS_CORRUPT: &str = "db-was-corrupt";
3335

@@ -554,6 +556,14 @@ impl Database {
554556
DatabaseMigrationReason::Upgrade,
555557
)?;
556558

559+
self.apply_migration(
560+
writer,
561+
|writer| self.migrate_v3_to_v4(writer),
562+
&mut current_version,
563+
4,
564+
DatabaseMigrationReason::Upgrade,
565+
)?;
566+
557567
Ok(())
558568
}
559569

@@ -677,6 +687,23 @@ impl Database {
677687
Ok(())
678688
}
679689

690+
fn migrate_v3_to_v4(&self, writer: &mut Writer) -> Result<()> {
691+
info!("Upgrading from version 3 to version 4");
692+
693+
let enrollment_store = self.get_store(StoreId::Enrollments);
694+
let enrollments: Vec<v3::LegacyExperimentEnrollment> =
695+
enrollment_store.try_collect_all(writer)?;
696+
697+
let enrollments: Vec<ExperimentEnrollment> =
698+
enrollments.into_iter().map(Into::into).collect();
699+
700+
for enrollment in enrollments {
701+
enrollment_store.put(writer, &enrollment.slug, &enrollment)?;
702+
}
703+
704+
Ok(())
705+
}
706+
680707
/// Gets a Store object, which used with the writer returned by
681708
/// `self.write()` to update the database in a transaction.
682709
pub fn get_store(&self, store_id: StoreId) -> &SingleStore {

0 commit comments

Comments
 (0)