Skip to main content

glean_core/upload/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Manages the pending pings queue and directory.
6//!
7//! * Keeps track of pending pings, loading any unsent ping from disk on startup;
8//! * Exposes [`get_upload_task`](PingUploadManager::get_upload_task) API for
9//!   the platform layer to request next upload task;
10//! * Exposes
11//!   [`process_ping_upload_response`](PingUploadManager::process_ping_upload_response)
12//!   API to check the HTTP response from the ping upload and either delete the
13//!   corresponding ping from disk or re-enqueue it for sending.
14
15use std::collections::HashMap;
16use std::collections::VecDeque;
17use std::mem;
18use std::path::PathBuf;
19use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
20use std::sync::{Arc, RwLock, RwLockWriteGuard};
21use std::time::{Duration, Instant};
22
23use chrono::Utc;
24use malloc_size_of::MallocSizeOf;
25use malloc_size_of_derive::MallocSizeOf;
26
27use crate::error::ErrorKind;
28use crate::TimerId;
29use crate::{internal_metrics::UploadMetrics, Glean};
30pub use directory::process_metadata;
31use directory::{PingDirectoryManager, PingPayloadsByDirectory};
32use policy::Policy;
33use request::create_date_header_value;
34
35pub use directory::{PingMetadata, PingPayload};
36pub use request::{HeaderMap, PingRequest};
37pub use result::{UploadResult, UploadTaskAction};
38
39mod directory;
40mod policy;
41mod request;
42mod result;
43
44const WAIT_TIME_FOR_PING_PROCESSING: u64 = 1000; // in milliseconds
45
46#[derive(Debug, MallocSizeOf)]
47struct RateLimiter {
48    /// The instant the current interval has started.
49    started: Option<Instant>,
50    /// The count for the current interval.
51    count: u32,
52    /// The duration of each interval.
53    interval: Duration,
54    /// The maximum count per interval.
55    max_count: u32,
56}
57
58/// An enum to represent the current state of the RateLimiter.
59#[derive(PartialEq)]
60enum RateLimiterState {
61    /// The RateLimiter has not reached the maximum count and is still incrementing.
62    Incrementing,
63    /// The RateLimiter has reached the maximum count for the  current interval.
64    ///
65    /// This variant contains the remaining time (in milliseconds)
66    /// until the rate limiter is not throttled anymore.
67    Throttled(u64),
68}
69
70impl RateLimiter {
71    pub fn new(interval: Duration, max_count: u32) -> Self {
72        Self {
73            started: None,
74            count: 0,
75            interval,
76            max_count,
77        }
78    }
79
80    fn reset(&mut self) {
81        self.started = Some(Instant::now());
82        self.count = 0;
83    }
84
85    fn elapsed(&self) -> Duration {
86        self.started.unwrap().elapsed()
87    }
88
89    // The counter should reset if
90    //
91    // 1. It has never started;
92    // 2. It has been started more than the interval time ago;
93    // 3. Something goes wrong while trying to calculate the elapsed time since the last reset.
94    fn should_reset(&self) -> bool {
95        if self.started.is_none() {
96            return true;
97        }
98
99        // Safe unwrap, we already stated that `self.started` is not `None` above.
100        if self.elapsed() > self.interval {
101            return true;
102        }
103
104        false
105    }
106
107    /// Tries to increment the internal counter.
108    ///
109    /// # Returns
110    ///
111    /// The current state of the RateLimiter.
112    pub fn get_state(&mut self) -> RateLimiterState {
113        if self.should_reset() {
114            self.reset();
115        }
116
117        if self.count == self.max_count {
118            // Note that `remining` can't be a negative number because we just called `reset`,
119            // which will check if it is and reset if so.
120            let remaining = self.interval.as_millis() - self.elapsed().as_millis();
121            return RateLimiterState::Throttled(
122                remaining
123                    .try_into()
124                    .unwrap_or(self.interval.as_secs() * 1000),
125            );
126        }
127
128        self.count += 1;
129        RateLimiterState::Incrementing
130    }
131}
132
133/// An enum representing the possible upload tasks to be performed by an uploader.
134///
135/// When asking for the next ping request to upload,
136/// the requester may receive one out of three possible tasks.
137#[derive(PartialEq, Eq, Debug)]
138pub enum PingUploadTask {
139    /// An upload task
140    Upload {
141        /// The ping request for upload
142        /// See [`PingRequest`](struct.PingRequest.html) for more information.
143        request: PingRequest,
144    },
145
146    /// A flag signaling that the pending pings directories are not done being processed,
147    /// thus the requester should wait and come back later.
148    Wait {
149        /// The time in milliseconds
150        /// the requester should wait before requesting a new task.
151        time: u64,
152    },
153
154    /// A flag signaling that requester doesn't need to request any more upload tasks at this moment.
155    ///
156    /// There are three possibilities for this scenario:
157    /// * Pending pings queue is empty, no more pings to request;
158    /// * Requester has gotten more than MAX_WAIT_ATTEMPTS (3, by default) `PingUploadTask::Wait` responses in a row;
159    /// * Requester has reported more than MAX_RECOVERABLE_FAILURES_PER_UPLOADING_WINDOW
160    ///   recoverable upload failures on the same uploading window (see below)
161    ///   and should stop requesting at this moment.
162    ///
163    /// An "uploading window" starts when a requester gets a new
164    /// `PingUploadTask::Upload(PingRequest)` response and finishes when they
165    /// finally get a `PingUploadTask::Done` or `PingUploadTask::Wait` response.
166    Done {
167        #[doc(hidden)]
168        /// Unused field. Required because UniFFI can't handle variants without fields.
169        unused: i8,
170    },
171}
172
173impl PingUploadTask {
174    /// Whether the current task is an upload task.
175    pub fn is_upload(&self) -> bool {
176        matches!(self, PingUploadTask::Upload { .. })
177    }
178
179    /// Whether the current task is wait task.
180    pub fn is_wait(&self) -> bool {
181        matches!(self, PingUploadTask::Wait { .. })
182    }
183
184    pub(crate) fn done() -> Self {
185        PingUploadTask::Done { unused: 0 }
186    }
187}
188
189/// Manages the pending pings queue and directory.
190#[derive(Debug)]
191pub struct PingUploadManager {
192    /// A FIFO queue storing a `PingRequest` for each pending ping.
193    queue: RwLock<VecDeque<PingRequest>>,
194    /// A manager for the pending pings directories.
195    directory_manager: PingDirectoryManager,
196    /// A flag signaling if we are done processing the pending pings directories.
197    processed_pending_pings: Arc<AtomicBool>,
198    /// A vector to store the pending pings processed off-thread.
199    cached_pings: Arc<RwLock<PingPayloadsByDirectory>>,
200    /// The number of upload failures for the current uploading window.
201    recoverable_failure_count: AtomicU32,
202    /// The number or times in a row a user has received a `PingUploadTask::Wait` response.
203    wait_attempt_count: AtomicU32,
204    /// A ping counter to help rate limit the ping uploads.
205    ///
206    /// To keep resource usage in check,
207    /// we may want to limit the amount of pings sent in a given interval.
208    rate_limiter: Option<RwLock<RateLimiter>>,
209    /// The name of the programming language used by the binding creating this instance of PingUploadManager.
210    ///
211    /// This will be used to build the value User-Agent header for each ping request.
212    language_binding_name: String,
213    /// Metrics related to ping uploading.
214    upload_metrics: UploadMetrics,
215    /// Policies for ping storage, uploading and requests.
216    policy: Policy,
217
218    in_flight: RwLock<HashMap<String, (TimerId, TimerId)>>,
219}
220
221impl MallocSizeOf for PingUploadManager {
222    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
223        let shallow_size = {
224            let queue = self.queue.read().unwrap();
225            if ops.has_malloc_enclosing_size_of() {
226                if let Some(front) = queue.front() {
227                    // SAFETY: The front element is a valid interior pointer and thus valid to pass
228                    // to an external function.
229                    unsafe { ops.malloc_enclosing_size_of(front) }
230                } else {
231                    // This assumes that no memory is allocated when the VecDeque is empty.
232                    0
233                }
234            } else {
235                // If `ops` can't estimate the size of a pointer,
236                // we can estimate the allocation size by the size of each element and the
237                // allocated capacity.
238                queue.capacity() * mem::size_of::<PingRequest>()
239            }
240        };
241
242        let mut n = shallow_size
243            + self.directory_manager.size_of(ops)
244            + mem::size_of::<AtomicBool>() // Allocated inside the `self.processed_pending_pings` `Arc`.
245            + self.cached_pings.read().unwrap().size_of(ops)
246            + self.rate_limiter.as_ref().map(|rl| {
247                let lock = rl.read().unwrap();
248                (*lock).size_of(ops)
249            }).unwrap_or(0)
250            + self.language_binding_name.size_of(ops)
251            + self.upload_metrics.size_of(ops)
252            + self.policy.size_of(ops);
253
254        let in_flight = self.in_flight.read().unwrap();
255        n += in_flight.size_of(ops);
256
257        n
258    }
259}
260
261impl PingUploadManager {
262    /// Creates a new PingUploadManager.
263    ///
264    /// # Arguments
265    ///
266    /// * `data_path` - Path to the pending pings directory.
267    /// * `language_binding_name` - The name of the language binding calling this managers instance.
268    ///
269    /// # Panics
270    ///
271    /// Will panic if unable to spawn a new thread.
272    pub fn new<P: Into<PathBuf>>(data_path: P, language_binding_name: &str) -> Self {
273        Self {
274            queue: RwLock::new(VecDeque::new()),
275            directory_manager: PingDirectoryManager::new(data_path),
276            processed_pending_pings: Arc::new(AtomicBool::new(false)),
277            cached_pings: Arc::new(RwLock::new(PingPayloadsByDirectory::default())),
278            recoverable_failure_count: AtomicU32::new(0),
279            wait_attempt_count: AtomicU32::new(0),
280            rate_limiter: None,
281            language_binding_name: language_binding_name.into(),
282            upload_metrics: UploadMetrics::new(),
283            policy: Policy::default(),
284            in_flight: RwLock::new(HashMap::default()),
285        }
286    }
287
288    /// Spawns a new thread and processes the pending pings directories,
289    /// filling up the queue with whatever pings are in there.
290    ///
291    /// # Returns
292    ///
293    /// The `JoinHandle` to the spawned thread
294    pub fn scan_pending_pings_directories(
295        &self,
296        trigger_upload: bool,
297    ) -> std::thread::JoinHandle<()> {
298        let local_manager = self.directory_manager.clone();
299        let local_cached_pings = self.cached_pings.clone();
300        let local_flag = self.processed_pending_pings.clone();
301        crate::thread::spawn("glean.ping_directory_manager.process_dir", move || {
302            {
303                // Be sure to drop local_cached_pings lock before triggering upload.
304                let mut local_cached_pings = local_cached_pings
305                    .write()
306                    .expect("Can't write to pending pings cache.");
307                local_cached_pings.extend(local_manager.process_dirs());
308                local_flag.store(true, Ordering::SeqCst);
309            }
310            if trigger_upload {
311                crate::dispatcher::launch(|| {
312                    if let Some(state) = crate::maybe_global_state().and_then(|s| s.lock().ok()) {
313                        if let Err(e) = state.callbacks.trigger_upload() {
314                            log::error!(
315                                "Triggering upload after pending ping scan failed. Error: {}",
316                                e
317                            );
318                        }
319                    }
320                });
321            }
322        })
323        .expect("Unable to spawn thread to process pings directories.")
324    }
325
326    /// Creates a new upload manager with no limitations, for tests.
327    #[cfg(test)]
328    pub fn no_policy<P: Into<PathBuf>>(data_path: P) -> Self {
329        let mut upload_manager = Self::new(data_path, "Test");
330
331        // Disable all policies for tests, if necessary individuals tests can re-enable them.
332        upload_manager.policy.set_max_recoverable_failures(None);
333        upload_manager.policy.set_max_wait_attempts(None);
334        upload_manager.policy.set_max_ping_body_size(None);
335        upload_manager
336            .policy
337            .set_max_pending_pings_directory_size(None);
338        upload_manager.policy.set_max_pending_pings_count(None);
339
340        // When building for tests, always scan the pending pings directories and do it sync.
341        upload_manager
342            .scan_pending_pings_directories(false)
343            .join()
344            .unwrap();
345
346        upload_manager
347    }
348
349    fn processed_pending_pings(&self) -> bool {
350        self.processed_pending_pings.load(Ordering::SeqCst)
351    }
352
353    fn recoverable_failure_count(&self) -> u32 {
354        self.recoverable_failure_count.load(Ordering::SeqCst)
355    }
356
357    fn wait_attempt_count(&self) -> u32 {
358        self.wait_attempt_count.load(Ordering::SeqCst)
359    }
360
361    /// Attempts to build a ping request from a ping file payload.
362    ///
363    /// Returns the `PingRequest` or `None` if unable to build,
364    /// in which case it will delete the ping file and record an error.
365    fn build_ping_request(&self, glean: &Glean, ping: PingPayload) -> Option<PingRequest> {
366        let PingPayload {
367            document_id,
368            upload_path: path,
369            json_body: body,
370            headers,
371            body_has_info_sections,
372            ping_name,
373            uploader_capabilities,
374        } = ping;
375        let mut request = PingRequest::builder(
376            &self.language_binding_name,
377            self.policy.max_ping_body_size(),
378        )
379        .document_id(&document_id)
380        .path(path)
381        .body(body)
382        .body_has_info_sections(body_has_info_sections)
383        .ping_name(ping_name)
384        .uploader_capabilities(uploader_capabilities);
385
386        if let Some(headers) = headers {
387            request = request.headers(headers);
388        }
389
390        match request.build() {
391            Ok(request) => Some(request),
392            Err(e) => {
393                log::warn!("Error trying to build ping request: {}", e);
394                self.directory_manager.delete_file(&document_id);
395
396                // Record the error.
397                // Currently the only possible error is PingBodyOverflow.
398                if let ErrorKind::PingBodyOverflow(s) = e.kind() {
399                    self.upload_metrics
400                        .discarded_exceeding_pings_size
401                        .accumulate_sync(glean, *s as i64 / 1024);
402                }
403
404                None
405            }
406        }
407    }
408
409    /// Enqueue a ping for upload.
410    pub fn enqueue_ping(&self, glean: &Glean, ping: PingPayload) {
411        let mut queue = self
412            .queue
413            .write()
414            .expect("Can't write to pending pings queue.");
415
416        let PingPayload {
417            ref document_id,
418            upload_path: ref path,
419            ..
420        } = ping;
421        // Checks if a ping with this `document_id` is already enqueued.
422        if queue
423            .iter()
424            .any(|request| request.document_id.as_str() == document_id)
425        {
426            log::warn!(
427                "Attempted to enqueue a duplicate ping {} at {}.",
428                document_id,
429                path
430            );
431            return;
432        }
433
434        {
435            let in_flight = self.in_flight.read().unwrap();
436            if in_flight.contains_key(document_id) {
437                log::warn!(
438                    "Attempted to enqueue an in-flight ping {} at {}.",
439                    document_id,
440                    path
441                );
442                self.upload_metrics
443                    .in_flight_pings_dropped
444                    .add_sync(glean, 0);
445                return;
446            }
447        }
448
449        log::trace!("Enqueuing ping {} at {}", document_id, path);
450        if let Some(request) = self.build_ping_request(glean, ping) {
451            queue.push_back(request)
452        }
453    }
454
455    /// Enqueues pings that might have been cached.
456    ///
457    /// The size of the PENDING_PINGS_DIRECTORY directory will be calculated
458    /// (by accumulating each ping's size in that directory)
459    /// and in case we exceed the quota, defined by the `quota` arg,
460    /// outstanding pings get deleted and are not enqueued.
461    ///
462    /// The size of the DELETION_REQUEST_PINGS_DIRECTORY will not be calculated
463    /// and no deletion-request pings will be deleted. Deletion request pings
464    /// are not very common and usually don't contain any data,
465    /// we don't expect that directory to ever reach quota.
466    /// Most importantly, we don't want to ever delete deletion-request pings.
467    ///
468    /// # Arguments
469    ///
470    /// * `glean` - The Glean object holding the database.
471    fn enqueue_cached_pings(&self, glean: &Glean) {
472        let mut cached_pings = self
473            .cached_pings
474            .write()
475            .expect("Can't write to pending pings cache.");
476
477        if cached_pings.len() > 0 {
478            let mut pending_pings_directory_size: u64 = 0;
479            let mut pending_pings_count = 0;
480            let mut deleting = false;
481            let mut delete_reason: Option<&'static str> = None;
482
483            let total = cached_pings.pending_pings.len() as u64;
484            self.upload_metrics
485                .pending_pings
486                .add_sync(glean, total.try_into().unwrap_or(0));
487
488            if total > self.policy.max_pending_pings_count() {
489                log::warn!(
490                    "More than {} pending pings in the directory, will delete {} old pings.",
491                    self.policy.max_pending_pings_count(),
492                    total - self.policy.max_pending_pings_count()
493                );
494            }
495
496            // The pending pings vector is sorted by date in ascending order (oldest -> newest).
497            // We need to calculate the size of the pending pings directory
498            // and delete the **oldest** pings in case quota is reached.
499            // Thus, we reverse the order of the pending pings vector,
500            // so that we iterate in descending order (newest -> oldest).
501            cached_pings.pending_pings.reverse();
502            cached_pings.pending_pings.retain(|(file_size, PingPayload {document_id, ..})| {
503                pending_pings_count += 1;
504                pending_pings_directory_size += file_size;
505
506                // We don't want to spam the log for every ping over the quota.
507                // Size is checked first; if both limits are exceeded simultaneously,
508                // size_quota takes precedence as the recorded reason.
509                if !deleting && pending_pings_directory_size > self.policy.max_pending_pings_directory_size() {
510                    log::warn!(
511                        "Pending pings directory has reached the size quota of {} bytes, outstanding pings will be deleted.",
512                        self.policy.max_pending_pings_directory_size()
513                    );
514                    deleting = true;
515                    delete_reason = Some("size_quota");
516                }
517
518                // Once we reach the number of allowed pings we start deleting,
519                // no matter what size.
520                // We already log this before the loop.
521                if !deleting && pending_pings_count > self.policy.max_pending_pings_count() {
522                    deleting = true;
523                    delete_reason = Some("count_quota");
524                }
525
526                if deleting && self.directory_manager.delete_file(document_id) {
527                    self.upload_metrics
528                        .deleted_pings_after_quota_hit
529                        .add_sync(glean, 1);
530                    if let Some(reason) = delete_reason {
531                        self.upload_metrics
532                            .pending_pings_deleted
533                            .get(reason)
534                            .add_sync(glean, 1);
535                    }
536                    return false;
537                }
538
539                true
540            });
541            // After calculating the size of the pending pings directory,
542            // we record the calculated number and reverse the pings array back for enqueueing.
543            cached_pings.pending_pings.reverse();
544            self.upload_metrics
545                .pending_pings_directory_size
546                .accumulate_sync(glean, pending_pings_directory_size as i64 / 1024);
547
548            // Enqueue the remaining pending pings and
549            // enqueue all deletion-request pings.
550            cached_pings
551                .deletion_request_pings
552                .drain(..)
553                .for_each(|(_, ping)| self.enqueue_ping(glean, ping));
554            cached_pings
555                .pending_pings
556                .drain(..)
557                .for_each(|(_, ping)| self.enqueue_ping(glean, ping));
558        }
559    }
560
561    /// Adds rate limiting capability to this upload manager.
562    ///
563    /// The rate limiter will limit the amount of calls to `get_upload_task` per interval.
564    ///
565    /// Setting this will restart count and timer in case there was a previous rate limiter set
566    /// (e.g. if we have reached the current limit and call this function, we start counting again
567    /// and the caller is allowed to asks for tasks).
568    ///
569    /// # Arguments
570    ///
571    /// * `interval` - the amount of seconds in each rate limiting window.
572    /// * `max_tasks` - the maximum amount of task requests allowed per interval.
573    pub fn set_rate_limiter(&mut self, interval: u64, max_tasks: u32) {
574        self.rate_limiter = Some(RwLock::new(RateLimiter::new(
575            Duration::from_secs(interval),
576            max_tasks,
577        )));
578    }
579
580    pub(crate) fn set_max_pending_pings_count(&mut self, n: u64) {
581        self.policy.set_max_pending_pings_count(Some(n));
582    }
583
584    pub(crate) fn set_max_pending_pings_directory_size(&mut self, n: u64) {
585        self.policy.set_max_pending_pings_directory_size(Some(n));
586    }
587
588    /// Reads a ping file, creates a `PingRequest` and adds it to the queue.
589    ///
590    /// Duplicate requests won't be added.
591    ///
592    /// # Arguments
593    ///
594    /// * `glean` - The Glean object holding the database.
595    /// * `document_id` - The UUID of the ping in question.
596    pub fn enqueue_ping_from_file(&self, glean: &Glean, document_id: &str) {
597        if let Some(ping) = self.directory_manager.process_file(document_id) {
598            self.enqueue_ping(glean, ping);
599        }
600    }
601
602    /// Clears the pending pings queue, leaves the deletion-request pings.
603    pub fn clear_ping_queue(&self) -> RwLockWriteGuard<'_, VecDeque<PingRequest>> {
604        log::trace!("Clearing ping queue");
605        let mut queue = self
606            .queue
607            .write()
608            .expect("Can't write to pending pings queue.");
609
610        queue.retain(|ping| ping.is_deletion_request());
611        log::trace!(
612            "{} pings left in the queue (only deletion-request expected)",
613            queue.len()
614        );
615        queue
616    }
617
618    fn get_upload_task_internal(&self, glean: &Glean, log_ping: bool) -> PingUploadTask {
619        // Helper to decide whether to return PingUploadTask::Wait or PingUploadTask::Done.
620        //
621        // We want to limit the amount of PingUploadTask::Wait returned in a row,
622        // in case we reach MAX_WAIT_ATTEMPTS we want to actually return PingUploadTask::Done.
623        let wait_or_done = |time: u64| {
624            self.wait_attempt_count.fetch_add(1, Ordering::SeqCst);
625            if self.wait_attempt_count() > self.policy.max_wait_attempts() {
626                PingUploadTask::done()
627            } else {
628                PingUploadTask::Wait { time }
629            }
630        };
631
632        if !self.processed_pending_pings() {
633            log::info!(
634                "Tried getting an upload task, but processing is ongoing. Will come back later."
635            );
636            return wait_or_done(WAIT_TIME_FOR_PING_PROCESSING);
637        }
638
639        // This is a no-op in case there are no cached pings.
640        self.enqueue_cached_pings(glean);
641
642        if self.recoverable_failure_count() >= self.policy.max_recoverable_failures() {
643            log::warn!(
644                "Reached maximum recoverable failures for the current uploading window. You are done."
645            );
646            return PingUploadTask::done();
647        }
648
649        let mut queue = self
650            .queue
651            .write()
652            .expect("Can't write to pending pings queue.");
653        match queue.front() {
654            Some(request) => {
655                if let Some(rate_limiter) = &self.rate_limiter {
656                    let mut rate_limiter = rate_limiter
657                        .write()
658                        .expect("Can't write to the rate limiter.");
659                    if let RateLimiterState::Throttled(remaining) = rate_limiter.get_state() {
660                        log::info!(
661                            "Tried getting an upload task, but we are throttled at the moment."
662                        );
663                        return wait_or_done(remaining);
664                    }
665                }
666
667                log::info!(
668                    "New upload task with id {} (path: {})",
669                    request.document_id,
670                    request.path
671                );
672
673                if log_ping {
674                    if let Some(body) = request.pretty_body() {
675                        chunked_log_info(&request.path, &body);
676                    } else {
677                        chunked_log_info(&request.path, "<invalid ping payload>");
678                    }
679                }
680
681                {
682                    // Synchronous timer starts.
683                    // We're in the uploader thread anyway.
684                    // But also: No data is stored on disk.
685                    let mut in_flight = self.in_flight.write().unwrap();
686                    let success_id = self.upload_metrics.send_success.start_sync();
687                    let failure_id = self.upload_metrics.send_failure.start_sync();
688                    in_flight.insert(request.document_id.clone(), (success_id, failure_id));
689                }
690
691                let mut request = queue.pop_front().unwrap();
692
693                // Adding the `Date` header just before actual upload happens.
694                request
695                    .headers
696                    .insert("Date".to_string(), create_date_header_value(Utc::now()));
697
698                PingUploadTask::Upload { request }
699            }
700            None => {
701                log::info!("No more pings to upload! You are done.");
702                PingUploadTask::done()
703            }
704        }
705    }
706
707    /// Gets the next `PingUploadTask`.
708    ///
709    /// # Arguments
710    ///
711    /// * `glean` - The Glean object holding the database.
712    /// * `log_ping` - Whether to log the ping before returning.
713    ///
714    /// # Returns
715    ///
716    /// The next [`PingUploadTask`](enum.PingUploadTask.html).
717    pub fn get_upload_task(&self, glean: &Glean, log_ping: bool) -> PingUploadTask {
718        let task = self.get_upload_task_internal(glean, log_ping);
719
720        if !task.is_wait() && self.wait_attempt_count() > 0 {
721            self.wait_attempt_count.store(0, Ordering::SeqCst);
722        }
723
724        if !task.is_upload() && self.recoverable_failure_count() > 0 {
725            self.recoverable_failure_count.store(0, Ordering::SeqCst);
726        }
727
728        task
729    }
730
731    /// Processes the response from an attempt to upload a ping.
732    ///
733    /// Based on the HTTP status of said response,
734    /// the possible outcomes are:
735    ///
736    /// * **200 - 299 Success**
737    ///   Any status on the 2XX range is considered a succesful upload,
738    ///   which means the corresponding ping file can be deleted.
739    ///   _Known 2XX status:_
740    ///   * 200 - OK. Request accepted into the pipeline.
741    ///
742    /// * **400 - 499 Unrecoverable error**
743    ///   Any status on the 4XX range means something our client did is not correct.
744    ///   It is unlikely that the client is going to recover from this by retrying,
745    ///   so in this case the corresponding ping file can also be deleted.
746    ///   _Known 4XX status:_
747    ///   * 404 - not found - POST/PUT to an unknown namespace
748    ///   * 405 - wrong request type (anything other than POST/PUT)
749    ///   * 411 - missing content-length header
750    ///   * 413 - request body too large Note that if we have badly-behaved clients that
751    ///           retry on 4XX, we should send back 202 on body/path too long).
752    ///   * 414 - request path too long (See above)
753    ///
754    /// * **Any other error**
755    ///   For any other error, a warning is logged and the ping is re-enqueued.
756    ///   _Known other errors:_
757    ///   * 500 - internal error
758    ///
759    /// # Note
760    ///
761    /// The disk I/O performed by this function is not done off-thread,
762    /// as it is expected to be called off-thread by the platform.
763    ///
764    /// # Arguments
765    ///
766    /// * `glean` - The Glean object holding the database.
767    /// * `document_id` - The UUID of the ping in question.
768    /// * `status` - The HTTP status of the response.
769    pub fn process_ping_upload_response(
770        &self,
771        glean: &Glean,
772        document_id: &str,
773        status: UploadResult,
774    ) -> UploadTaskAction {
775        use UploadResult::*;
776
777        let stop_time = zeitstempel::now_awake();
778
779        if let Some(label) = status.get_label() {
780            let metric = self.upload_metrics.ping_upload_failure.get(label);
781            metric.add_sync(glean, 1);
782        }
783
784        let send_ids = {
785            let mut lock = self.in_flight.write().unwrap();
786            lock.remove(document_id)
787        };
788
789        if send_ids.is_none() {
790            self.upload_metrics.missing_send_ids.add_sync(glean, 1);
791        }
792
793        match status {
794            HttpStatus { code } if (200..=299).contains(&code) => {
795                log::info!("Ping {} successfully sent {}.", document_id, code);
796                if let Some((success_id, failure_id)) = send_ids {
797                    self.upload_metrics
798                        .send_success
799                        .set_stop_and_accumulate(glean, success_id, stop_time);
800                    self.upload_metrics.send_failure.cancel_sync(failure_id);
801                }
802                if glean.store_submitted_pings_enabled {
803                    glean
804                        .storage()
805                        .mark_ping_as_uploaded(document_id, Utc::now());
806                }
807                self.directory_manager.delete_file(document_id);
808            }
809
810            UnrecoverableFailure { .. } | HttpStatus { code: 400..=499 } | Incapable { .. } => {
811                log::warn!(
812                    "Unrecoverable upload failure while attempting to send ping {}. Error was {:?}",
813                    document_id,
814                    status
815                );
816                if let Some((success_id, failure_id)) = send_ids {
817                    self.upload_metrics.send_success.cancel_sync(success_id);
818                    self.upload_metrics
819                        .send_failure
820                        .set_stop_and_accumulate(glean, failure_id, stop_time);
821                }
822                if glean.store_submitted_pings_enabled {
823                    glean.storage().mark_ping_as_upload_failed(document_id);
824                }
825                self.directory_manager.delete_file(document_id);
826            }
827
828            RecoverableFailure { .. } | HttpStatus { .. } => {
829                log::warn!(
830                    "Recoverable upload failure while attempting to send ping {}, will retry. Error was {:?}",
831                    document_id,
832                    status
833                );
834                if let Some((success_id, failure_id)) = send_ids {
835                    self.upload_metrics.send_success.cancel_sync(success_id);
836                    self.upload_metrics
837                        .send_failure
838                        .set_stop_and_accumulate(glean, failure_id, stop_time);
839                }
840                self.enqueue_ping_from_file(glean, document_id);
841                self.recoverable_failure_count
842                    .fetch_add(1, Ordering::SeqCst);
843            }
844
845            Done { .. } => {
846                log::debug!("Uploader signaled Done. Exiting.");
847                if let Some((success_id, failure_id)) = send_ids {
848                    self.upload_metrics.send_success.cancel_sync(success_id);
849                    self.upload_metrics.send_failure.cancel_sync(failure_id);
850                }
851                return UploadTaskAction::End;
852            }
853        };
854
855        UploadTaskAction::Next
856    }
857}
858
859/// Splits log message into chunks on Android.
860#[cfg(target_os = "android")]
861pub fn chunked_log_info(path: &str, payload: &str) {
862    // Since the logcat ring buffer size is configurable, but it's 'max payload' size is not,
863    // we must break apart long pings into chunks no larger than the max payload size of 4076b.
864    // We leave some head space for our prefix.
865    const MAX_LOG_PAYLOAD_SIZE_BYTES: usize = 4000;
866
867    // If the length of the ping will fit within one logcat payload, then we can
868    // short-circuit here and avoid some overhead, otherwise we must split up the
869    // message so that we don't truncate it.
870    if path.len() + payload.len() <= MAX_LOG_PAYLOAD_SIZE_BYTES {
871        log::info!("Glean ping to URL: {}\n{}", path, payload);
872        return;
873    }
874
875    // Otherwise we break it apart into chunks of smaller size,
876    // prefixing it with the path and a counter.
877    let mut start = 0;
878    let mut end = MAX_LOG_PAYLOAD_SIZE_BYTES;
879    let mut chunk_idx = 1;
880    // Might be off by 1 on edge cases, but do we really care?
881    let total_chunks = payload.len() / MAX_LOG_PAYLOAD_SIZE_BYTES + 1;
882
883    while end < payload.len() {
884        // Find char boundary from the end.
885        // It's UTF-8, so it is within 4 bytes from here.
886        for _ in 0..4 {
887            if payload.is_char_boundary(end) {
888                break;
889            }
890            end -= 1;
891        }
892
893        log::info!(
894            "Glean ping to URL: {} [Part {} of {}]\n{}",
895            path,
896            chunk_idx,
897            total_chunks,
898            &payload[start..end]
899        );
900
901        // Move on with the string
902        start = end;
903        end = end + MAX_LOG_PAYLOAD_SIZE_BYTES;
904        chunk_idx += 1;
905    }
906
907    // Print any suffix left
908    if start < payload.len() {
909        log::info!(
910            "Glean ping to URL: {} [Part {} of {}]\n{}",
911            path,
912            chunk_idx,
913            total_chunks,
914            &payload[start..]
915        );
916    }
917}
918
919/// Logs payload in one go (all other OS).
920#[cfg(not(target_os = "android"))]
921pub fn chunked_log_info(_path: &str, payload: &str) {
922    log::info!("{}", payload)
923}
924
925#[cfg(test)]
926mod test {
927    use std::thread;
928    use uuid::Uuid;
929
930    use super::*;
931    use crate::metrics::PingType;
932    use crate::{tests::new_glean, PENDING_PINGS_DIRECTORY};
933
934    const PATH: &str = "/submit/app_id/ping_name/schema_version/doc_id";
935
936    #[test]
937    fn doesnt_error_when_there_are_no_pending_pings() {
938        let (glean, _t) = new_glean(None);
939
940        // Try and get the next request.
941        // Verify request was not returned
942        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
943    }
944
945    #[test]
946    fn returns_ping_request_when_there_is_one() {
947        let (glean, dir) = new_glean(None);
948
949        let upload_manager = PingUploadManager::no_policy(dir.path());
950
951        // Enqueue a ping
952        upload_manager.enqueue_ping(
953            &glean,
954            PingPayload {
955                document_id: Uuid::new_v4().to_string(),
956                upload_path: PATH.into(),
957                json_body: "".into(),
958                headers: None,
959                body_has_info_sections: true,
960                ping_name: "ping-name".into(),
961                uploader_capabilities: vec![],
962            },
963        );
964
965        // Try and get the next request.
966        // Verify request was returned
967        let task = upload_manager.get_upload_task(&glean, false);
968        assert!(task.is_upload());
969    }
970
971    #[test]
972    fn returns_as_many_ping_requests_as_there_are() {
973        let (glean, dir) = new_glean(None);
974
975        let upload_manager = PingUploadManager::no_policy(dir.path());
976
977        // Enqueue a ping multiple times
978        let n = 10;
979        for _ in 0..n {
980            upload_manager.enqueue_ping(
981                &glean,
982                PingPayload {
983                    document_id: Uuid::new_v4().to_string(),
984                    upload_path: PATH.into(),
985                    json_body: "".into(),
986                    headers: None,
987                    body_has_info_sections: true,
988                    ping_name: "ping-name".into(),
989                    uploader_capabilities: vec![],
990                },
991            );
992        }
993
994        // Verify a request is returned for each submitted ping
995        for _ in 0..n {
996            let task = upload_manager.get_upload_task(&glean, false);
997            assert!(task.is_upload());
998        }
999
1000        // Verify that after all requests are returned, none are left
1001        assert_eq!(
1002            upload_manager.get_upload_task(&glean, false),
1003            PingUploadTask::done()
1004        );
1005    }
1006
1007    #[test]
1008    fn limits_the_number_of_pings_when_there_is_rate_limiting() {
1009        let (glean, dir) = new_glean(None);
1010
1011        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1012
1013        // Add a rate limiter to the upload mangager with max of 10 pings every 3 seconds.
1014        let max_pings_per_interval = 10;
1015        upload_manager.set_rate_limiter(3, 10);
1016
1017        // Enqueue the max number of pings allowed per uploading window
1018        for _ in 0..max_pings_per_interval {
1019            upload_manager.enqueue_ping(
1020                &glean,
1021                PingPayload {
1022                    document_id: Uuid::new_v4().to_string(),
1023                    upload_path: PATH.into(),
1024                    json_body: "".into(),
1025                    headers: None,
1026                    body_has_info_sections: true,
1027                    ping_name: "ping-name".into(),
1028                    uploader_capabilities: vec![],
1029                },
1030            );
1031        }
1032
1033        // Verify a request is returned for each submitted ping
1034        for _ in 0..max_pings_per_interval {
1035            let task = upload_manager.get_upload_task(&glean, false);
1036            assert!(task.is_upload());
1037        }
1038
1039        // Enqueue just one more ping
1040        upload_manager.enqueue_ping(
1041            &glean,
1042            PingPayload {
1043                document_id: Uuid::new_v4().to_string(),
1044                upload_path: PATH.into(),
1045                json_body: "".into(),
1046                headers: None,
1047                body_has_info_sections: true,
1048                ping_name: "ping-name".into(),
1049                uploader_capabilities: vec![],
1050            },
1051        );
1052
1053        // Verify that we are indeed told to wait because we are at capacity
1054        match upload_manager.get_upload_task(&glean, false) {
1055            PingUploadTask::Wait { time } => {
1056                // Wait for the uploading window to reset
1057                thread::sleep(Duration::from_millis(time));
1058            }
1059            _ => panic!("Expected upload manager to return a wait task!"),
1060        };
1061
1062        let task = upload_manager.get_upload_task(&glean, false);
1063        assert!(task.is_upload());
1064    }
1065
1066    #[test]
1067    fn clearing_the_queue_works_correctly() {
1068        let (glean, dir) = new_glean(None);
1069
1070        let upload_manager = PingUploadManager::no_policy(dir.path());
1071
1072        // Enqueue a ping multiple times
1073        for _ in 0..10 {
1074            upload_manager.enqueue_ping(
1075                &glean,
1076                PingPayload {
1077                    document_id: Uuid::new_v4().to_string(),
1078                    upload_path: PATH.into(),
1079                    json_body: "".into(),
1080                    headers: None,
1081                    body_has_info_sections: true,
1082                    ping_name: "ping-name".into(),
1083                    uploader_capabilities: vec![],
1084                },
1085            );
1086        }
1087
1088        // Clear the queue
1089        drop(upload_manager.clear_ping_queue());
1090
1091        // Verify there really isn't any ping in the queue
1092        assert_eq!(
1093            upload_manager.get_upload_task(&glean, false),
1094            PingUploadTask::done()
1095        );
1096    }
1097
1098    #[test]
1099    fn clearing_the_queue_doesnt_clear_deletion_request_pings() {
1100        let (mut glean, _t) = new_glean(None);
1101
1102        // Register a ping for testing
1103        let ping_type = PingType::new(
1104            "test",
1105            true,
1106            /* send_if_empty */ true,
1107            true,
1108            true,
1109            true,
1110            vec![],
1111            vec![],
1112            true,
1113            vec![],
1114        );
1115        glean.register_ping_type(&ping_type);
1116
1117        // Submit the ping multiple times
1118        let n = 10;
1119        for _ in 0..n {
1120            ping_type.submit_sync(&glean, None);
1121        }
1122
1123        glean
1124            .internal_pings
1125            .deletion_request
1126            .submit_sync(&glean, None);
1127
1128        // Clear the queue
1129        drop(glean.upload_manager.clear_ping_queue());
1130
1131        let upload_task = glean.get_upload_task();
1132        match upload_task {
1133            PingUploadTask::Upload { request } => assert!(request.is_deletion_request()),
1134            _ => panic!("Expected upload manager to return the next request!"),
1135        }
1136
1137        // Verify there really isn't any other pings in the queue
1138        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1139    }
1140
1141    #[test]
1142    fn fills_up_queue_successfully_from_disk() {
1143        let (mut glean, dir) = new_glean(None);
1144
1145        // Register a ping for testing
1146        let ping_type = PingType::new(
1147            "test",
1148            true,
1149            /* send_if_empty */ true,
1150            true,
1151            true,
1152            true,
1153            vec![],
1154            vec![],
1155            true,
1156            vec![],
1157        );
1158        glean.register_ping_type(&ping_type);
1159
1160        // Submit the ping multiple times
1161        let n = 10;
1162        for _ in 0..n {
1163            ping_type.submit_sync(&glean, None);
1164        }
1165
1166        // Create a new upload manager pointing to the same data_path as the glean instance.
1167        let upload_manager = PingUploadManager::no_policy(dir.path());
1168
1169        // Verify the requests were properly enqueued
1170        for _ in 0..n {
1171            let task = upload_manager.get_upload_task(&glean, false);
1172            assert!(task.is_upload());
1173        }
1174
1175        // Verify that after all requests are returned, none are left
1176        assert_eq!(
1177            upload_manager.get_upload_task(&glean, false),
1178            PingUploadTask::done()
1179        );
1180    }
1181
1182    #[test]
1183    fn processes_correctly_success_upload_response() {
1184        let (mut glean, dir) = new_glean(None);
1185
1186        // Register a ping for testing
1187        let ping_type = PingType::new(
1188            "test",
1189            true,
1190            /* send_if_empty */ true,
1191            true,
1192            true,
1193            true,
1194            vec![],
1195            vec![],
1196            true,
1197            vec![],
1198        );
1199        glean.register_ping_type(&ping_type);
1200
1201        // Submit a ping
1202        ping_type.submit_sync(&glean, None);
1203
1204        // Get the pending ping directory path
1205        let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
1206
1207        // Get the submitted PingRequest
1208        match glean.get_upload_task() {
1209            PingUploadTask::Upload { request } => {
1210                // Simulate the processing of a sucessfull request
1211                let document_id = request.document_id;
1212                glean.process_ping_upload_response(&document_id, UploadResult::http_status(200));
1213                // Verify file was deleted
1214                assert!(!pending_pings_dir.join(document_id).exists());
1215            }
1216            _ => panic!("Expected upload manager to return the next request!"),
1217        }
1218
1219        // Verify that after request is returned, none are left
1220        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1221    }
1222
1223    #[test]
1224    fn processes_correctly_client_error_upload_response() {
1225        let (mut glean, dir) = new_glean(None);
1226
1227        // Register a ping for testing
1228        let ping_type = PingType::new(
1229            "test",
1230            true,
1231            /* send_if_empty */ true,
1232            true,
1233            true,
1234            true,
1235            vec![],
1236            vec![],
1237            true,
1238            vec![],
1239        );
1240        glean.register_ping_type(&ping_type);
1241
1242        // Submit a ping
1243        ping_type.submit_sync(&glean, None);
1244
1245        // Get the pending ping directory path
1246        let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
1247
1248        // Get the submitted PingRequest
1249        match glean.get_upload_task() {
1250            PingUploadTask::Upload { request } => {
1251                // Simulate the processing of a client error
1252                let document_id = request.document_id;
1253                glean.process_ping_upload_response(&document_id, UploadResult::http_status(404));
1254                // Verify file was deleted
1255                assert!(!pending_pings_dir.join(document_id).exists());
1256            }
1257            _ => panic!("Expected upload manager to return the next request!"),
1258        }
1259
1260        // Verify that after request is returned, none are left
1261        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1262    }
1263
1264    #[test]
1265    fn processes_correctly_server_error_upload_response() {
1266        let (mut glean, _t) = new_glean(None);
1267
1268        // Register a ping for testing
1269        let ping_type = PingType::new(
1270            "test",
1271            true,
1272            /* send_if_empty */ true,
1273            true,
1274            true,
1275            true,
1276            vec![],
1277            vec![],
1278            true,
1279            vec![],
1280        );
1281        glean.register_ping_type(&ping_type);
1282
1283        // Submit a ping
1284        ping_type.submit_sync(&glean, None);
1285
1286        // Get the submitted PingRequest
1287        match glean.get_upload_task() {
1288            PingUploadTask::Upload { request } => {
1289                // Simulate the processing of a client error
1290                let document_id = request.document_id;
1291                glean.process_ping_upload_response(&document_id, UploadResult::http_status(500));
1292                // Verify this ping was indeed re-enqueued
1293                match glean.get_upload_task() {
1294                    PingUploadTask::Upload { request } => {
1295                        assert_eq!(document_id, request.document_id);
1296                    }
1297                    _ => panic!("Expected upload manager to return the next request!"),
1298                }
1299            }
1300            _ => panic!("Expected upload manager to return the next request!"),
1301        }
1302
1303        // Verify that after request is returned, none are left
1304        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1305    }
1306
1307    #[test]
1308    fn processes_correctly_unrecoverable_upload_response() {
1309        let (mut glean, dir) = new_glean(None);
1310
1311        // Register a ping for testing
1312        let ping_type = PingType::new(
1313            "test",
1314            true,
1315            /* send_if_empty */ true,
1316            true,
1317            true,
1318            true,
1319            vec![],
1320            vec![],
1321            true,
1322            vec![],
1323        );
1324        glean.register_ping_type(&ping_type);
1325
1326        // Submit a ping
1327        ping_type.submit_sync(&glean, None);
1328
1329        // Get the pending ping directory path
1330        let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
1331
1332        // Get the submitted PingRequest
1333        match glean.get_upload_task() {
1334            PingUploadTask::Upload { request } => {
1335                // Simulate the processing of a client error
1336                let document_id = request.document_id;
1337                glean.process_ping_upload_response(
1338                    &document_id,
1339                    UploadResult::unrecoverable_failure(),
1340                );
1341                // Verify file was deleted
1342                assert!(!pending_pings_dir.join(document_id).exists());
1343            }
1344            _ => panic!("Expected upload manager to return the next request!"),
1345        }
1346
1347        // Verify that after request is returned, none are left
1348        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1349    }
1350
1351    #[test]
1352    fn new_pings_are_added_while_upload_in_progress() {
1353        let (glean, dir) = new_glean(None);
1354
1355        let upload_manager = PingUploadManager::no_policy(dir.path());
1356
1357        let doc1 = Uuid::new_v4().to_string();
1358        let path1 = format!("/submit/app_id/test-ping/1/{}", doc1);
1359
1360        let doc2 = Uuid::new_v4().to_string();
1361        let path2 = format!("/submit/app_id/test-ping/1/{}", doc2);
1362
1363        // Enqueue a ping
1364        upload_manager.enqueue_ping(
1365            &glean,
1366            PingPayload {
1367                document_id: doc1.clone(),
1368                upload_path: path1,
1369                json_body: "".into(),
1370                headers: None,
1371                body_has_info_sections: true,
1372                ping_name: "test-ping".into(),
1373                uploader_capabilities: vec![],
1374            },
1375        );
1376
1377        // Try and get the first request.
1378        let req = match upload_manager.get_upload_task(&glean, false) {
1379            PingUploadTask::Upload { request } => request,
1380            _ => panic!("Expected upload manager to return the next request!"),
1381        };
1382        assert_eq!(doc1, req.document_id);
1383
1384        // Schedule the next one while the first one is "in progress"
1385        upload_manager.enqueue_ping(
1386            &glean,
1387            PingPayload {
1388                document_id: doc2.clone(),
1389                upload_path: path2,
1390                json_body: "".into(),
1391                headers: None,
1392                body_has_info_sections: true,
1393                ping_name: "test-ping".into(),
1394                uploader_capabilities: vec![],
1395            },
1396        );
1397
1398        // Mark as processed
1399        upload_manager.process_ping_upload_response(
1400            &glean,
1401            &req.document_id,
1402            UploadResult::http_status(200),
1403        );
1404
1405        // Get the second request.
1406        let req = match upload_manager.get_upload_task(&glean, false) {
1407            PingUploadTask::Upload { request } => request,
1408            _ => panic!("Expected upload manager to return the next request!"),
1409        };
1410        assert_eq!(doc2, req.document_id);
1411
1412        // Mark as processed
1413        upload_manager.process_ping_upload_response(
1414            &glean,
1415            &req.document_id,
1416            UploadResult::http_status(200),
1417        );
1418
1419        // ... and then we're done.
1420        assert_eq!(
1421            upload_manager.get_upload_task(&glean, false),
1422            PingUploadTask::done()
1423        );
1424    }
1425
1426    #[test]
1427    fn adds_debug_view_header_to_requests_when_tag_is_set() {
1428        let (mut glean, _t) = new_glean(None);
1429
1430        glean.set_debug_view_tag("valid-tag");
1431
1432        // Register a ping for testing
1433        let ping_type = PingType::new(
1434            "test",
1435            true,
1436            /* send_if_empty */ true,
1437            true,
1438            true,
1439            true,
1440            vec![],
1441            vec![],
1442            true,
1443            vec![],
1444        );
1445        glean.register_ping_type(&ping_type);
1446
1447        // Submit a ping
1448        ping_type.submit_sync(&glean, None);
1449
1450        // Get the submitted PingRequest
1451        match glean.get_upload_task() {
1452            PingUploadTask::Upload { request } => {
1453                assert_eq!(request.headers.get("X-Debug-ID").unwrap(), "valid-tag")
1454            }
1455            _ => panic!("Expected upload manager to return the next request!"),
1456        }
1457    }
1458
1459    #[test]
1460    fn duplicates_are_not_enqueued() {
1461        let (glean, dir) = new_glean(None);
1462
1463        // Create a new upload manager so that we have access to its functions directly,
1464        // make it synchronous so we don't have to manually wait for the scanning to finish.
1465        let upload_manager = PingUploadManager::no_policy(dir.path());
1466
1467        let doc_id = Uuid::new_v4().to_string();
1468        let path = format!("/submit/app_id/test-ping/1/{}", doc_id);
1469
1470        // Try to enqueue a ping with the same doc_id twice
1471        upload_manager.enqueue_ping(
1472            &glean,
1473            PingPayload {
1474                document_id: doc_id.clone(),
1475                upload_path: path.clone(),
1476                json_body: "".into(),
1477                headers: None,
1478                body_has_info_sections: true,
1479                ping_name: "test-ping".into(),
1480                uploader_capabilities: vec![],
1481            },
1482        );
1483        upload_manager.enqueue_ping(
1484            &glean,
1485            PingPayload {
1486                document_id: doc_id,
1487                upload_path: path,
1488                json_body: "".into(),
1489                headers: None,
1490                body_has_info_sections: true,
1491                ping_name: "test-ping".into(),
1492                uploader_capabilities: vec![],
1493            },
1494        );
1495
1496        // Get a task once
1497        let task = upload_manager.get_upload_task(&glean, false);
1498        assert!(task.is_upload());
1499
1500        // There should be no more queued tasks
1501        assert_eq!(
1502            upload_manager.get_upload_task(&glean, false),
1503            PingUploadTask::done()
1504        );
1505    }
1506
1507    #[test]
1508    fn maximum_of_recoverable_errors_is_enforced_for_uploading_window() {
1509        let (mut glean, dir) = new_glean(None);
1510
1511        // Register a ping for testing
1512        let ping_type = PingType::new(
1513            "test",
1514            true,
1515            /* send_if_empty */ true,
1516            true,
1517            true,
1518            true,
1519            vec![],
1520            vec![],
1521            true,
1522            vec![],
1523        );
1524        glean.register_ping_type(&ping_type);
1525
1526        // Submit the ping multiple times
1527        let n = 5;
1528        for _ in 0..n {
1529            ping_type.submit_sync(&glean, None);
1530        }
1531
1532        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1533
1534        // Set a policy for max recoverable failures, this is usually disabled for tests.
1535        let max_recoverable_failures = 3;
1536        upload_manager
1537            .policy
1538            .set_max_recoverable_failures(Some(max_recoverable_failures));
1539
1540        // Return the max recoverable error failures in a row
1541        for _ in 0..max_recoverable_failures {
1542            match upload_manager.get_upload_task(&glean, false) {
1543                PingUploadTask::Upload { request } => {
1544                    upload_manager.process_ping_upload_response(
1545                        &glean,
1546                        &request.document_id,
1547                        UploadResult::recoverable_failure(),
1548                    );
1549                }
1550                _ => panic!("Expected upload manager to return the next request!"),
1551            }
1552        }
1553
1554        // Verify that after returning the max amount of recoverable failures,
1555        // we are done even though we haven't gotten all the enqueued requests.
1556        assert_eq!(
1557            upload_manager.get_upload_task(&glean, false),
1558            PingUploadTask::done()
1559        );
1560
1561        // Verify all requests are returned when we try again.
1562        for _ in 0..n {
1563            let task = upload_manager.get_upload_task(&glean, false);
1564            assert!(task.is_upload());
1565        }
1566    }
1567
1568    #[test]
1569    fn quota_is_enforced_when_enqueueing_cached_pings() {
1570        let (mut glean, dir) = new_glean(None);
1571
1572        // Register a ping for testing
1573        let ping_type = PingType::new(
1574            "test",
1575            true,
1576            /* send_if_empty */ true,
1577            true,
1578            true,
1579            true,
1580            vec![],
1581            vec![],
1582            true,
1583            vec![],
1584        );
1585        glean.register_ping_type(&ping_type);
1586
1587        // Submit the ping multiple times
1588        let n = 10;
1589        for _ in 0..n {
1590            ping_type.submit_sync(&glean, None);
1591        }
1592
1593        let directory_manager = PingDirectoryManager::new(dir.path());
1594        let pending_pings = directory_manager.process_dirs().pending_pings;
1595        // The pending pings array is sorted by date in ascending order,
1596        // the newest element is the last one.
1597        let (_, newest_ping) = &pending_pings.last().unwrap();
1598        let PingPayload {
1599            document_id: newest_ping_id,
1600            ..
1601        } = &newest_ping;
1602
1603        // Create a new upload manager pointing to the same data_path as the glean instance.
1604        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1605
1606        // Set the quota to just a little over the size on an empty ping file.
1607        // This way we can check that one ping is kept and all others are deleted.
1608        //
1609        // From manual testing I figured out an empty ping file is 324bytes,
1610        // I am setting this a little over just so that minor changes to the ping structure
1611        // don't immediatelly break this.
1612        upload_manager
1613            .policy
1614            .set_max_pending_pings_directory_size(Some(500));
1615
1616        // Get a task once
1617        // One ping should have been enqueued.
1618        // Make sure it is the newest ping.
1619        match upload_manager.get_upload_task(&glean, false) {
1620            PingUploadTask::Upload { request } => assert_eq!(&request.document_id, newest_ping_id),
1621            _ => panic!("Expected upload manager to return the next request!"),
1622        }
1623
1624        // Verify that no other requests were returned,
1625        // they should all have been deleted because pending pings quota was hit.
1626        assert_eq!(
1627            upload_manager.get_upload_task(&glean, false),
1628            PingUploadTask::done()
1629        );
1630
1631        // Verify that the correct number of deleted pings was recorded
1632        assert_eq!(
1633            n - 1,
1634            upload_manager
1635                .upload_metrics
1636                .deleted_pings_after_quota_hit
1637                .get_value(&glean, Some("metrics"))
1638                .unwrap()
1639        );
1640        assert_eq!(
1641            n,
1642            upload_manager
1643                .upload_metrics
1644                .pending_pings
1645                .get_value(&glean, Some("metrics"))
1646                .unwrap()
1647        );
1648    }
1649
1650    #[test]
1651    fn number_quota_is_enforced_when_enqueueing_cached_pings() {
1652        let (mut glean, dir) = new_glean(None);
1653
1654        // Register a ping for testing
1655        let ping_type = PingType::new(
1656            "test",
1657            true,
1658            /* send_if_empty */ true,
1659            true,
1660            true,
1661            true,
1662            vec![],
1663            vec![],
1664            true,
1665            vec![],
1666        );
1667        glean.register_ping_type(&ping_type);
1668
1669        // How many pings we allow at maximum
1670        let count_quota = 3;
1671        // The number of pings we fill the pending pings directory with.
1672        let n = 10;
1673
1674        // Submit the ping multiple times
1675        for _ in 0..n {
1676            ping_type.submit_sync(&glean, None);
1677        }
1678
1679        let directory_manager = PingDirectoryManager::new(dir.path());
1680        let pending_pings = directory_manager.process_dirs().pending_pings;
1681        // The pending pings array is sorted by date in ascending order,
1682        // the newest element is the last one.
1683        let expected_pings = pending_pings
1684            .iter()
1685            .rev()
1686            .take(count_quota)
1687            .map(|(_, ping)| ping.document_id.clone())
1688            .collect::<Vec<_>>();
1689
1690        // Create a new upload manager pointing to the same data_path as the glean instance.
1691        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1692
1693        upload_manager
1694            .policy
1695            .set_max_pending_pings_count(Some(count_quota as u64));
1696
1697        // Get a task once
1698        // One ping should have been enqueued.
1699        // Make sure it is the newest ping.
1700        for ping_id in expected_pings.iter().rev() {
1701            match upload_manager.get_upload_task(&glean, false) {
1702                PingUploadTask::Upload { request } => assert_eq!(&request.document_id, ping_id),
1703                _ => panic!("Expected upload manager to return the next request!"),
1704            }
1705        }
1706
1707        // Verify that no other requests were returned,
1708        // they should all have been deleted because pending pings quota was hit.
1709        assert_eq!(
1710            upload_manager.get_upload_task(&glean, false),
1711            PingUploadTask::done()
1712        );
1713
1714        // Verify that the correct number of deleted pings was recorded
1715        assert_eq!(
1716            (n - count_quota) as i32,
1717            upload_manager
1718                .upload_metrics
1719                .deleted_pings_after_quota_hit
1720                .get_value(&glean, Some("metrics"))
1721                .unwrap()
1722        );
1723        assert_eq!(
1724            n as i32,
1725            upload_manager
1726                .upload_metrics
1727                .pending_pings
1728                .get_value(&glean, Some("metrics"))
1729                .unwrap()
1730        );
1731    }
1732
1733    #[test]
1734    fn size_and_count_quota_work_together_size_first() {
1735        let (mut glean, dir) = new_glean(None);
1736
1737        // Register a ping for testing
1738        let ping_type = PingType::new(
1739            "test",
1740            true,
1741            /* send_if_empty */ true,
1742            true,
1743            true,
1744            true,
1745            vec![],
1746            vec![],
1747            true,
1748            vec![],
1749        );
1750        glean.register_ping_type(&ping_type);
1751
1752        let expected_number_of_pings = 3;
1753        // The number of pings we fill the pending pings directory with.
1754        let n = 10;
1755
1756        // Submit the ping multiple times
1757        for _ in 0..n {
1758            ping_type.submit_sync(&glean, None);
1759        }
1760
1761        let directory_manager = PingDirectoryManager::new(dir.path());
1762        let pending_pings = directory_manager.process_dirs().pending_pings;
1763        // The pending pings array is sorted by date in ascending order,
1764        // the newest element is the last one.
1765        let expected_pings = pending_pings
1766            .iter()
1767            .rev()
1768            .take(expected_number_of_pings)
1769            .map(|(_, ping)| ping.document_id.clone())
1770            .collect::<Vec<_>>();
1771
1772        // Create a new upload manager pointing to the same data_path as the glean instance.
1773        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1774
1775        // From manual testing we figured out a basically empty ping file is 399 bytes,
1776        // so this allows 3 pings with some headroom in case of future changes.
1777        upload_manager
1778            .policy
1779            .set_max_pending_pings_directory_size(Some(1300));
1780        upload_manager.policy.set_max_pending_pings_count(Some(5));
1781
1782        // Get a task once
1783        // One ping should have been enqueued.
1784        // Make sure it is the newest ping.
1785        for ping_id in expected_pings.iter().rev() {
1786            match upload_manager.get_upload_task(&glean, false) {
1787                PingUploadTask::Upload { request } => assert_eq!(&request.document_id, ping_id),
1788                _ => panic!("Expected upload manager to return the next request!"),
1789            }
1790        }
1791
1792        // Verify that no other requests were returned,
1793        // they should all have been deleted because pending pings quota was hit.
1794        assert_eq!(
1795            upload_manager.get_upload_task(&glean, false),
1796            PingUploadTask::done()
1797        );
1798
1799        // Verify that the correct number of deleted pings was recorded
1800        assert_eq!(
1801            (n - expected_number_of_pings) as i32,
1802            upload_manager
1803                .upload_metrics
1804                .deleted_pings_after_quota_hit
1805                .get_value(&glean, Some("metrics"))
1806                .unwrap()
1807        );
1808        assert_eq!(
1809            n as i32,
1810            upload_manager
1811                .upload_metrics
1812                .pending_pings
1813                .get_value(&glean, Some("metrics"))
1814                .unwrap()
1815        );
1816        // Verify the labeled deletion counter attributes deletions to size_quota
1817        assert_eq!(
1818            (n - expected_number_of_pings) as i32,
1819            upload_manager
1820                .upload_metrics
1821                .pending_pings_deleted
1822                .get("size_quota")
1823                .get_value(&glean, Some("health"))
1824                .unwrap()
1825        );
1826        assert!(upload_manager
1827            .upload_metrics
1828            .pending_pings_deleted
1829            .get("count_quota")
1830            .get_value(&glean, Some("health"))
1831            .is_none());
1832    }
1833
1834    #[test]
1835    fn size_and_count_quota_work_together_count_first() {
1836        let (mut glean, dir) = new_glean(None);
1837
1838        // Register a ping for testing
1839        let ping_type = PingType::new(
1840            "test",
1841            true,
1842            /* send_if_empty */ true,
1843            true,
1844            true,
1845            true,
1846            vec![],
1847            vec![],
1848            true,
1849            vec![],
1850        );
1851        glean.register_ping_type(&ping_type);
1852
1853        let expected_number_of_pings = 2;
1854        // The number of pings we fill the pending pings directory with.
1855        let n = 10;
1856
1857        // Submit the ping multiple times
1858        for _ in 0..n {
1859            ping_type.submit_sync(&glean, None);
1860        }
1861
1862        let directory_manager = PingDirectoryManager::new(dir.path());
1863        let pending_pings = directory_manager.process_dirs().pending_pings;
1864        // The pending pings array is sorted by date in ascending order,
1865        // the newest element is the last one.
1866        let expected_pings = pending_pings
1867            .iter()
1868            .rev()
1869            .take(expected_number_of_pings)
1870            .map(|(_, ping)| ping.document_id.clone())
1871            .collect::<Vec<_>>();
1872
1873        // Create a new upload manager pointing to the same data_path as the glean instance.
1874        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1875
1876        // Set a large enough size quota so it never triggers before the count quota does.
1877        upload_manager
1878            .policy
1879            .set_max_pending_pings_directory_size(Some(100_000));
1880        upload_manager.policy.set_max_pending_pings_count(Some(2));
1881
1882        // Get a task once
1883        // One ping should have been enqueued.
1884        // Make sure it is the newest ping.
1885        for ping_id in expected_pings.iter().rev() {
1886            match upload_manager.get_upload_task(&glean, false) {
1887                PingUploadTask::Upload { request } => assert_eq!(&request.document_id, ping_id),
1888                _ => panic!("Expected upload manager to return the next request!"),
1889            }
1890        }
1891
1892        // Verify that no other requests were returned,
1893        // they should all have been deleted because pending pings quota was hit.
1894        assert_eq!(
1895            upload_manager.get_upload_task(&glean, false),
1896            PingUploadTask::done()
1897        );
1898
1899        // Verify that the correct number of deleted pings was recorded
1900        assert_eq!(
1901            (n - expected_number_of_pings) as i32,
1902            upload_manager
1903                .upload_metrics
1904                .deleted_pings_after_quota_hit
1905                .get_value(&glean, Some("metrics"))
1906                .unwrap()
1907        );
1908        assert_eq!(
1909            n as i32,
1910            upload_manager
1911                .upload_metrics
1912                .pending_pings
1913                .get_value(&glean, Some("metrics"))
1914                .unwrap()
1915        );
1916        // Verify the labeled deletion counter attributes deletions to count_quota
1917        assert_eq!(
1918            (n - expected_number_of_pings) as i32,
1919            upload_manager
1920                .upload_metrics
1921                .pending_pings_deleted
1922                .get("count_quota")
1923                .get_value(&glean, Some("health"))
1924                .unwrap()
1925        );
1926        assert!(upload_manager
1927            .upload_metrics
1928            .pending_pings_deleted
1929            .get("size_quota")
1930            .get_value(&glean, Some("health"))
1931            .is_none());
1932    }
1933
1934    #[test]
1935    fn pending_pings_deleted_is_not_recorded_when_quota_not_hit() {
1936        let (mut glean, dir) = new_glean(None);
1937
1938        let ping_type = PingType::new(
1939            "test",
1940            true,
1941            /* send_if_empty */ true,
1942            true,
1943            true,
1944            true,
1945            vec![],
1946            vec![],
1947            true,
1948            vec![],
1949        );
1950        glean.register_ping_type(&ping_type);
1951
1952        // Submit fewer pings than any quota.
1953        for _ in 0..3 {
1954            ping_type.submit_sync(&glean, None);
1955        }
1956
1957        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1958        upload_manager.policy.set_max_pending_pings_count(Some(10));
1959        upload_manager
1960            .policy
1961            .set_max_pending_pings_directory_size(Some(1024 * 1024));
1962
1963        upload_manager.get_upload_task(&glean, false);
1964
1965        assert!(upload_manager
1966            .upload_metrics
1967            .pending_pings_deleted
1968            .get("count_quota")
1969            .get_value(&glean, Some("health"))
1970            .is_none());
1971        assert!(upload_manager
1972            .upload_metrics
1973            .pending_pings_deleted
1974            .get("size_quota")
1975            .get_value(&glean, Some("health"))
1976            .is_none());
1977    }
1978
1979    #[test]
1980    fn pending_pings_config_overrides_are_applied() {
1981        let (_, dir) = new_glean(None);
1982
1983        let mut upload_manager = PingUploadManager::new(dir.path(), "test");
1984
1985        let custom_count: u64 = 42;
1986        let custom_size: u64 = 999_999;
1987        upload_manager.set_max_pending_pings_count(custom_count);
1988        upload_manager.set_max_pending_pings_directory_size(custom_size);
1989
1990        assert_eq!(
1991            custom_count,
1992            upload_manager.policy.max_pending_pings_count()
1993        );
1994        assert_eq!(
1995            custom_size,
1996            upload_manager.policy.max_pending_pings_directory_size()
1997        );
1998    }
1999
2000    #[test]
2001    fn maximum_wait_attemps_is_enforced() {
2002        let (glean, dir) = new_glean(None);
2003
2004        let mut upload_manager = PingUploadManager::no_policy(dir.path());
2005
2006        // Define a max_wait_attemps policy, this is disabled for tests by default.
2007        let max_wait_attempts = 3;
2008        upload_manager
2009            .policy
2010            .set_max_wait_attempts(Some(max_wait_attempts));
2011
2012        // Add a rate limiter to the upload mangager with max of 1 ping 5secs.
2013        //
2014        // We arbitrarily set the maximum pings per interval to a very low number,
2015        // when the rate limiter reaches it's limit get_upload_task returns a PingUploadTask::Wait,
2016        // which will allow us to test the limitations around returning too many of those in a row.
2017        let secs_per_interval = 5;
2018        let max_pings_per_interval = 1;
2019        upload_manager.set_rate_limiter(secs_per_interval, max_pings_per_interval);
2020
2021        // Enqueue two pings
2022        upload_manager.enqueue_ping(
2023            &glean,
2024            PingPayload {
2025                document_id: Uuid::new_v4().to_string(),
2026                upload_path: PATH.into(),
2027                json_body: "".into(),
2028                headers: None,
2029                body_has_info_sections: true,
2030                ping_name: "ping-name".into(),
2031                uploader_capabilities: vec![],
2032            },
2033        );
2034        upload_manager.enqueue_ping(
2035            &glean,
2036            PingPayload {
2037                document_id: Uuid::new_v4().to_string(),
2038                upload_path: PATH.into(),
2039                json_body: "".into(),
2040                headers: None,
2041                body_has_info_sections: true,
2042                ping_name: "ping-name".into(),
2043                uploader_capabilities: vec![],
2044            },
2045        );
2046
2047        // Get the first ping, it should be returned normally.
2048        match upload_manager.get_upload_task(&glean, false) {
2049            PingUploadTask::Upload { .. } => {}
2050            _ => panic!("Expected upload manager to return the next request!"),
2051        }
2052
2053        // Try to get the next ping,
2054        // we should be throttled and thus get a PingUploadTask::Wait.
2055        // Check that we are indeed allowed to get this response as many times as expected.
2056        for _ in 0..max_wait_attempts {
2057            let task = upload_manager.get_upload_task(&glean, false);
2058            assert!(task.is_wait());
2059        }
2060
2061        // Check that after we get PingUploadTask::Wait the allowed number of times,
2062        // we then get PingUploadTask::Done.
2063        assert_eq!(
2064            upload_manager.get_upload_task(&glean, false),
2065            PingUploadTask::done()
2066        );
2067
2068        // Wait for the rate limiter to allow upload tasks again.
2069        thread::sleep(Duration::from_secs(secs_per_interval));
2070
2071        // Check that we are allowed again to get pings.
2072        let task = upload_manager.get_upload_task(&glean, false);
2073        assert!(task.is_upload());
2074
2075        // And once we are done we don't need to wait anymore.
2076        assert_eq!(
2077            upload_manager.get_upload_task(&glean, false),
2078            PingUploadTask::done()
2079        );
2080    }
2081
2082    #[test]
2083    fn wait_task_contains_expected_wait_time_when_pending_pings_dir_not_processed_yet() {
2084        let (glean, dir) = new_glean(None);
2085        let upload_manager = PingUploadManager::new(dir.path(), "test");
2086        match upload_manager.get_upload_task(&glean, false) {
2087            PingUploadTask::Wait { time } => {
2088                assert_eq!(time, WAIT_TIME_FOR_PING_PROCESSING);
2089            }
2090            _ => panic!("Expected upload manager to return a wait task!"),
2091        };
2092    }
2093
2094    #[test]
2095    fn cannot_enqueue_ping_while_its_being_processed() {
2096        let (glean, dir) = new_glean(None);
2097
2098        let upload_manager = PingUploadManager::no_policy(dir.path());
2099
2100        // Enqueue a ping and start processing it
2101        let identifier = &Uuid::new_v4();
2102        let ping = PingPayload {
2103            document_id: identifier.to_string(),
2104            upload_path: PATH.into(),
2105            json_body: "".into(),
2106            headers: None,
2107            body_has_info_sections: true,
2108            ping_name: "ping-name".into(),
2109            uploader_capabilities: vec![],
2110        };
2111        upload_manager.enqueue_ping(&glean, ping);
2112        assert!(upload_manager.get_upload_task(&glean, false).is_upload());
2113
2114        // Attempt to re-enqueue the same ping
2115        let ping = PingPayload {
2116            document_id: identifier.to_string(),
2117            upload_path: PATH.into(),
2118            json_body: "".into(),
2119            headers: None,
2120            body_has_info_sections: true,
2121            ping_name: "ping-name".into(),
2122            uploader_capabilities: vec![],
2123        };
2124        upload_manager.enqueue_ping(&glean, ping);
2125
2126        // No new pings should have been enqueued so the upload task is Done.
2127        assert_eq!(
2128            upload_manager.get_upload_task(&glean, false),
2129            PingUploadTask::done()
2130        );
2131
2132        // Process the upload response
2133        upload_manager.process_ping_upload_response(
2134            &glean,
2135            &identifier.to_string(),
2136            UploadResult::http_status(200),
2137        );
2138    }
2139
2140    #[test]
2141    fn stores_pings_during_submission_and_upload_if_enabled() {
2142        let (mut glean, _t) = new_glean(None);
2143        glean.set_store_submitted_pings_enabled(true);
2144
2145        // Register a ping for testing
2146        let ping_type = PingType::new(
2147            "test",
2148            true,
2149            /* send_if_empty */ true,
2150            true,
2151            true,
2152            true,
2153            vec![],
2154            vec![],
2155            true,
2156            vec![],
2157        );
2158        glean.register_ping_type(&ping_type);
2159
2160        // Submit a ping
2161        ping_type.submit_sync(&glean, None);
2162
2163        let pings = glean.storage().get_all_submitted_pings();
2164        assert_eq!(pings.len(), 1);
2165        let ping = pings.first().unwrap();
2166        assert!(ping.submitted_date.0 <= Utc::now());
2167        assert!(ping.uploaded_date.is_none());
2168
2169        // Get the submitted PingRequest
2170        match glean.get_upload_task() {
2171            PingUploadTask::Upload { request } => {
2172                // Simulate the processing of a sucessful request
2173                let document_id = request.document_id;
2174                glean.process_ping_upload_response(&document_id, UploadResult::http_status(200));
2175            }
2176            _ => panic!("Expected upload manager to return the next request!"),
2177        }
2178
2179        let pings = glean.storage().get_all_submitted_pings();
2180        assert_eq!(pings.len(), 1);
2181        let ping = pings.first().unwrap();
2182        assert!(ping.submitted_date.0 <= Utc::now());
2183        assert!(ping.uploaded_date.is_some());
2184
2185        // Verify that after request is returned, none are left
2186        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
2187    }
2188
2189    #[test]
2190    fn stores_pings_during_submission_and_marks_as_upload_failed_when_appropriate() {
2191        let (mut glean, _t) = new_glean(None);
2192        glean.set_store_submitted_pings_enabled(true);
2193
2194        // Register a ping for testing
2195        let ping_type = PingType::new(
2196            "test",
2197            true,
2198            /* send_if_empty */ true,
2199            true,
2200            true,
2201            true,
2202            vec![],
2203            vec![],
2204            true,
2205            vec![],
2206        );
2207        glean.register_ping_type(&ping_type);
2208
2209        // Submit a ping
2210        ping_type.submit_sync(&glean, None);
2211
2212        let pings = glean.storage().get_all_submitted_pings();
2213        assert_eq!(pings.len(), 1);
2214        let ping = pings.first().unwrap();
2215        assert!(ping.submitted_date.0 <= Utc::now());
2216        assert!(ping.uploaded_date.is_none());
2217
2218        // Get the submitted PingRequest
2219        match glean.get_upload_task() {
2220            PingUploadTask::Upload { request } => {
2221                // Simulate the processing of a sucessful request
2222                let document_id = request.document_id;
2223                glean.process_ping_upload_response(&document_id, UploadResult::http_status(400));
2224            }
2225            _ => panic!("Expected upload manager to return the next request!"),
2226        }
2227
2228        let pings = glean.storage().get_all_submitted_pings();
2229        assert_eq!(pings.len(), 1);
2230        let ping = pings.first().unwrap();
2231        assert!(ping.submitted_date.0 <= Utc::now());
2232        assert!(ping.upload_failed.is_some());
2233        assert!(ping.uploaded_date.is_none());
2234
2235        // Verify that after request is returned, none are left
2236        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
2237    }
2238}