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                #[cfg(feature = "sqlite")]
803                if glean.store_submitted_pings_enabled {
804                    glean
805                        .storage()
806                        .mark_ping_as_uploaded(document_id, Utc::now());
807                }
808                self.directory_manager.delete_file(document_id);
809            }
810
811            UnrecoverableFailure { .. } | HttpStatus { code: 400..=499 } | Incapable { .. } => {
812                log::warn!(
813                    "Unrecoverable upload failure while attempting to send ping {}. Error was {:?}",
814                    document_id,
815                    status
816                );
817                if let Some((success_id, failure_id)) = send_ids {
818                    self.upload_metrics.send_success.cancel_sync(success_id);
819                    self.upload_metrics
820                        .send_failure
821                        .set_stop_and_accumulate(glean, failure_id, stop_time);
822                }
823                #[cfg(feature = "sqlite")]
824                if glean.store_submitted_pings_enabled {
825                    glean.storage().mark_ping_as_upload_failed(document_id);
826                }
827                self.directory_manager.delete_file(document_id);
828            }
829
830            RecoverableFailure { .. } | HttpStatus { .. } => {
831                log::warn!(
832                    "Recoverable upload failure while attempting to send ping {}, will retry. Error was {:?}",
833                    document_id,
834                    status
835                );
836                if let Some((success_id, failure_id)) = send_ids {
837                    self.upload_metrics.send_success.cancel_sync(success_id);
838                    self.upload_metrics
839                        .send_failure
840                        .set_stop_and_accumulate(glean, failure_id, stop_time);
841                }
842                self.enqueue_ping_from_file(glean, document_id);
843                self.recoverable_failure_count
844                    .fetch_add(1, Ordering::SeqCst);
845            }
846
847            Done { .. } => {
848                log::debug!("Uploader signaled Done. Exiting.");
849                if let Some((success_id, failure_id)) = send_ids {
850                    self.upload_metrics.send_success.cancel_sync(success_id);
851                    self.upload_metrics.send_failure.cancel_sync(failure_id);
852                }
853                return UploadTaskAction::End;
854            }
855        };
856
857        UploadTaskAction::Next
858    }
859}
860
861/// Splits log message into chunks on Android.
862#[cfg(target_os = "android")]
863pub fn chunked_log_info(path: &str, payload: &str) {
864    // Since the logcat ring buffer size is configurable, but it's 'max payload' size is not,
865    // we must break apart long pings into chunks no larger than the max payload size of 4076b.
866    // We leave some head space for our prefix.
867    const MAX_LOG_PAYLOAD_SIZE_BYTES: usize = 4000;
868
869    // If the length of the ping will fit within one logcat payload, then we can
870    // short-circuit here and avoid some overhead, otherwise we must split up the
871    // message so that we don't truncate it.
872    if path.len() + payload.len() <= MAX_LOG_PAYLOAD_SIZE_BYTES {
873        log::info!("Glean ping to URL: {}\n{}", path, payload);
874        return;
875    }
876
877    // Otherwise we break it apart into chunks of smaller size,
878    // prefixing it with the path and a counter.
879    let mut start = 0;
880    let mut end = MAX_LOG_PAYLOAD_SIZE_BYTES;
881    let mut chunk_idx = 1;
882    // Might be off by 1 on edge cases, but do we really care?
883    let total_chunks = payload.len() / MAX_LOG_PAYLOAD_SIZE_BYTES + 1;
884
885    while end < payload.len() {
886        // Find char boundary from the end.
887        // It's UTF-8, so it is within 4 bytes from here.
888        for _ in 0..4 {
889            if payload.is_char_boundary(end) {
890                break;
891            }
892            end -= 1;
893        }
894
895        log::info!(
896            "Glean ping to URL: {} [Part {} of {}]\n{}",
897            path,
898            chunk_idx,
899            total_chunks,
900            &payload[start..end]
901        );
902
903        // Move on with the string
904        start = end;
905        end = end + MAX_LOG_PAYLOAD_SIZE_BYTES;
906        chunk_idx += 1;
907    }
908
909    // Print any suffix left
910    if start < payload.len() {
911        log::info!(
912            "Glean ping to URL: {} [Part {} of {}]\n{}",
913            path,
914            chunk_idx,
915            total_chunks,
916            &payload[start..]
917        );
918    }
919}
920
921/// Logs payload in one go (all other OS).
922#[cfg(not(target_os = "android"))]
923pub fn chunked_log_info(_path: &str, payload: &str) {
924    log::info!("{}", payload)
925}
926
927#[cfg(test)]
928mod test {
929    use std::thread;
930    use uuid::Uuid;
931
932    use super::*;
933    use crate::metrics::PingType;
934    use crate::{tests::new_glean, PENDING_PINGS_DIRECTORY};
935
936    const PATH: &str = "/submit/app_id/ping_name/schema_version/doc_id";
937
938    #[test]
939    fn doesnt_error_when_there_are_no_pending_pings() {
940        let (glean, _t) = new_glean(None);
941
942        // Try and get the next request.
943        // Verify request was not returned
944        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
945    }
946
947    #[test]
948    fn returns_ping_request_when_there_is_one() {
949        let (glean, dir) = new_glean(None);
950
951        let upload_manager = PingUploadManager::no_policy(dir.path());
952
953        // Enqueue a ping
954        upload_manager.enqueue_ping(
955            &glean,
956            PingPayload {
957                document_id: Uuid::new_v4().to_string(),
958                upload_path: PATH.into(),
959                json_body: "".into(),
960                headers: None,
961                body_has_info_sections: true,
962                ping_name: "ping-name".into(),
963                uploader_capabilities: vec![],
964            },
965        );
966
967        // Try and get the next request.
968        // Verify request was returned
969        let task = upload_manager.get_upload_task(&glean, false);
970        assert!(task.is_upload());
971    }
972
973    #[test]
974    fn returns_as_many_ping_requests_as_there_are() {
975        let (glean, dir) = new_glean(None);
976
977        let upload_manager = PingUploadManager::no_policy(dir.path());
978
979        // Enqueue a ping multiple times
980        let n = 10;
981        for _ in 0..n {
982            upload_manager.enqueue_ping(
983                &glean,
984                PingPayload {
985                    document_id: Uuid::new_v4().to_string(),
986                    upload_path: PATH.into(),
987                    json_body: "".into(),
988                    headers: None,
989                    body_has_info_sections: true,
990                    ping_name: "ping-name".into(),
991                    uploader_capabilities: vec![],
992                },
993            );
994        }
995
996        // Verify a request is returned for each submitted ping
997        for _ in 0..n {
998            let task = upload_manager.get_upload_task(&glean, false);
999            assert!(task.is_upload());
1000        }
1001
1002        // Verify that after all requests are returned, none are left
1003        assert_eq!(
1004            upload_manager.get_upload_task(&glean, false),
1005            PingUploadTask::done()
1006        );
1007    }
1008
1009    #[test]
1010    fn limits_the_number_of_pings_when_there_is_rate_limiting() {
1011        let (glean, dir) = new_glean(None);
1012
1013        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1014
1015        // Add a rate limiter to the upload mangager with max of 10 pings every 3 seconds.
1016        let max_pings_per_interval = 10;
1017        upload_manager.set_rate_limiter(3, 10);
1018
1019        // Enqueue the max number of pings allowed per uploading window
1020        for _ in 0..max_pings_per_interval {
1021            upload_manager.enqueue_ping(
1022                &glean,
1023                PingPayload {
1024                    document_id: Uuid::new_v4().to_string(),
1025                    upload_path: PATH.into(),
1026                    json_body: "".into(),
1027                    headers: None,
1028                    body_has_info_sections: true,
1029                    ping_name: "ping-name".into(),
1030                    uploader_capabilities: vec![],
1031                },
1032            );
1033        }
1034
1035        // Verify a request is returned for each submitted ping
1036        for _ in 0..max_pings_per_interval {
1037            let task = upload_manager.get_upload_task(&glean, false);
1038            assert!(task.is_upload());
1039        }
1040
1041        // Enqueue just one more ping
1042        upload_manager.enqueue_ping(
1043            &glean,
1044            PingPayload {
1045                document_id: Uuid::new_v4().to_string(),
1046                upload_path: PATH.into(),
1047                json_body: "".into(),
1048                headers: None,
1049                body_has_info_sections: true,
1050                ping_name: "ping-name".into(),
1051                uploader_capabilities: vec![],
1052            },
1053        );
1054
1055        // Verify that we are indeed told to wait because we are at capacity
1056        match upload_manager.get_upload_task(&glean, false) {
1057            PingUploadTask::Wait { time } => {
1058                // Wait for the uploading window to reset
1059                thread::sleep(Duration::from_millis(time));
1060            }
1061            _ => panic!("Expected upload manager to return a wait task!"),
1062        };
1063
1064        let task = upload_manager.get_upload_task(&glean, false);
1065        assert!(task.is_upload());
1066    }
1067
1068    #[test]
1069    fn clearing_the_queue_works_correctly() {
1070        let (glean, dir) = new_glean(None);
1071
1072        let upload_manager = PingUploadManager::no_policy(dir.path());
1073
1074        // Enqueue a ping multiple times
1075        for _ in 0..10 {
1076            upload_manager.enqueue_ping(
1077                &glean,
1078                PingPayload {
1079                    document_id: Uuid::new_v4().to_string(),
1080                    upload_path: PATH.into(),
1081                    json_body: "".into(),
1082                    headers: None,
1083                    body_has_info_sections: true,
1084                    ping_name: "ping-name".into(),
1085                    uploader_capabilities: vec![],
1086                },
1087            );
1088        }
1089
1090        // Clear the queue
1091        drop(upload_manager.clear_ping_queue());
1092
1093        // Verify there really isn't any ping in the queue
1094        assert_eq!(
1095            upload_manager.get_upload_task(&glean, false),
1096            PingUploadTask::done()
1097        );
1098    }
1099
1100    #[test]
1101    fn clearing_the_queue_doesnt_clear_deletion_request_pings() {
1102        let (mut glean, _t) = new_glean(None);
1103
1104        // Register a ping for testing
1105        let ping_type = PingType::new(
1106            "test",
1107            true,
1108            /* send_if_empty */ true,
1109            true,
1110            true,
1111            true,
1112            vec![],
1113            vec![],
1114            true,
1115            vec![],
1116        );
1117        glean.register_ping_type(&ping_type);
1118
1119        // Submit the ping multiple times
1120        let n = 10;
1121        for _ in 0..n {
1122            ping_type.submit_sync(&glean, None);
1123        }
1124
1125        glean
1126            .internal_pings
1127            .deletion_request
1128            .submit_sync(&glean, None);
1129
1130        // Clear the queue
1131        drop(glean.upload_manager.clear_ping_queue());
1132
1133        let upload_task = glean.get_upload_task();
1134        match upload_task {
1135            PingUploadTask::Upload { request } => assert!(request.is_deletion_request()),
1136            _ => panic!("Expected upload manager to return the next request!"),
1137        }
1138
1139        // Verify there really isn't any other pings in the queue
1140        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1141    }
1142
1143    #[test]
1144    fn fills_up_queue_successfully_from_disk() {
1145        let (mut glean, dir) = new_glean(None);
1146
1147        // Register a ping for testing
1148        let ping_type = PingType::new(
1149            "test",
1150            true,
1151            /* send_if_empty */ true,
1152            true,
1153            true,
1154            true,
1155            vec![],
1156            vec![],
1157            true,
1158            vec![],
1159        );
1160        glean.register_ping_type(&ping_type);
1161
1162        // Submit the ping multiple times
1163        let n = 10;
1164        for _ in 0..n {
1165            ping_type.submit_sync(&glean, None);
1166        }
1167
1168        // Create a new upload manager pointing to the same data_path as the glean instance.
1169        let upload_manager = PingUploadManager::no_policy(dir.path());
1170
1171        // Verify the requests were properly enqueued
1172        for _ in 0..n {
1173            let task = upload_manager.get_upload_task(&glean, false);
1174            assert!(task.is_upload());
1175        }
1176
1177        // Verify that after all requests are returned, none are left
1178        assert_eq!(
1179            upload_manager.get_upload_task(&glean, false),
1180            PingUploadTask::done()
1181        );
1182    }
1183
1184    #[test]
1185    fn processes_correctly_success_upload_response() {
1186        let (mut glean, dir) = new_glean(None);
1187
1188        // Register a ping for testing
1189        let ping_type = PingType::new(
1190            "test",
1191            true,
1192            /* send_if_empty */ true,
1193            true,
1194            true,
1195            true,
1196            vec![],
1197            vec![],
1198            true,
1199            vec![],
1200        );
1201        glean.register_ping_type(&ping_type);
1202
1203        // Submit a ping
1204        ping_type.submit_sync(&glean, None);
1205
1206        // Get the pending ping directory path
1207        let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
1208
1209        // Get the submitted PingRequest
1210        match glean.get_upload_task() {
1211            PingUploadTask::Upload { request } => {
1212                // Simulate the processing of a sucessfull request
1213                let document_id = request.document_id;
1214                glean.process_ping_upload_response(&document_id, UploadResult::http_status(200));
1215                // Verify file was deleted
1216                assert!(!pending_pings_dir.join(document_id).exists());
1217            }
1218            _ => panic!("Expected upload manager to return the next request!"),
1219        }
1220
1221        // Verify that after request is returned, none are left
1222        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1223    }
1224
1225    #[test]
1226    fn processes_correctly_client_error_upload_response() {
1227        let (mut glean, dir) = new_glean(None);
1228
1229        // Register a ping for testing
1230        let ping_type = PingType::new(
1231            "test",
1232            true,
1233            /* send_if_empty */ true,
1234            true,
1235            true,
1236            true,
1237            vec![],
1238            vec![],
1239            true,
1240            vec![],
1241        );
1242        glean.register_ping_type(&ping_type);
1243
1244        // Submit a ping
1245        ping_type.submit_sync(&glean, None);
1246
1247        // Get the pending ping directory path
1248        let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
1249
1250        // Get the submitted PingRequest
1251        match glean.get_upload_task() {
1252            PingUploadTask::Upload { request } => {
1253                // Simulate the processing of a client error
1254                let document_id = request.document_id;
1255                glean.process_ping_upload_response(&document_id, UploadResult::http_status(404));
1256                // Verify file was deleted
1257                assert!(!pending_pings_dir.join(document_id).exists());
1258            }
1259            _ => panic!("Expected upload manager to return the next request!"),
1260        }
1261
1262        // Verify that after request is returned, none are left
1263        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1264    }
1265
1266    #[test]
1267    fn processes_correctly_server_error_upload_response() {
1268        let (mut glean, _t) = new_glean(None);
1269
1270        // Register a ping for testing
1271        let ping_type = PingType::new(
1272            "test",
1273            true,
1274            /* send_if_empty */ true,
1275            true,
1276            true,
1277            true,
1278            vec![],
1279            vec![],
1280            true,
1281            vec![],
1282        );
1283        glean.register_ping_type(&ping_type);
1284
1285        // Submit a ping
1286        ping_type.submit_sync(&glean, None);
1287
1288        // Get the submitted PingRequest
1289        match glean.get_upload_task() {
1290            PingUploadTask::Upload { request } => {
1291                // Simulate the processing of a client error
1292                let document_id = request.document_id;
1293                glean.process_ping_upload_response(&document_id, UploadResult::http_status(500));
1294                // Verify this ping was indeed re-enqueued
1295                match glean.get_upload_task() {
1296                    PingUploadTask::Upload { request } => {
1297                        assert_eq!(document_id, request.document_id);
1298                    }
1299                    _ => panic!("Expected upload manager to return the next request!"),
1300                }
1301            }
1302            _ => panic!("Expected upload manager to return the next request!"),
1303        }
1304
1305        // Verify that after request is returned, none are left
1306        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1307    }
1308
1309    #[test]
1310    fn processes_correctly_unrecoverable_upload_response() {
1311        let (mut glean, dir) = new_glean(None);
1312
1313        // Register a ping for testing
1314        let ping_type = PingType::new(
1315            "test",
1316            true,
1317            /* send_if_empty */ true,
1318            true,
1319            true,
1320            true,
1321            vec![],
1322            vec![],
1323            true,
1324            vec![],
1325        );
1326        glean.register_ping_type(&ping_type);
1327
1328        // Submit a ping
1329        ping_type.submit_sync(&glean, None);
1330
1331        // Get the pending ping directory path
1332        let pending_pings_dir = dir.path().join(PENDING_PINGS_DIRECTORY);
1333
1334        // Get the submitted PingRequest
1335        match glean.get_upload_task() {
1336            PingUploadTask::Upload { request } => {
1337                // Simulate the processing of a client error
1338                let document_id = request.document_id;
1339                glean.process_ping_upload_response(
1340                    &document_id,
1341                    UploadResult::unrecoverable_failure(),
1342                );
1343                // Verify file was deleted
1344                assert!(!pending_pings_dir.join(document_id).exists());
1345            }
1346            _ => panic!("Expected upload manager to return the next request!"),
1347        }
1348
1349        // Verify that after request is returned, none are left
1350        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
1351    }
1352
1353    #[test]
1354    fn new_pings_are_added_while_upload_in_progress() {
1355        let (glean, dir) = new_glean(None);
1356
1357        let upload_manager = PingUploadManager::no_policy(dir.path());
1358
1359        let doc1 = Uuid::new_v4().to_string();
1360        let path1 = format!("/submit/app_id/test-ping/1/{}", doc1);
1361
1362        let doc2 = Uuid::new_v4().to_string();
1363        let path2 = format!("/submit/app_id/test-ping/1/{}", doc2);
1364
1365        // Enqueue a ping
1366        upload_manager.enqueue_ping(
1367            &glean,
1368            PingPayload {
1369                document_id: doc1.clone(),
1370                upload_path: path1,
1371                json_body: "".into(),
1372                headers: None,
1373                body_has_info_sections: true,
1374                ping_name: "test-ping".into(),
1375                uploader_capabilities: vec![],
1376            },
1377        );
1378
1379        // Try and get the first request.
1380        let req = match upload_manager.get_upload_task(&glean, false) {
1381            PingUploadTask::Upload { request } => request,
1382            _ => panic!("Expected upload manager to return the next request!"),
1383        };
1384        assert_eq!(doc1, req.document_id);
1385
1386        // Schedule the next one while the first one is "in progress"
1387        upload_manager.enqueue_ping(
1388            &glean,
1389            PingPayload {
1390                document_id: doc2.clone(),
1391                upload_path: path2,
1392                json_body: "".into(),
1393                headers: None,
1394                body_has_info_sections: true,
1395                ping_name: "test-ping".into(),
1396                uploader_capabilities: vec![],
1397            },
1398        );
1399
1400        // Mark as processed
1401        upload_manager.process_ping_upload_response(
1402            &glean,
1403            &req.document_id,
1404            UploadResult::http_status(200),
1405        );
1406
1407        // Get the second request.
1408        let req = match upload_manager.get_upload_task(&glean, false) {
1409            PingUploadTask::Upload { request } => request,
1410            _ => panic!("Expected upload manager to return the next request!"),
1411        };
1412        assert_eq!(doc2, req.document_id);
1413
1414        // Mark as processed
1415        upload_manager.process_ping_upload_response(
1416            &glean,
1417            &req.document_id,
1418            UploadResult::http_status(200),
1419        );
1420
1421        // ... and then we're done.
1422        assert_eq!(
1423            upload_manager.get_upload_task(&glean, false),
1424            PingUploadTask::done()
1425        );
1426    }
1427
1428    #[test]
1429    fn adds_debug_view_header_to_requests_when_tag_is_set() {
1430        let (mut glean, _t) = new_glean(None);
1431
1432        glean.set_debug_view_tag("valid-tag");
1433
1434        // Register a ping for testing
1435        let ping_type = PingType::new(
1436            "test",
1437            true,
1438            /* send_if_empty */ true,
1439            true,
1440            true,
1441            true,
1442            vec![],
1443            vec![],
1444            true,
1445            vec![],
1446        );
1447        glean.register_ping_type(&ping_type);
1448
1449        // Submit a ping
1450        ping_type.submit_sync(&glean, None);
1451
1452        // Get the submitted PingRequest
1453        match glean.get_upload_task() {
1454            PingUploadTask::Upload { request } => {
1455                assert_eq!(request.headers.get("X-Debug-ID").unwrap(), "valid-tag")
1456            }
1457            _ => panic!("Expected upload manager to return the next request!"),
1458        }
1459    }
1460
1461    #[test]
1462    fn duplicates_are_not_enqueued() {
1463        let (glean, dir) = new_glean(None);
1464
1465        // Create a new upload manager so that we have access to its functions directly,
1466        // make it synchronous so we don't have to manually wait for the scanning to finish.
1467        let upload_manager = PingUploadManager::no_policy(dir.path());
1468
1469        let doc_id = Uuid::new_v4().to_string();
1470        let path = format!("/submit/app_id/test-ping/1/{}", doc_id);
1471
1472        // Try to enqueue a ping with the same doc_id twice
1473        upload_manager.enqueue_ping(
1474            &glean,
1475            PingPayload {
1476                document_id: doc_id.clone(),
1477                upload_path: path.clone(),
1478                json_body: "".into(),
1479                headers: None,
1480                body_has_info_sections: true,
1481                ping_name: "test-ping".into(),
1482                uploader_capabilities: vec![],
1483            },
1484        );
1485        upload_manager.enqueue_ping(
1486            &glean,
1487            PingPayload {
1488                document_id: doc_id,
1489                upload_path: path,
1490                json_body: "".into(),
1491                headers: None,
1492                body_has_info_sections: true,
1493                ping_name: "test-ping".into(),
1494                uploader_capabilities: vec![],
1495            },
1496        );
1497
1498        // Get a task once
1499        let task = upload_manager.get_upload_task(&glean, false);
1500        assert!(task.is_upload());
1501
1502        // There should be no more queued tasks
1503        assert_eq!(
1504            upload_manager.get_upload_task(&glean, false),
1505            PingUploadTask::done()
1506        );
1507    }
1508
1509    #[test]
1510    fn maximum_of_recoverable_errors_is_enforced_for_uploading_window() {
1511        let (mut glean, dir) = new_glean(None);
1512
1513        // Register a ping for testing
1514        let ping_type = PingType::new(
1515            "test",
1516            true,
1517            /* send_if_empty */ true,
1518            true,
1519            true,
1520            true,
1521            vec![],
1522            vec![],
1523            true,
1524            vec![],
1525        );
1526        glean.register_ping_type(&ping_type);
1527
1528        // Submit the ping multiple times
1529        let n = 5;
1530        for _ in 0..n {
1531            ping_type.submit_sync(&glean, None);
1532        }
1533
1534        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1535
1536        // Set a policy for max recoverable failures, this is usually disabled for tests.
1537        let max_recoverable_failures = 3;
1538        upload_manager
1539            .policy
1540            .set_max_recoverable_failures(Some(max_recoverable_failures));
1541
1542        // Return the max recoverable error failures in a row
1543        for _ in 0..max_recoverable_failures {
1544            match upload_manager.get_upload_task(&glean, false) {
1545                PingUploadTask::Upload { request } => {
1546                    upload_manager.process_ping_upload_response(
1547                        &glean,
1548                        &request.document_id,
1549                        UploadResult::recoverable_failure(),
1550                    );
1551                }
1552                _ => panic!("Expected upload manager to return the next request!"),
1553            }
1554        }
1555
1556        // Verify that after returning the max amount of recoverable failures,
1557        // we are done even though we haven't gotten all the enqueued requests.
1558        assert_eq!(
1559            upload_manager.get_upload_task(&glean, false),
1560            PingUploadTask::done()
1561        );
1562
1563        // Verify all requests are returned when we try again.
1564        for _ in 0..n {
1565            let task = upload_manager.get_upload_task(&glean, false);
1566            assert!(task.is_upload());
1567        }
1568    }
1569
1570    #[test]
1571    fn quota_is_enforced_when_enqueueing_cached_pings() {
1572        let (mut glean, dir) = new_glean(None);
1573
1574        // Register a ping for testing
1575        let ping_type = PingType::new(
1576            "test",
1577            true,
1578            /* send_if_empty */ true,
1579            true,
1580            true,
1581            true,
1582            vec![],
1583            vec![],
1584            true,
1585            vec![],
1586        );
1587        glean.register_ping_type(&ping_type);
1588
1589        // Submit the ping multiple times
1590        let n = 10;
1591        for _ in 0..n {
1592            ping_type.submit_sync(&glean, None);
1593        }
1594
1595        let directory_manager = PingDirectoryManager::new(dir.path());
1596        let pending_pings = directory_manager.process_dirs().pending_pings;
1597        // The pending pings array is sorted by date in ascending order,
1598        // the newest element is the last one.
1599        let (_, newest_ping) = &pending_pings.last().unwrap();
1600        let PingPayload {
1601            document_id: newest_ping_id,
1602            ..
1603        } = &newest_ping;
1604
1605        // Create a new upload manager pointing to the same data_path as the glean instance.
1606        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1607
1608        // Set the quota to just a little over the size on an empty ping file.
1609        // This way we can check that one ping is kept and all others are deleted.
1610        //
1611        // From manual testing I figured out an empty ping file is 324bytes,
1612        // I am setting this a little over just so that minor changes to the ping structure
1613        // don't immediatelly break this.
1614        upload_manager
1615            .policy
1616            .set_max_pending_pings_directory_size(Some(500));
1617
1618        // Get a task once
1619        // One ping should have been enqueued.
1620        // Make sure it is the newest ping.
1621        match upload_manager.get_upload_task(&glean, false) {
1622            PingUploadTask::Upload { request } => assert_eq!(&request.document_id, newest_ping_id),
1623            _ => panic!("Expected upload manager to return the next request!"),
1624        }
1625
1626        // Verify that no other requests were returned,
1627        // they should all have been deleted because pending pings quota was hit.
1628        assert_eq!(
1629            upload_manager.get_upload_task(&glean, false),
1630            PingUploadTask::done()
1631        );
1632
1633        // Verify that the correct number of deleted pings was recorded
1634        assert_eq!(
1635            n - 1,
1636            upload_manager
1637                .upload_metrics
1638                .deleted_pings_after_quota_hit
1639                .get_value(&glean, Some("metrics"))
1640                .unwrap()
1641        );
1642        assert_eq!(
1643            n,
1644            upload_manager
1645                .upload_metrics
1646                .pending_pings
1647                .get_value(&glean, Some("metrics"))
1648                .unwrap()
1649        );
1650    }
1651
1652    #[test]
1653    fn number_quota_is_enforced_when_enqueueing_cached_pings() {
1654        let (mut glean, dir) = new_glean(None);
1655
1656        // Register a ping for testing
1657        let ping_type = PingType::new(
1658            "test",
1659            true,
1660            /* send_if_empty */ true,
1661            true,
1662            true,
1663            true,
1664            vec![],
1665            vec![],
1666            true,
1667            vec![],
1668        );
1669        glean.register_ping_type(&ping_type);
1670
1671        // How many pings we allow at maximum
1672        let count_quota = 3;
1673        // The number of pings we fill the pending pings directory with.
1674        let n = 10;
1675
1676        // Submit the ping multiple times
1677        for _ in 0..n {
1678            ping_type.submit_sync(&glean, None);
1679        }
1680
1681        let directory_manager = PingDirectoryManager::new(dir.path());
1682        let pending_pings = directory_manager.process_dirs().pending_pings;
1683        // The pending pings array is sorted by date in ascending order,
1684        // the newest element is the last one.
1685        let expected_pings = pending_pings
1686            .iter()
1687            .rev()
1688            .take(count_quota)
1689            .map(|(_, ping)| ping.document_id.clone())
1690            .collect::<Vec<_>>();
1691
1692        // Create a new upload manager pointing to the same data_path as the glean instance.
1693        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1694
1695        upload_manager
1696            .policy
1697            .set_max_pending_pings_count(Some(count_quota as u64));
1698
1699        // Get a task once
1700        // One ping should have been enqueued.
1701        // Make sure it is the newest ping.
1702        for ping_id in expected_pings.iter().rev() {
1703            match upload_manager.get_upload_task(&glean, false) {
1704                PingUploadTask::Upload { request } => assert_eq!(&request.document_id, ping_id),
1705                _ => panic!("Expected upload manager to return the next request!"),
1706            }
1707        }
1708
1709        // Verify that no other requests were returned,
1710        // they should all have been deleted because pending pings quota was hit.
1711        assert_eq!(
1712            upload_manager.get_upload_task(&glean, false),
1713            PingUploadTask::done()
1714        );
1715
1716        // Verify that the correct number of deleted pings was recorded
1717        assert_eq!(
1718            (n - count_quota) as i32,
1719            upload_manager
1720                .upload_metrics
1721                .deleted_pings_after_quota_hit
1722                .get_value(&glean, Some("metrics"))
1723                .unwrap()
1724        );
1725        assert_eq!(
1726            n as i32,
1727            upload_manager
1728                .upload_metrics
1729                .pending_pings
1730                .get_value(&glean, Some("metrics"))
1731                .unwrap()
1732        );
1733    }
1734
1735    #[test]
1736    fn size_and_count_quota_work_together_size_first() {
1737        let (mut glean, dir) = new_glean(None);
1738
1739        // Register a ping for testing
1740        let ping_type = PingType::new(
1741            "test",
1742            true,
1743            /* send_if_empty */ true,
1744            true,
1745            true,
1746            true,
1747            vec![],
1748            vec![],
1749            true,
1750            vec![],
1751        );
1752        glean.register_ping_type(&ping_type);
1753
1754        let expected_number_of_pings = 3;
1755        // The number of pings we fill the pending pings directory with.
1756        let n = 10;
1757
1758        // Submit the ping multiple times
1759        for _ in 0..n {
1760            ping_type.submit_sync(&glean, None);
1761        }
1762
1763        let directory_manager = PingDirectoryManager::new(dir.path());
1764        let pending_pings = directory_manager.process_dirs().pending_pings;
1765        // The pending pings array is sorted by date in ascending order,
1766        // the newest element is the last one.
1767        let expected_pings = pending_pings
1768            .iter()
1769            .rev()
1770            .take(expected_number_of_pings)
1771            .map(|(_, ping)| ping.document_id.clone())
1772            .collect::<Vec<_>>();
1773
1774        // Create a new upload manager pointing to the same data_path as the glean instance.
1775        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1776
1777        // From manual testing we figured out a basically empty ping file is 399 bytes,
1778        // so this allows 3 pings with some headroom in case of future changes.
1779        upload_manager
1780            .policy
1781            .set_max_pending_pings_directory_size(Some(1300));
1782        upload_manager.policy.set_max_pending_pings_count(Some(5));
1783
1784        // Get a task once
1785        // One ping should have been enqueued.
1786        // Make sure it is the newest ping.
1787        for ping_id in expected_pings.iter().rev() {
1788            match upload_manager.get_upload_task(&glean, false) {
1789                PingUploadTask::Upload { request } => assert_eq!(&request.document_id, ping_id),
1790                _ => panic!("Expected upload manager to return the next request!"),
1791            }
1792        }
1793
1794        // Verify that no other requests were returned,
1795        // they should all have been deleted because pending pings quota was hit.
1796        assert_eq!(
1797            upload_manager.get_upload_task(&glean, false),
1798            PingUploadTask::done()
1799        );
1800
1801        // Verify that the correct number of deleted pings was recorded
1802        assert_eq!(
1803            (n - expected_number_of_pings) as i32,
1804            upload_manager
1805                .upload_metrics
1806                .deleted_pings_after_quota_hit
1807                .get_value(&glean, Some("metrics"))
1808                .unwrap()
1809        );
1810        assert_eq!(
1811            n as i32,
1812            upload_manager
1813                .upload_metrics
1814                .pending_pings
1815                .get_value(&glean, Some("metrics"))
1816                .unwrap()
1817        );
1818        // Verify the labeled deletion counter attributes deletions to size_quota
1819        assert_eq!(
1820            (n - expected_number_of_pings) as i32,
1821            upload_manager
1822                .upload_metrics
1823                .pending_pings_deleted
1824                .get("size_quota")
1825                .get_value(&glean, Some("health"))
1826                .unwrap()
1827        );
1828        assert!(upload_manager
1829            .upload_metrics
1830            .pending_pings_deleted
1831            .get("count_quota")
1832            .get_value(&glean, Some("health"))
1833            .is_none());
1834    }
1835
1836    #[test]
1837    fn size_and_count_quota_work_together_count_first() {
1838        let (mut glean, dir) = new_glean(None);
1839
1840        // Register a ping for testing
1841        let ping_type = PingType::new(
1842            "test",
1843            true,
1844            /* send_if_empty */ true,
1845            true,
1846            true,
1847            true,
1848            vec![],
1849            vec![],
1850            true,
1851            vec![],
1852        );
1853        glean.register_ping_type(&ping_type);
1854
1855        let expected_number_of_pings = 2;
1856        // The number of pings we fill the pending pings directory with.
1857        let n = 10;
1858
1859        // Submit the ping multiple times
1860        for _ in 0..n {
1861            ping_type.submit_sync(&glean, None);
1862        }
1863
1864        let directory_manager = PingDirectoryManager::new(dir.path());
1865        let pending_pings = directory_manager.process_dirs().pending_pings;
1866        // The pending pings array is sorted by date in ascending order,
1867        // the newest element is the last one.
1868        let expected_pings = pending_pings
1869            .iter()
1870            .rev()
1871            .take(expected_number_of_pings)
1872            .map(|(_, ping)| ping.document_id.clone())
1873            .collect::<Vec<_>>();
1874
1875        // Create a new upload manager pointing to the same data_path as the glean instance.
1876        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1877
1878        // Set a large enough size quota so it never triggers before the count quota does.
1879        upload_manager
1880            .policy
1881            .set_max_pending_pings_directory_size(Some(100_000));
1882        upload_manager.policy.set_max_pending_pings_count(Some(2));
1883
1884        // Get a task once
1885        // One ping should have been enqueued.
1886        // Make sure it is the newest ping.
1887        for ping_id in expected_pings.iter().rev() {
1888            match upload_manager.get_upload_task(&glean, false) {
1889                PingUploadTask::Upload { request } => assert_eq!(&request.document_id, ping_id),
1890                _ => panic!("Expected upload manager to return the next request!"),
1891            }
1892        }
1893
1894        // Verify that no other requests were returned,
1895        // they should all have been deleted because pending pings quota was hit.
1896        assert_eq!(
1897            upload_manager.get_upload_task(&glean, false),
1898            PingUploadTask::done()
1899        );
1900
1901        // Verify that the correct number of deleted pings was recorded
1902        assert_eq!(
1903            (n - expected_number_of_pings) as i32,
1904            upload_manager
1905                .upload_metrics
1906                .deleted_pings_after_quota_hit
1907                .get_value(&glean, Some("metrics"))
1908                .unwrap()
1909        );
1910        assert_eq!(
1911            n as i32,
1912            upload_manager
1913                .upload_metrics
1914                .pending_pings
1915                .get_value(&glean, Some("metrics"))
1916                .unwrap()
1917        );
1918        // Verify the labeled deletion counter attributes deletions to count_quota
1919        assert_eq!(
1920            (n - expected_number_of_pings) as i32,
1921            upload_manager
1922                .upload_metrics
1923                .pending_pings_deleted
1924                .get("count_quota")
1925                .get_value(&glean, Some("health"))
1926                .unwrap()
1927        );
1928        assert!(upload_manager
1929            .upload_metrics
1930            .pending_pings_deleted
1931            .get("size_quota")
1932            .get_value(&glean, Some("health"))
1933            .is_none());
1934    }
1935
1936    #[test]
1937    fn pending_pings_deleted_is_not_recorded_when_quota_not_hit() {
1938        let (mut glean, dir) = new_glean(None);
1939
1940        let ping_type = PingType::new(
1941            "test",
1942            true,
1943            /* send_if_empty */ true,
1944            true,
1945            true,
1946            true,
1947            vec![],
1948            vec![],
1949            true,
1950            vec![],
1951        );
1952        glean.register_ping_type(&ping_type);
1953
1954        // Submit fewer pings than any quota.
1955        for _ in 0..3 {
1956            ping_type.submit_sync(&glean, None);
1957        }
1958
1959        let mut upload_manager = PingUploadManager::no_policy(dir.path());
1960        upload_manager.policy.set_max_pending_pings_count(Some(10));
1961        upload_manager
1962            .policy
1963            .set_max_pending_pings_directory_size(Some(1024 * 1024));
1964
1965        upload_manager.get_upload_task(&glean, false);
1966
1967        assert!(upload_manager
1968            .upload_metrics
1969            .pending_pings_deleted
1970            .get("count_quota")
1971            .get_value(&glean, Some("health"))
1972            .is_none());
1973        assert!(upload_manager
1974            .upload_metrics
1975            .pending_pings_deleted
1976            .get("size_quota")
1977            .get_value(&glean, Some("health"))
1978            .is_none());
1979    }
1980
1981    #[test]
1982    fn pending_pings_config_overrides_are_applied() {
1983        let (_, dir) = new_glean(None);
1984
1985        let mut upload_manager = PingUploadManager::new(dir.path(), "test");
1986
1987        let custom_count: u64 = 42;
1988        let custom_size: u64 = 999_999;
1989        upload_manager.set_max_pending_pings_count(custom_count);
1990        upload_manager.set_max_pending_pings_directory_size(custom_size);
1991
1992        assert_eq!(
1993            custom_count,
1994            upload_manager.policy.max_pending_pings_count()
1995        );
1996        assert_eq!(
1997            custom_size,
1998            upload_manager.policy.max_pending_pings_directory_size()
1999        );
2000    }
2001
2002    #[test]
2003    fn maximum_wait_attemps_is_enforced() {
2004        let (glean, dir) = new_glean(None);
2005
2006        let mut upload_manager = PingUploadManager::no_policy(dir.path());
2007
2008        // Define a max_wait_attemps policy, this is disabled for tests by default.
2009        let max_wait_attempts = 3;
2010        upload_manager
2011            .policy
2012            .set_max_wait_attempts(Some(max_wait_attempts));
2013
2014        // Add a rate limiter to the upload mangager with max of 1 ping 5secs.
2015        //
2016        // We arbitrarily set the maximum pings per interval to a very low number,
2017        // when the rate limiter reaches it's limit get_upload_task returns a PingUploadTask::Wait,
2018        // which will allow us to test the limitations around returning too many of those in a row.
2019        let secs_per_interval = 5;
2020        let max_pings_per_interval = 1;
2021        upload_manager.set_rate_limiter(secs_per_interval, max_pings_per_interval);
2022
2023        // Enqueue two pings
2024        upload_manager.enqueue_ping(
2025            &glean,
2026            PingPayload {
2027                document_id: Uuid::new_v4().to_string(),
2028                upload_path: PATH.into(),
2029                json_body: "".into(),
2030                headers: None,
2031                body_has_info_sections: true,
2032                ping_name: "ping-name".into(),
2033                uploader_capabilities: vec![],
2034            },
2035        );
2036        upload_manager.enqueue_ping(
2037            &glean,
2038            PingPayload {
2039                document_id: Uuid::new_v4().to_string(),
2040                upload_path: PATH.into(),
2041                json_body: "".into(),
2042                headers: None,
2043                body_has_info_sections: true,
2044                ping_name: "ping-name".into(),
2045                uploader_capabilities: vec![],
2046            },
2047        );
2048
2049        // Get the first ping, it should be returned normally.
2050        match upload_manager.get_upload_task(&glean, false) {
2051            PingUploadTask::Upload { .. } => {}
2052            _ => panic!("Expected upload manager to return the next request!"),
2053        }
2054
2055        // Try to get the next ping,
2056        // we should be throttled and thus get a PingUploadTask::Wait.
2057        // Check that we are indeed allowed to get this response as many times as expected.
2058        for _ in 0..max_wait_attempts {
2059            let task = upload_manager.get_upload_task(&glean, false);
2060            assert!(task.is_wait());
2061        }
2062
2063        // Check that after we get PingUploadTask::Wait the allowed number of times,
2064        // we then get PingUploadTask::Done.
2065        assert_eq!(
2066            upload_manager.get_upload_task(&glean, false),
2067            PingUploadTask::done()
2068        );
2069
2070        // Wait for the rate limiter to allow upload tasks again.
2071        thread::sleep(Duration::from_secs(secs_per_interval));
2072
2073        // Check that we are allowed again to get pings.
2074        let task = upload_manager.get_upload_task(&glean, false);
2075        assert!(task.is_upload());
2076
2077        // And once we are done we don't need to wait anymore.
2078        assert_eq!(
2079            upload_manager.get_upload_task(&glean, false),
2080            PingUploadTask::done()
2081        );
2082    }
2083
2084    #[test]
2085    fn wait_task_contains_expected_wait_time_when_pending_pings_dir_not_processed_yet() {
2086        let (glean, dir) = new_glean(None);
2087        let upload_manager = PingUploadManager::new(dir.path(), "test");
2088        match upload_manager.get_upload_task(&glean, false) {
2089            PingUploadTask::Wait { time } => {
2090                assert_eq!(time, WAIT_TIME_FOR_PING_PROCESSING);
2091            }
2092            _ => panic!("Expected upload manager to return a wait task!"),
2093        };
2094    }
2095
2096    #[test]
2097    fn cannot_enqueue_ping_while_its_being_processed() {
2098        let (glean, dir) = new_glean(None);
2099
2100        let upload_manager = PingUploadManager::no_policy(dir.path());
2101
2102        // Enqueue a ping and start processing it
2103        let identifier = &Uuid::new_v4();
2104        let ping = PingPayload {
2105            document_id: identifier.to_string(),
2106            upload_path: PATH.into(),
2107            json_body: "".into(),
2108            headers: None,
2109            body_has_info_sections: true,
2110            ping_name: "ping-name".into(),
2111            uploader_capabilities: vec![],
2112        };
2113        upload_manager.enqueue_ping(&glean, ping);
2114        assert!(upload_manager.get_upload_task(&glean, false).is_upload());
2115
2116        // Attempt to re-enqueue the same ping
2117        let ping = PingPayload {
2118            document_id: identifier.to_string(),
2119            upload_path: PATH.into(),
2120            json_body: "".into(),
2121            headers: None,
2122            body_has_info_sections: true,
2123            ping_name: "ping-name".into(),
2124            uploader_capabilities: vec![],
2125        };
2126        upload_manager.enqueue_ping(&glean, ping);
2127
2128        // No new pings should have been enqueued so the upload task is Done.
2129        assert_eq!(
2130            upload_manager.get_upload_task(&glean, false),
2131            PingUploadTask::done()
2132        );
2133
2134        // Process the upload response
2135        upload_manager.process_ping_upload_response(
2136            &glean,
2137            &identifier.to_string(),
2138            UploadResult::http_status(200),
2139        );
2140    }
2141
2142    #[cfg(feature = "sqlite")]
2143    #[test]
2144    fn stores_pings_during_submission_and_upload_if_enabled() {
2145        let (mut glean, _t) = new_glean(None);
2146        glean.set_store_submitted_pings_enabled(true);
2147
2148        // Register a ping for testing
2149        let ping_type = PingType::new(
2150            "test",
2151            true,
2152            /* send_if_empty */ true,
2153            true,
2154            true,
2155            true,
2156            vec![],
2157            vec![],
2158            true,
2159            vec![],
2160        );
2161        glean.register_ping_type(&ping_type);
2162
2163        // Submit a ping
2164        ping_type.submit_sync(&glean, None);
2165
2166        let pings = glean.storage().get_all_submitted_pings();
2167        assert_eq!(pings.len(), 1);
2168        let ping = pings.first().unwrap();
2169        assert!(ping.submitted_date.0 <= Utc::now());
2170        assert!(ping.uploaded_date.is_none());
2171
2172        // Get the submitted PingRequest
2173        match glean.get_upload_task() {
2174            PingUploadTask::Upload { request } => {
2175                // Simulate the processing of a sucessful request
2176                let document_id = request.document_id;
2177                glean.process_ping_upload_response(&document_id, UploadResult::http_status(200));
2178            }
2179            _ => panic!("Expected upload manager to return the next request!"),
2180        }
2181
2182        let pings = glean.storage().get_all_submitted_pings();
2183        assert_eq!(pings.len(), 1);
2184        let ping = pings.first().unwrap();
2185        assert!(ping.submitted_date.0 <= Utc::now());
2186        assert!(ping.uploaded_date.is_some());
2187
2188        // Verify that after request is returned, none are left
2189        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
2190    }
2191
2192    #[cfg(feature = "sqlite")]
2193    #[test]
2194    fn stores_pings_during_submission_and_marks_as_upload_failed_when_appropriate() {
2195        let (mut glean, _t) = new_glean(None);
2196        glean.set_store_submitted_pings_enabled(true);
2197
2198        // Register a ping for testing
2199        let ping_type = PingType::new(
2200            "test",
2201            true,
2202            /* send_if_empty */ true,
2203            true,
2204            true,
2205            true,
2206            vec![],
2207            vec![],
2208            true,
2209            vec![],
2210        );
2211        glean.register_ping_type(&ping_type);
2212
2213        // Submit a ping
2214        ping_type.submit_sync(&glean, None);
2215
2216        let pings = glean.storage().get_all_submitted_pings();
2217        assert_eq!(pings.len(), 1);
2218        let ping = pings.first().unwrap();
2219        assert!(ping.submitted_date.0 <= Utc::now());
2220        assert!(ping.uploaded_date.is_none());
2221
2222        // Get the submitted PingRequest
2223        match glean.get_upload_task() {
2224            PingUploadTask::Upload { request } => {
2225                // Simulate the processing of a sucessful request
2226                let document_id = request.document_id;
2227                glean.process_ping_upload_response(&document_id, UploadResult::http_status(400));
2228            }
2229            _ => panic!("Expected upload manager to return the next request!"),
2230        }
2231
2232        let pings = glean.storage().get_all_submitted_pings();
2233        assert_eq!(pings.len(), 1);
2234        let ping = pings.first().unwrap();
2235        assert!(ping.submitted_date.0 <= Utc::now());
2236        assert!(ping.upload_failed.is_some());
2237        assert!(ping.uploaded_date.is_none());
2238
2239        // Verify that after request is returned, none are left
2240        assert_eq!(glean.get_upload_task(), PingUploadTask::done());
2241    }
2242}