def nimbus_check_experiments_are_live(): """ A scheduled task that checks the kinto collection for any experiment slugs that are present in the collection but are not yet marked as live in the database and marks them as live. """ metrics.incr("check_experiments_are_live.started") accepted_experiments = NimbusExperiment.objects.filter( status=NimbusExperiment.Status.ACCEPTED) for collection in NimbusExperiment.KINTO_APPLICATION_COLLECTION.values(): kinto_client = KintoClient(collection) records = kinto_client.get_main_records() record_ids = [r.get("id") for r in records] for experiment in accepted_experiments: if experiment.slug in record_ids: logger.info( f"{experiment} status is being updated to live".format( experiment=experiment)) experiment.status = NimbusExperiment.Status.LIVE experiment.save() generate_nimbus_changelog(experiment, get_kinto_user()) logger.info(f"{experiment} status is set to Live") metrics.incr("check_experiments_are_live.completed")
def nimbus_check_kinto_push_queue_by_collection(collection): """ Because kinto has a restriction that it can only have a single pending review, this task brokers the queue of all experiments ready to be pushed to kinto and ensures that only a single experiment is ever in review. A scheduled task that - Checks the kinto collection for a single rejected experiment from a previous push - If one exists, pull it out of the collection and mark it as rejected - Checks if there is still a pending review and if so, aborts - Gets the list of all experiments ready to be pushed to kinto and pushes the first one - Checks for experiments that should be paused but are not paused in the kinto collection and marks them as paused and updates the record in the collection. """ metrics.incr(f"check_kinto_push_queue_by_collection:{collection}.started") applications = NimbusExperiment.KINTO_COLLECTION_APPLICATIONS[collection] kinto_client = KintoClient(collection) should_rollback = False if kinto_client.has_pending_review(): logger.info(f"{collection} has pending review") should_abort = handle_pending_review(applications) if should_abort: return should_rollback = True if kinto_client.has_rejection(): logger.info(f"{collection} has rejection") handle_rejection(applications, kinto_client) should_rollback = True if should_rollback: kinto_client.rollback_changes() records = kinto_client.get_main_records() handle_launching_experiments(applications, records) handle_updating_experiments(applications, records) handle_ending_experiments(applications, records) handle_waiting_experiments(applications) if queued_launch_experiment := NimbusExperiment.objects.launch_queue( applications).first(): nimbus_push_experiment_to_kinto.delay(collection, queued_launch_experiment.id)
def nimbus_check_experiments_are_complete(): """ A scheduled task that checks the kinto collection for any experiment slugs that are marked as live in the database but missing from the collection, indicating that they are no longer live and can be marked as complete. """ metrics.incr("check_experiments_are_complete.started") for ( collection, applications, ) in NimbusExperiment.KINTO_COLLECTION_APPLICATIONS.items(): kinto_client = KintoClient(collection) live_experiments = NimbusExperiment.objects.filter( status=NimbusExperiment.Status.LIVE, application__in=applications, ) records = kinto_client.get_main_records() for experiment in live_experiments: if (experiment.should_end and not experiment.emails.filter( type=NimbusExperiment.EmailType.EXPERIMENT_END).exists()): nimbus_send_experiment_ending_email(experiment) if experiment.slug not in records: logger.info( f"{experiment.slug} status is being updated to complete". format(experiment=experiment)) experiment.status = NimbusExperiment.Status.COMPLETE experiment.status_next = None experiment.publish_status = NimbusExperiment.PublishStatus.IDLE experiment.save() generate_nimbus_changelog( experiment, get_kinto_user(), message=NimbusChangeLog.Messages.COMPLETED, ) logger.info(f"{experiment.slug} status is set to Complete") metrics.incr("check_experiments_are_complete.completed")
def nimbus_check_experiments_are_complete(): """ A scheduled task that checks the kinto collection for any experiment slugs that are marked as live in the database but missing from the collection, indicating that they are no longer live and can be marked as complete. """ metrics.incr("check_experiments_are_complete.started") for application, collection in NimbusExperiment.KINTO_APPLICATION_COLLECTION.items( ): kinto_client = KintoClient(collection) live_experiments = NimbusExperiment.objects.filter( status=NimbusExperiment.Status.LIVE, application=application, ) records = kinto_client.get_main_records() record_ids = [r.get("id") for r in records] print("found record ids", record_ids) for experiment in live_experiments: print("checking", experiment) if (experiment.should_end and not experiment.emails.filter( type=NimbusExperiment.EmailType.EXPERIMENT_END).exists()): nimbus_send_experiment_ending_email(experiment) if experiment.slug not in record_ids: logger.info( f"{experiment} status is being updated to complete".format( experiment=experiment)) experiment.status = NimbusExperiment.Status.COMPLETE experiment.save() generate_nimbus_changelog(experiment, get_kinto_user()) logger.info(f"{experiment} status is set to Complete") metrics.incr("check_experiments_are_complete.completed")
def nimbus_synchronize_preview_experiments_in_kinto(): """ A scheduled task that pushes any experiments with status PREVIEW to the preview collection and removes any experiments not with status PREVIEW from the preview collection. """ metrics.incr("nimbus_synchronize_preview_experiments_in_kinto.started") kinto_client = KintoClient(settings.KINTO_COLLECTION_NIMBUS_PREVIEW, review=False) try: published_preview_slugs = kinto_client.get_main_records().keys() should_publish_experiments = NimbusExperiment.objects.filter( status=NimbusExperiment.Status.PREVIEW).exclude( slug__in=published_preview_slugs) for experiment in should_publish_experiments: data = NimbusExperimentSerializer(experiment).data kinto_client.create_record(data) logger.info(f"{experiment.slug} is being pushed to preview") should_unpublish_experiments = NimbusExperiment.objects.filter( slug__in=published_preview_slugs).exclude( status=NimbusExperiment.Status.PREVIEW) for experiment in should_unpublish_experiments: kinto_client.delete_record(experiment.slug) logger.info(f"{experiment.slug} is being removed from preview") metrics.incr( "nimbus_synchronize_preview_experiments_in_kinto.completed") except Exception as e: metrics.incr("nimbus_synchronize_preview_experiments_in_kinto.failed") logger.info(f"Synchronizing preview experiments failed: {e}") raise e
def nimbus_check_experiments_are_live(): """ A scheduled task that checks the kinto collection for any experiment slugs that are present in the collection but are not yet marked as live in the database and marks them as live. """ metrics.incr("check_experiments_are_live.started") for ( collection, applications, ) in NimbusExperiment.KINTO_COLLECTION_APPLICATIONS.items(): kinto_client = KintoClient(collection) records = kinto_client.get_main_records() for experiment in NimbusExperiment.objects.waiting_to_launch_queue( applications): if experiment.slug in records: logger.info( f"{experiment} status is being updated to live".format( experiment=experiment)) experiment.status = NimbusExperiment.Status.LIVE experiment.status_next = None experiment.publish_status = NimbusExperiment.PublishStatus.IDLE experiment.published_dto = records[experiment.slug] experiment.save() generate_nimbus_changelog( experiment, get_kinto_user(), message=NimbusChangeLog.Messages.LIVE, ) logger.info(f"{experiment.slug} status is set to Live") metrics.incr("check_experiments_are_live.completed")
class TestKintoClient(MockKintoClientMixin, TestCase): def setUp(self): super().setUp() self.collection = "test-collection" self.client = KintoClient(self.collection) @parameterized.expand([ [False, KINTO_SIGN_STATUS], [True, KINTO_REVIEW_STATUS], ]) def test_create_record_creates_record_patches_collection( self, review, status): client = KintoClient(self.collection, review=review) client.create_record({"test": "data"}) self.mock_kinto_client_creator.assert_called_with( server_url=settings.KINTO_HOST, auth=(settings.KINTO_USER, settings.KINTO_PASS), ) self.mock_kinto_client.create_record.assert_called_with( data={"test": "data"}, collection=self.collection, bucket=settings.KINTO_BUCKET_WORKSPACE, if_not_exists=True, ) self.mock_kinto_client.patch_collection.assert_called_with( id=self.collection, data={"status": status}, bucket=settings.KINTO_BUCKET_WORKSPACE, ) @parameterized.expand([ [False, KINTO_SIGN_STATUS], [True, KINTO_REVIEW_STATUS], ]) def test_update_record_updates_record_patches_collection( self, review, status): client = KintoClient(self.collection, review=review) data = {"id": "my-record", "field": "value"} client.update_record(data) self.mock_kinto_client.update_record.assert_called_with( data=data, collection=self.collection, bucket=settings.KINTO_BUCKET_WORKSPACE, if_match='"0"', ) self.mock_kinto_client.patch_collection.assert_called_with( id=self.collection, data={"status": status}, bucket=settings.KINTO_BUCKET_WORKSPACE, ) @parameterized.expand([ [False, KINTO_SIGN_STATUS], [True, KINTO_REVIEW_STATUS], ]) def test_delete_record_deletes_record_patches_collection( self, review, status): client = KintoClient(self.collection, review=review) record_id = "abc-123" client.delete_record(record_id) self.mock_kinto_client_creator.assert_called_with( server_url=settings.KINTO_HOST, auth=(settings.KINTO_USER, settings.KINTO_PASS), ) self.mock_kinto_client.delete_record.assert_called_with( id=record_id, collection=self.collection, bucket=settings.KINTO_BUCKET_WORKSPACE, ) self.mock_kinto_client.patch_collection.assert_called_with( id=self.collection, data={"status": status}, bucket=settings.KINTO_BUCKET_WORKSPACE, ) def test_rollback_changes_patches_collection(self): self.client.rollback_changes() self.mock_kinto_client_creator.assert_called_with( server_url=settings.KINTO_HOST, auth=(settings.KINTO_USER, settings.KINTO_PASS), ) self.mock_kinto_client.patch_collection.assert_called_with( id=self.collection, data={"status": KINTO_ROLLBACK_STATUS}, bucket=settings.KINTO_BUCKET_WORKSPACE, ) def test_returns_true_for_pending_review(self): self.setup_kinto_pending_review() self.assertTrue(self.client.has_pending_review()) def test_returns_false_for_no_pending_review(self): self.setup_kinto_no_pending_review() self.assertFalse(self.client.has_pending_review()) def test_returns_records(self): slug = "test-slug" self.setup_kinto_get_main_records([slug]) self.assertEqual(self.client.get_main_records(), {slug: { "id": slug, "last_modified": "0" }}) def test_returns_no_records(self): self.setup_kinto_get_main_records([]) self.assertEqual(self.client.get_main_records(), {}) def test_returns_nothing_when_not_rejects(self): self.setup_kinto_no_pending_review() self.assertIsNone(self.client.get_rejected_collection_data()) def test_returns_rejected_data(self): self.setup_kinto_rejected_review() self.assertTrue(self.client.get_rejected_collection_data())
class TestKintoClient(MockKintoClientMixin, TestCase): def setUp(self): super().setUp() self.collection = "test-collection" self.client = KintoClient(self.collection) def test_push_to_kinto_sends_data_updates_collection(self): self.client.push_to_kinto({"test": "data"}) self.mock_kinto_client_creator.assert_called_with( server_url=settings.KINTO_HOST, auth=(settings.KINTO_USER, settings.KINTO_PASS), ) self.mock_kinto_client.create_record.assert_called_with( data={"test": "data"}, collection=self.collection, bucket=settings.KINTO_BUCKET, if_not_exists=True, ) self.mock_kinto_client.patch_collection.assert_called_with( id=self.collection, data={"status": KINTO_REVIEW_STATUS}, bucket=settings.KINTO_BUCKET, ) def test_rollback_changes_patches_collection(self): self.client.rollback_changes() self.mock_kinto_client_creator.assert_called_with( server_url=settings.KINTO_HOST, auth=(settings.KINTO_USER, settings.KINTO_PASS), ) self.mock_kinto_client.patch_collection.assert_called_with( id=self.collection, data={"status": KINTO_ROLLBACK_STATUS}, bucket=settings.KINTO_BUCKET, ) def test_returns_true_for_pending_review(self): self.setup_kinto_pending_review() self.assertTrue(self.client.has_pending_review()) def test_returns_false_for_no_pending_review(self): self.setup_kinto_no_pending_review() self.assertFalse(self.client.has_pending_review()) def test_returns_records(self): slug = "test-slug" self.setup_kinto_get_main_records([slug]) self.assertEqual(self.client.get_main_records(), [{"id": slug}]) def test_returns_no_records(self): self.setup_kinto_get_main_records([]) self.assertEqual(self.client.get_main_records(), []) def test_returns_nothing_when_not_rejects(self): self.setup_kinto_no_pending_review() self.assertIsNone(self.client.get_rejected_collection_data()) def test_returns_rejected_data(self): self.setup_kinto_rejected_review() self.assertTrue(self.client.get_rejected_collection_data()) def test_returns_rejected_record(self): self.mock_kinto_client.get_records.side_effect = [ [{ "id": "bug-12345-rapid-test-release-55" }], [ { "id": "bug-12345-rapid-test-release-55" }, { "id": "bug-9999-rapid-test-release-55" }, ], ] self.assertEqual(self.client.get_rejected_record(), "bug-9999-rapid-test-release-55")