def create_with_status(cls, target_status, **kwargs): experiment = cls.create(**kwargs) for status, _ in NimbusExperiment.Status.choices: if status == NimbusExperiment.Status.REVIEW.value: NimbusIsolationGroup.request_isolation_group_buckets( experiment.slug, experiment, 100, ) if status == target_status: break return NimbusExperiment.objects.get(id=experiment.id)
def test_full_isolation_group_creates_next_isolation_group_adds_bucket_range( self, ): """ Rare case: An isolation group with experiment bucket allocations exists, and the next requested bucket allocation would overflow its total bucket range, and so a an isolation group with the same name but subsequent instance ID is created. This is currently treated naively, ie does not account for possible collisions and overlaps. When this case becomes more common this will likely need to be given more thought. """ experiment = NimbusExperimentFactory.create( application=self.application) isolation_group = NimbusIsolationGroupFactory.create( name=experiment.slug, application=self.application, total=100) NimbusBucketRangeFactory(isolation_group=isolation_group, count=100) bucket = NimbusIsolationGroup.request_isolation_group_buckets( experiment.slug, experiment, 100) self.assertEqual(bucket.start, 0) self.assertEqual(bucket.end, 99) self.assertEqual(bucket.count, 100) self.assertEqual(bucket.isolation_group.name, isolation_group.name) self.assertEqual(bucket.isolation_group.instance, isolation_group.instance + 1) self.assertEqual( bucket.isolation_group.randomization_unit, self.randomization_unit, )
def nimbus_push_experiment_to_kinto(experiment_id): """ An invoked task that given a single experiment id, query it in the db, serialize it, and push its data to the configured collection. If it fails for any reason, log the error and reraise it so it will be forwarded to sentry. """ metrics.incr("push_experiment_to_kinto.started") try: experiment = NimbusExperiment.objects.get(id=experiment_id) logger.info(f"Pushing {experiment} to Kinto") kinto_client = KintoClient( NimbusExperiment.KINTO_APPLICATION_COLLECTION[ experiment.application]) if not NimbusBucketRange.objects.filter( experiment=experiment).exists(): NimbusIsolationGroup.request_isolation_group_buckets( experiment.slug, experiment, int(experiment.population_percent / Decimal("100.0") * NimbusExperiment.BUCKET_TOTAL), ) data = NimbusExperimentSerializer(experiment).data kinto_client.push_to_kinto(data) experiment.status = NimbusExperiment.Status.ACCEPTED experiment.save() generate_nimbus_changelog(experiment, get_kinto_user()) logger.info(f"{experiment} pushed to Kinto") metrics.incr("push_experiment_to_kinto.completed") except Exception as e: metrics.incr("push_experiment_to_kinto.failed") logger.info( f"Pushing experiment id {experiment_id} to Kinto failed: {e}") raise e
def test_existing_isolation_group_adds_bucket_range(self): """ Rare case: An isolation group with no buckets allocated already exists. This may become common when users can create their own isolation groups and then later assign experiments to them. """ experiment = NimbusExperimentFactory.create() isolation_group = NimbusIsolationGroupFactory.create( name=experiment.slug) bucket = NimbusIsolationGroup.request_isolation_group_buckets( experiment.slug, experiment, 100) self.assertEqual(bucket.start, 0) self.assertEqual(bucket.end, 99) self.assertEqual(bucket.count, 100) self.assertEqual(bucket.isolation_group, isolation_group)
def test_existing_isolation_group_with_matching_name_but_not_application_is_filtered( self, ): """ Now that isolation groups are bound to applications, we have to check for the case where isolation groups with the same name but different applications are treated separately. """ name = "isolation group name" NimbusIsolationGroupFactory.create( name=name, application=NimbusExperiment.Application.DESKTOP) experiment = NimbusExperimentFactory.create( name=name, slug=name, application=NimbusExperiment.Application.FENIX) bucket = NimbusIsolationGroup.request_isolation_group_buckets( name, experiment, 100) self.assertEqual(bucket.isolation_group.name, name) self.assertEqual(bucket.isolation_group.application, NimbusExperiment.Application.FENIX)
def test_existing_isolation_group_with_buckets_adds_next_bucket_range( self): """ Common case: An isolation group with experiment bucket allocations exists, and a subsequent bucket allocation is requested. This will be the common case for any experiments that share an isolation group. """ experiment = NimbusExperimentFactory.create() isolation_group = NimbusIsolationGroupFactory.create( name=experiment.slug) NimbusBucketRangeFactory.create(isolation_group=isolation_group, start=0, count=100) bucket = NimbusIsolationGroup.request_isolation_group_buckets( experiment.slug, experiment, 100) self.assertEqual(bucket.start, 100) self.assertEqual(bucket.end, 199) self.assertEqual(bucket.count, 100) self.assertEqual(bucket.isolation_group, isolation_group)
def test_empty_isolation_group_creates_isolation_group_and_bucket_range(self): """ Common case: A new empty isolation group for an experiment that is orthogonal to all other current experiments. This will likely describe most experiment launches. """ experiment = NimbusExperimentFactory.create(application=self.application) bucket = NimbusIsolationGroup.request_isolation_group_buckets( experiment.slug, experiment, 100 ) self.assertEqual(bucket.start, 0) self.assertEqual(bucket.end, 99) self.assertEqual(bucket.count, 100) self.assertEqual(bucket.isolation_group.name, experiment.slug) self.assertEqual(bucket.isolation_group.instance, 1) self.assertEqual(bucket.isolation_group.total, NimbusExperiment.BUCKET_TOTAL) self.assertEqual( bucket.isolation_group.randomization_unit, experiment.application_config.randomization_unit, )