1use std::borrow::Cow;
2use std::collections::{BTreeMap, HashMap, HashSet};
3use std::error::Error as StdError;
4use std::fmt;
5use std::fmt::Display;
6use std::future::Future;
7use std::str::FromStr;
8use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
9use std::sync::{Arc, Mutex};
10use std::time::{Duration, SystemTime};
11
12use again::RetryPolicy;
13use async_trait::async_trait;
14use cadence::StatsdClient;
15#[cfg(feature = "reliable_report")]
16use chrono::TimeDelta;
17use gcp_auth::TokenProvider;
18use googleapis_tonic_google_bigtable_v2::google::bigtable::v2 as bigtable;
19use googleapis_tonic_google_bigtable_v2::google::bigtable::v2::bigtable_client::BigtableClient;
20use serde_json::{from_str, json};
21use tonic::metadata::{AsciiMetadataValue, MetadataMap};
22use tonic::transport::Channel;
23use tonic::{Code, Request, Status};
24use uuid::Uuid;
25
26use crate::MAX_ROUTER_TTL_SECS;
27use crate::db::{
28 DbSettings, Notification, USER_RECORD_VERSION, User,
29 client::{DbClient, FetchMessageResponse},
30 error::{DbError, DbResult},
31 models::RangeKey,
32};
33use crate::metric_name::MetricName;
34use crate::metrics::StatsdClientExt;
35
36pub use self::metadata::MetadataBuilder;
37use self::row::{Row, RowCells};
38use super::BigTableDbSettings;
39use super::pool::BigTablePool;
40
41pub mod cell;
42pub mod error;
43pub(crate) mod merge;
44pub mod metadata;
45pub mod row;
46
47pub type RowKey = String;
49
50pub type Qualifier = String;
55pub type FamilyId = String;
56
57const ROUTER_FAMILY: &str = "router";
58const MESSAGE_FAMILY: &str = "message"; const MESSAGE_TOPIC_FAMILY: &str = "message_topic";
60#[cfg(feature = "reliable_report")]
61const RELIABLE_LOG_FAMILY: &str = "reliability";
62#[cfg(feature = "reliable_report")]
63pub const RELIABLE_LOG_TTL: TimeDelta = TimeDelta::days(60);
66
67pub(crate) const RETRY_COUNT: usize = 2;
74
75const MAX_MESSAGE_LEN: usize = 1 << 28;
77
78const BIGTABLE_DATA_SCOPES: &[&str] = &["https://www.googleapis.com/auth/bigtable.data"];
80
81#[derive(Debug)]
86pub struct CircuitBreaker {
87 consecutive_failures: AtomicU32,
88 opened_at_epoch_secs: AtomicU64,
89 failure_threshold: u32,
90 cooldown_secs: u64,
91}
92
93impl CircuitBreaker {
94 pub fn new(failure_threshold: u32, cooldown_secs: u64) -> Self {
95 Self {
96 consecutive_failures: AtomicU32::new(0),
97 opened_at_epoch_secs: AtomicU64::new(0),
98 failure_threshold,
99 cooldown_secs,
100 }
101 }
102
103 pub fn allow_request(&self) -> bool {
106 let failures = self.consecutive_failures.load(Ordering::Relaxed);
107 if failures < self.failure_threshold {
108 return true;
109 }
110 let opened_at = self.opened_at_epoch_secs.load(Ordering::Relaxed);
112 let now = SystemTime::now()
113 .duration_since(SystemTime::UNIX_EPOCH)
114 .unwrap_or_default()
115 .as_secs();
116 if now.saturating_sub(opened_at) >= self.cooldown_secs {
117 true
119 } else {
120 false
121 }
122 }
123
124 pub fn record_success(&self) {
126 self.consecutive_failures.store(0, Ordering::Relaxed);
127 }
128
129 pub fn record_failure(&self) {
131 let prev = self.consecutive_failures.fetch_add(1, Ordering::Relaxed);
132 if prev + 1 >= self.failure_threshold {
133 let now = SystemTime::now()
134 .duration_since(SystemTime::UNIX_EPOCH)
135 .unwrap_or_default()
136 .as_secs();
137 self.opened_at_epoch_secs.store(now, Ordering::Relaxed);
138 }
139 }
140}
141
142impl Default for CircuitBreaker {
143 fn default() -> Self {
144 Self::new(5, 30)
146 }
147}
148
149struct Uaid(Uuid);
152
153impl Display for Uaid {
154 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155 write!(f, "{}", self.0.as_simple())
156 }
157}
158
159impl From<Uaid> for String {
160 fn from(uaid: Uaid) -> String {
161 uaid.0.as_simple().to_string()
162 }
163}
164
165#[derive(Clone)]
166pub struct BigTableClientImpl {
168 pub(crate) settings: BigTableDbSettings,
169 metrics: Arc<StatsdClient>,
171 pool: BigTablePool,
173 metadata: MetadataMap,
174 circuit_breaker: Arc<CircuitBreaker>,
176}
177
178fn max_versions_filter() -> bigtable::RowFilter {
182 bigtable::RowFilter {
183 filter: Some(bigtable::row_filter::Filter::CellsPerColumnLimitFilter(1)),
184 }
185}
186
187fn expiry_filter() -> Result<bigtable::RowFilter, error::BigTableError> {
193 let bt_now: i64 = SystemTime::now()
194 .duration_since(SystemTime::UNIX_EPOCH)
195 .map_err(error::BigTableError::WriteTime)?
196 .as_millis() as i64;
197 Ok(bigtable::RowFilter {
198 filter: Some(bigtable::row_filter::Filter::TimestampRangeFilter(
199 bigtable::TimestampRange {
200 start_timestamp_micros: bt_now * 1000,
201 end_timestamp_micros: 0,
202 },
203 )),
204 })
205}
206
207fn router_gc_policy_filter() -> Result<Vec<bigtable::RowFilter>, error::BigTableError> {
211 Ok(vec![max_versions_filter(), expiry_filter()?])
212}
213
214fn message_gc_policy_filter() -> Result<Vec<bigtable::RowFilter>, error::BigTableError> {
217 Ok(vec![max_versions_filter(), expiry_filter()?])
218}
219
220fn family_filter(regex: String) -> bigtable::RowFilter {
222 bigtable::RowFilter {
223 filter: Some(bigtable::row_filter::Filter::FamilyNameRegexFilter(regex)),
224 }
225}
226
227fn escape_bytes(bytes: &[u8]) -> Vec<u8> {
231 let mut vec = Vec::with_capacity(bytes.len() * 2);
232 for &b in bytes {
233 if !b.is_ascii_alphanumeric() && b != b'_' && (b & 128) == 0 {
234 if b == b'\0' {
235 vec.extend("\\x00".as_bytes());
240 continue;
241 }
242 vec.push(b'\\');
243 }
244 vec.push(b);
245 }
246 vec
247}
248
249fn version_filter(version: &Uuid) -> Vec<bigtable::RowFilter> {
252 let cq_filter = bigtable::RowFilter {
253 filter: Some(bigtable::row_filter::Filter::ColumnQualifierRegexFilter(
254 "^version$".as_bytes().to_vec(),
255 )),
256 };
257 let value_filter = bigtable::RowFilter {
258 filter: Some(bigtable::row_filter::Filter::ValueRegexFilter(
259 escape_bytes(version.as_bytes()),
260 )),
261 };
262
263 vec![
264 family_filter(format!("^{ROUTER_FAMILY}$")),
265 cq_filter,
266 value_filter,
267 ]
268}
269
270fn new_version_cell(timestamp: SystemTime) -> cell::Cell {
272 cell::Cell {
273 qualifier: "version".to_owned(),
274 value: Uuid::new_v4().into(),
275 timestamp,
276 ..Default::default()
277 }
278}
279
280fn filter_chain(filters: Vec<bigtable::RowFilter>) -> bigtable::RowFilter {
282 bigtable::RowFilter {
283 filter: Some(bigtable::row_filter::Filter::Chain(
284 bigtable::row_filter::Chain { filters },
285 )),
286 }
287}
288
289fn read_row_request(
291 table_name: &str,
292 app_profile_id: &str,
293 row_key: &str,
294) -> bigtable::ReadRowsRequest {
295 bigtable::ReadRowsRequest {
296 table_name: table_name.to_owned(),
297 app_profile_id: app_profile_id.to_owned(),
298 rows: Some(bigtable::RowSet {
299 row_keys: vec![row_key.as_bytes().to_vec()],
300 row_ranges: Vec::new(),
301 }),
302 ..Default::default()
303 }
304}
305
306fn to_u64(value: Vec<u8>, name: &str) -> Result<u64, DbError> {
307 let v: [u8; 8] = value
308 .try_into()
309 .map_err(|_| DbError::DeserializeU64(name.to_owned()))?;
310 Ok(u64::from_be_bytes(v))
311}
312
313fn to_string(value: Vec<u8>, name: &str) -> Result<String, DbError> {
314 String::from_utf8(value).map_err(|e| {
315 debug!("🉑 cannot read string {}: {:?}", name, e);
316 DbError::DeserializeString(name.to_owned())
317 })
318}
319
320fn channels_from_cells(cells: &RowCells) -> DbResult<HashSet<Uuid>> {
324 let mut result = HashSet::new();
325 for cells in cells.values() {
326 let Some(cell) = cells.last() else {
327 continue;
328 };
329 let Some((_, chid)) = cell.qualifier.split_once("chid:") else {
330 return Err(DbError::Integrity(
331 "get_channels expected: chid:<chid>".to_owned(),
332 None,
333 ));
334 };
335 result.insert(Uuid::from_str(chid).map_err(|e| DbError::General(e.to_string()))?);
336 }
337 Ok(result)
338}
339
340fn channels_to_cells(channels: Cow<HashSet<Uuid>>, expiry: SystemTime) -> Vec<cell::Cell> {
342 let channels = channels.into_owned();
343 let mut cells = Vec::with_capacity(channels.len().min(100_000));
344 for (i, channel_id) in channels.into_iter().enumerate() {
345 if i >= 100_000 {
349 break;
350 }
351 cells.push(cell::Cell {
352 qualifier: format!("chid:{}", channel_id.as_hyphenated()),
353 timestamp: expiry,
354 ..Default::default()
355 });
356 }
357 cells
358}
359
360pub fn retry_policy(max: usize) -> RetryPolicy {
361 RetryPolicy::default()
362 .with_max_retries(max)
363 .with_jitter(true)
364}
365
366#[derive(Clone, Copy, Debug, Eq, PartialEq)]
367enum RetryKind {
368 Read,
369 IdempotentWrite,
370 ConditionalWrite,
371}
372
373#[derive(Clone, Copy, Debug)]
374enum RpcClass {
375 Point,
376 Scan,
377}
378
379#[derive(Clone, Copy, Debug, Eq, PartialEq)]
380enum BreakerPolicy {
381 Track,
382 Ignore,
383}
384
385#[derive(Clone, Copy, Debug)]
386struct RpcPolicy {
387 attempt_timeout: Duration,
388 total_timeout: Duration,
389 retry_kind: RetryKind,
390 breaker: BreakerPolicy,
391}
392
393async fn rpc_attempt_until<T>(
394 deadline: tokio::time::Instant,
395 future: impl Future<Output = Result<T, error::BigTableError>>,
396) -> Result<T, error::BigTableError> {
397 tokio::time::timeout_at(deadline, future)
398 .await
399 .map_err(|_| error::BigTableError::AttemptTimeout)?
400}
401
402async fn operation_budget_until<T>(
403 deadline: tokio::time::Instant,
404 future: impl Future<Output = Result<T, error::BigTableError>>,
405) -> Result<T, error::BigTableError> {
406 tokio::time::timeout_at(deadline, future)
407 .await
408 .map_err(|_| error::BigTableError::OperationTimeout)?
409}
410
411fn retryable_pre_send_err(status: &Status) -> bool {
412 (status.code() == Code::Unknown
413 && status
414 .message()
415 .to_ascii_lowercase()
416 .starts_with("service was not ready:"))
417 || has_connect_source(status)
418}
419
420fn retryable_transient_err(status: &Status) -> bool {
421 if retryable_pre_send_err(status) {
422 return true;
423 }
424 match status.code() {
425 Code::Internal => {
426 let message = status.message().to_ascii_lowercase();
427 [
428 "rst_stream",
429 "rst stream",
430 "received unexpected eos on data frame from server",
431 ]
432 .iter()
433 .any(|fragment| message.contains(fragment))
434 }
435 Code::Unavailable | Code::DeadlineExceeded => true,
436 _ => false,
437 }
438}
439
440fn retryable_read_err(status: &Status) -> bool {
457 if retryable_transient_err(status) {
458 return true;
459 }
460 if matches!(status.code(), Code::Internal | Code::Unknown)
464 && status
465 .message()
466 .to_ascii_lowercase()
467 .starts_with("h2 protocol error:")
468 {
469 return true;
470 }
471 match status.code() {
472 Code::Cancelled => status
473 .message()
474 .eq_ignore_ascii_case("operation was canceled"),
475 Code::Unknown => has_transport_source(status),
478 _ => false,
479 }
480}
481
482fn has_transport_source(status: &Status) -> bool {
483 let mut source = StdError::source(status);
484 while let Some(error) = source {
485 if error.downcast_ref::<tonic::transport::Error>().is_some()
486 || error.downcast_ref::<std::io::Error>().is_some()
487 {
488 return true;
489 }
490 source = error.source();
491 }
492 false
493}
494
495fn has_connect_source(status: &Status) -> bool {
496 let mut source = StdError::source(status);
497 while let Some(error) = source {
498 if error.downcast_ref::<tonic::ConnectError>().is_some() {
499 return true;
500 }
501 source = error.source();
502 }
503 false
504}
505
506fn counts_toward_breaker(error: &error::BigTableError, operation_timed_out_in_rpc: bool) -> bool {
507 matches!(
508 error,
509 error::BigTableError::InvalidRowResponse(_)
510 | error::BigTableError::InvalidChunk(_)
511 | error::BigTableError::Read(_)
512 | error::BigTableError::Write(_)
513 | error::BigTableError::AttemptTimeout
514 | error::BigTableError::Status(_, _)
515 ) || matches!(error, error::BigTableError::OperationTimeout) && operation_timed_out_in_rpc
516}
517
518#[cfg(test)]
519mod retry_tests {
520 use std::convert::Infallible;
521 use std::sync::Mutex;
522 use std::sync::atomic::AtomicUsize;
523
524 use futures::StreamExt;
525 use http_body_util::StreamBody;
526 use hyper::body::{Bytes, Frame, Incoming};
527 use hyper::server::conn::http2;
528 use hyper::service::service_fn;
529 use hyper::{Request as HyperRequest, Response as HyperResponse};
530 use hyper_util::rt::{TokioExecutor, TokioIo};
531 use tokio::net::TcpListener;
532 use tokio::sync::oneshot;
533
534 use super::*;
535 #[derive(Debug, thiserror::Error)]
536 #[error("transport error")]
537 struct TestTransportError {
538 #[source]
539 source: std::io::Error,
540 }
541
542 fn broken_pipe_status() -> Status {
543 Status::from_error(Box::new(TestTransportError {
544 source: std::io::Error::from(std::io::ErrorKind::BrokenPipe),
545 }))
546 }
547
548 #[test]
549 fn retries_tonic_pre_send_readiness_errors() {
550 let status = Status::unknown("Service was not ready: transport error");
551 assert!(retryable_pre_send_err(&status));
552 }
553
554 #[test]
555 fn retries_source_backed_connect_errors_before_conditional_writes() {
556 let connect = tonic::ConnectError(Box::new(std::io::Error::from(
557 std::io::ErrorKind::ConnectionRefused,
558 )));
559 let status = Status::from_error(Box::new(connect));
560
561 assert_eq!(status.code(), Code::Unavailable);
562 assert!(retryable_pre_send_err(&status));
563 }
564
565 #[test]
566 fn does_not_retry_arbitrary_unknown_errors() {
567 let status = Status::unknown("operation outcome is unknown");
568 assert!(!retryable_pre_send_err(&status));
569 }
570
571 #[test]
572 fn retries_transient_bigtable_statuses() {
573 assert!(retryable_transient_err(&Status::unavailable(
574 "No zones were available"
575 )));
576 assert!(retryable_transient_err(&Status::deadline_exceeded(
577 "deadline exceeded"
578 )));
579 assert!(retryable_transient_err(&Status::internal(
580 "stream terminated by RST_STREAM before headers"
581 )));
582 }
583
584 #[test]
585 fn retries_observed_tonic_transport_failures_for_reads() {
586 let goaway = Status::internal("h2 protocol error: http2 error");
587 assert!(retryable_read_err(&goaway));
588 assert!(classify_retry(RetryKind::Read, &error::BigTableError::Read(goaway)).is_some());
589
590 let connection_closed = Status::cancelled("operation was canceled");
591 assert!(retryable_read_err(&connection_closed));
592 assert!(
593 classify_retry(
594 RetryKind::Read,
595 &error::BigTableError::Read(connection_closed)
596 )
597 .is_some()
598 );
599
600 let transport_error = broken_pipe_status();
604 assert_eq!(transport_error.code(), Code::Unknown);
605 assert_eq!(transport_error.message(), "transport error");
606 assert!(retryable_read_err(&transport_error));
607 assert!(
608 classify_retry(
609 RetryKind::Read,
610 &error::BigTableError::Read(transport_error)
611 )
612 .is_some()
613 );
614 }
615
616 #[test]
617 fn does_not_retry_non_transport_unknown_reads() {
618 assert!(!retryable_read_err(&Status::unknown(
621 "some application error"
622 )));
623 }
624
625 #[test]
626 fn retries_idempotent_but_not_conditional_writes() {
627 let goaway =
628 error::BigTableError::Write(Status::internal("h2 protocol error: http2 error"));
629 assert!(classify_retry(RetryKind::IdempotentWrite, &goaway).is_some());
630 let conditional_goaway =
631 error::BigTableError::Write(Status::internal("h2 protocol error: http2 error"));
632 assert!(classify_retry(RetryKind::ConditionalWrite, &conditional_goaway).is_none());
633
634 let connection_closed =
635 error::BigTableError::Write(Status::cancelled("operation was canceled"));
636 assert!(classify_retry(RetryKind::IdempotentWrite, &connection_closed).is_some());
637
638 let transport_error = error::BigTableError::Write(broken_pipe_status());
639 assert!(classify_retry(RetryKind::IdempotentWrite, &transport_error).is_some());
640
641 let unavailable = error::BigTableError::Write(Status::unavailable("try again"));
642 assert!(classify_retry(RetryKind::IdempotentWrite, &unavailable).is_some());
643 assert!(classify_retry(RetryKind::ConditionalWrite, &unavailable).is_none());
644
645 let attempt_timeout = error::BigTableError::AttemptTimeout;
646 assert!(classify_retry(RetryKind::Read, &attempt_timeout).is_some());
647 assert!(classify_retry(RetryKind::ConditionalWrite, &attempt_timeout).is_none());
648 }
649
650 #[actix_rt::test]
651 async fn attempt_and_operation_timeouts_are_enforced() {
652 let attempt = rpc_attempt_until::<()>(
653 tokio::time::Instant::now() + Duration::from_millis(1),
654 async { futures::future::pending().await },
655 )
656 .await;
657 assert!(matches!(attempt, Err(error::BigTableError::AttemptTimeout)));
658
659 let operation = operation_budget_until::<()>(
660 tokio::time::Instant::now() + Duration::from_millis(1),
661 async { futures::future::pending().await },
662 )
663 .await;
664 assert!(matches!(
665 operation,
666 Err(error::BigTableError::OperationTimeout)
667 ));
668 }
669
670 #[actix_rt::test]
671 async fn retries_a_midstream_drop_on_the_next_channel() {
672 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
673 let address = listener.local_addr().unwrap();
674 let (drop_connection, wait_for_drop) = oneshot::channel::<()>();
675 let (finish_server, wait_for_finish) = oneshot::channel::<()>();
676
677 let server = tokio::spawn(async move {
678 let (socket, _) = listener.accept().await.unwrap();
682 let service = service_fn(|_request: HyperRequest<Incoming>| async move {
683 let first_message = Frame::data(Bytes::from_static(&[0, 0, 0, 0, 0]));
687 let body = StreamBody::new(
688 futures::stream::once(
689 async move { Ok::<Frame<Bytes>, Infallible>(first_message) },
690 )
691 .chain(futures::stream::pending::<Result<Frame<Bytes>, Infallible>>()),
692 );
693 Ok::<_, Infallible>(
694 HyperResponse::builder()
695 .status(200)
696 .header("content-type", "application/grpc")
697 .body(body)
698 .unwrap(),
699 )
700 });
701 {
702 let connection = http2::Builder::new(TokioExecutor::new())
703 .serve_connection(TokioIo::new(socket), service);
704 tokio::pin!(connection);
705 tokio::select! {
706 result = &mut connection => {
707 panic!("test HTTP/2 connection ended before it was dropped: {result:?}");
708 }
709 _ = wait_for_drop => {}
710 }
711 }
712
713 let (socket, _) = listener.accept().await.unwrap();
717 let service = service_fn(|_request: HyperRequest<Incoming>| async move {
718 let body =
721 StreamBody::new(futures::stream::empty::<Result<Frame<Bytes>, Infallible>>());
722 Ok::<_, Infallible>(
723 HyperResponse::builder()
724 .status(200)
725 .header("content-type", "application/grpc")
726 .header("grpc-status", "0")
727 .body(body)
728 .unwrap(),
729 )
730 });
731 let connection = http2::Builder::new(TokioExecutor::new())
732 .serve_connection(TokioIo::new(socket), service);
733 tokio::pin!(connection);
734 tokio::select! {
735 result = &mut connection => {
736 panic!("successful HTTP/2 connection ended unexpectedly: {result:?}");
737 }
738 _ = wait_for_finish => {}
739 }
740 });
741
742 let metrics = Arc::new(StatsdClient::builder("", cadence::NopMetricSink).build());
743 let settings = DbSettings {
744 dsn: Some(format!("grpc://localhost:{}", address.port())),
745 db_settings: json!({
746 "table_name": "projects/test/instances/test/tables/test",
747 "grpc_channel_count": 2,
748 "retry_count": 1,
749 "grpc_connect_timeout": 2,
750 "grpc_point_attempt_timeout": 5,
751 "grpc_point_total_timeout": 10
752 })
753 .to_string(),
754 };
755 let client = BigTableClientImpl::new(metrics, &settings).unwrap();
756 let drop_connection = Arc::new(Mutex::new(Some(drop_connection)));
757 let policy = client.rpc_policy(RpcClass::Point, RetryKind::Read, BreakerPolicy::Track);
758 let started = tokio::time::Instant::now();
759 tokio::time::timeout(
760 Duration::from_secs(8),
761 client.execute_rpc(
762 bigtable::ReadRowsRequest::default(),
763 policy,
764 move |channel, request| {
765 let drop_connection = drop_connection.clone();
766 async move {
767 let mut bigtable = BigtableDb::client(channel);
768 let mut stream = bigtable
769 .read_rows(request)
770 .await
771 .map_err(error::BigTableError::Read)?
772 .into_inner();
773
774 let signal = drop_connection
775 .lock()
776 .unwrap_or_else(|poisoned| poisoned.into_inner())
777 .take();
778 if let Some(signal) = signal {
779 assert!(stream.message().await.unwrap().is_some());
780 signal.send(()).unwrap();
781 }
782 while stream
783 .message()
784 .await
785 .map_err(error::BigTableError::Read)?
786 .is_some()
787 {}
788 Ok(())
789 }
790 },
791 ),
792 )
793 .await
794 .expect("Bigtable retry exceeded the test budget")
795 .expect("the second channel should complete the read");
796 assert!(
797 started.elapsed() < Duration::from_secs(5),
798 "the retry waited for the attempt deadline instead of classifying the transport drop"
799 );
800
801 finish_server.send(()).unwrap();
802 server.await.unwrap();
803 }
804
805 #[test]
806 fn default_retry_budget_is_bounded() {
807 assert_eq!(RETRY_COUNT, 2);
808 }
809
810 #[actix_rt::test]
811 async fn request_preserves_metadata_and_grpc_timeout() {
812 let db = BigtableDb::new(None);
813 let mut metadata = MetadataMap::new();
814 metadata.insert("x-goog-request-params", "table_name=test".parse().unwrap());
815
816 let request = db
817 .request(
818 bigtable::ReadRowsRequest::default(),
819 &metadata,
820 tokio::time::Instant::now() + Duration::from_secs(5),
821 )
822 .await
823 .unwrap();
824
825 assert_eq!(
826 request.metadata().get("x-goog-request-params").unwrap(),
827 "table_name=test"
828 );
829 assert!(request.metadata().get("grpc-timeout").is_some());
830 }
831
832 #[actix_rt::test]
833 async fn retry_metric_is_consumed_only_by_a_followup_attempt() {
834 let pending = Arc::new(Mutex::new(None));
835 let attempt_pending = pending.clone();
836 let condition_pending = pending.clone();
837 let attempts = Arc::new(AtomicUsize::new(0));
838 let attempt_count = attempts.clone();
839 let emitted = Arc::new(AtomicUsize::new(0));
840 let emitted_count = emitted.clone();
841
842 let result = RetryPolicy::fixed(Duration::ZERO)
843 .with_max_retries(1)
844 .retry_if(
845 || {
846 attempt_count.fetch_add(1, Ordering::Relaxed);
847 if take_pending_retry_metric(&attempt_pending).is_some() {
848 emitted_count.fetch_add(1, Ordering::Relaxed);
849 }
850 async { Err::<(), _>(error::BigTableError::AttemptTimeout) }
851 },
852 move |error: &error::BigTableError| {
853 if let Some(retry_metric) = classify_retry(RetryKind::Read, error) {
854 set_pending_retry_metric(&condition_pending, retry_metric);
855 true
856 } else {
857 false
858 }
859 },
860 )
861 .await;
862
863 assert!(matches!(result, Err(error::BigTableError::AttemptTimeout)));
864 assert_eq!(attempts.load(Ordering::Relaxed), 2);
865 assert_eq!(emitted.load(Ordering::Relaxed), 1);
866 }
867
868 #[test]
869 fn local_failures_do_not_count_toward_the_backend_breaker() {
870 assert!(!counts_toward_breaker(
871 &error::BigTableError::PreSendTimeout,
872 false,
873 ));
874 assert!(!counts_toward_breaker(
875 &error::BigTableError::CircuitBreakerOpen,
876 false,
877 ));
878 assert!(!counts_toward_breaker(
879 &error::BigTableError::OperationTimeout,
880 false,
881 ));
882 assert!(counts_toward_breaker(
883 &error::BigTableError::OperationTimeout,
884 true,
885 ));
886 assert!(counts_toward_breaker(
887 &error::BigTableError::Read(Status::unavailable("backend unavailable")),
888 false,
889 ));
890 }
891}
892
893pub fn metric(metrics: &Arc<StatsdClient>, err_type: &str, code: Option<&str>) {
894 let mut metric = metrics
895 .incr_with_tags(MetricName::DatabaseRetry)
896 .with_tag("error", err_type)
897 .with_tag("type", "bigtable");
898 if let Some(code) = code {
899 metric = metric.with_tag("code", code);
900 }
901 metric.send();
902}
903
904#[derive(Clone, Copy, Debug)]
905struct RetryMetric {
906 error: &'static str,
907 code: Option<Code>,
908}
909
910impl RetryMetric {
911 fn send(self, metrics: &Arc<StatsdClient>) {
912 let code = self.code.map(|code| format!("{code:?}"));
913 metric(metrics, self.error, code.as_deref());
914 }
915}
916
917fn take_pending_retry_metric(pending: &Mutex<Option<RetryMetric>>) -> Option<RetryMetric> {
918 pending
919 .lock()
920 .unwrap_or_else(|poisoned| poisoned.into_inner())
921 .take()
922}
923
924fn set_pending_retry_metric(pending: &Mutex<Option<RetryMetric>>, metric: RetryMetric) {
925 *pending
926 .lock()
927 .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(metric);
928}
929
930fn classify_retry(kind: RetryKind, err: &error::BigTableError) -> Option<RetryMetric> {
933 debug!("🉑 Checking BigTableError...{err}");
934 match err {
935 error::BigTableError::InvalidRowResponse(status)
936 | error::BigTableError::Read(status)
937 | error::BigTableError::Write(status) => {
938 let retry = match kind {
939 RetryKind::Read | RetryKind::IdempotentWrite => retryable_read_err(status),
940 RetryKind::ConditionalWrite => retryable_pre_send_err(status),
941 };
942 info!("GRPC Failure: {:?}", status);
943 retry.then_some(RetryMetric {
944 error: "RpcFailure",
945 code: Some(status.code()),
946 })
947 }
948 error::BigTableError::AttemptTimeout if kind != RetryKind::ConditionalWrite => {
949 Some(RetryMetric {
950 error: "AttemptTimeout",
951 code: None,
952 })
953 }
954 error::BigTableError::PreSendTimeout => Some(RetryMetric {
958 error: "PreSendTimeout",
959 code: None,
960 }),
961 error::BigTableError::Auth(_) => Some(RetryMetric {
964 error: "Auth",
965 code: None,
966 }),
967 _ => None,
968 }
969}
970
971fn is_incomplete_router_record(cells: &RowCells) -> bool {
985 cells
986 .keys()
987 .all(|k| ["current_timestamp", "version"].contains(&k.as_str()) || k.starts_with("chid:"))
988}
989
990impl BigTableClientImpl {
1015 pub fn new(metrics: Arc<StatsdClient>, settings: &DbSettings) -> DbResult<Self> {
1016 debug!("🏊 BT Pool new");
1017 let db_settings = BigTableDbSettings::try_from(settings.db_settings.as_ref())?;
1018 info!("🉑 {:#?}", db_settings);
1019 let pool = BigTablePool::new(settings, &metrics)?;
1020
1021 let metadata = db_settings.metadata()?;
1023 Ok(Self {
1024 settings: db_settings,
1025 metrics,
1026 metadata,
1027 pool,
1028 circuit_breaker: Arc::new(CircuitBreaker::default()),
1029 })
1030 }
1031
1032 fn router_ttl(&self) -> Duration {
1033 self.settings
1034 .max_router_ttl
1035 .unwrap_or(Duration::from_secs(MAX_ROUTER_TTL_SECS))
1036 }
1037
1038 fn read_row_request(&self, row_key: &str) -> bigtable::ReadRowsRequest {
1040 read_row_request(
1041 &self.settings.table_name,
1042 &self.settings.app_profile_id,
1043 row_key,
1044 )
1045 }
1046
1047 fn mutate_row_request(&self, row_key: &str) -> bigtable::MutateRowRequest {
1049 bigtable::MutateRowRequest {
1050 table_name: self.settings.table_name.clone(),
1051 app_profile_id: self.settings.app_profile_id.clone(),
1052 row_key: row_key.as_bytes().to_vec(),
1053 ..Default::default()
1054 }
1055 }
1056
1057 fn check_and_mutate_row_request(&self, row_key: &str) -> bigtable::CheckAndMutateRowRequest {
1059 bigtable::CheckAndMutateRowRequest {
1060 table_name: self.settings.table_name.clone(),
1061 app_profile_id: self.settings.app_profile_id.clone(),
1062 row_key: row_key.as_bytes().to_vec(),
1063 ..Default::default()
1064 }
1065 }
1066
1067 fn rpc_policy(
1068 &self,
1069 class: RpcClass,
1070 retry_kind: RetryKind,
1071 breaker: BreakerPolicy,
1072 ) -> RpcPolicy {
1073 let (attempt_timeout, total_timeout) = match class {
1074 RpcClass::Point => (
1075 self.settings.grpc_point_attempt_timeout,
1076 self.settings.grpc_point_total_timeout,
1077 ),
1078 RpcClass::Scan => (
1079 self.settings.grpc_scan_attempt_timeout,
1080 self.settings.grpc_scan_total_timeout,
1081 ),
1082 };
1083 RpcPolicy {
1084 attempt_timeout,
1085 total_timeout,
1086 retry_kind,
1087 breaker,
1088 }
1089 }
1090
1091 async fn execute_rpc<M, T, Call, CallFuture>(
1098 &self,
1099 message: M,
1100 policy: RpcPolicy,
1101 call: Call,
1102 ) -> Result<T, error::BigTableError>
1103 where
1104 M: Clone,
1105 Call: Fn(Channel, Request<M>) -> CallFuture + Clone,
1106 CallFuture: Future<Output = Result<T, error::BigTableError>>,
1107 {
1108 if policy.breaker == BreakerPolicy::Track && !self.circuit_breaker.allow_request() {
1109 return Err(error::BigTableError::CircuitBreakerOpen);
1110 }
1111
1112 let operation_deadline = tokio::time::Instant::now() + policy.total_timeout;
1113 let pooled = match tokio::time::timeout_at(operation_deadline, self.pool.get()).await {
1114 Ok(result) => result?,
1115 Err(_) => return Err(error::BigTableError::OperationTimeout),
1116 };
1117 let bigtable = (*pooled).clone();
1118 let metadata = self.metadata.clone();
1119 let retry_policy = retry_policy(self.settings.retry_count);
1120 let pending_retry_metric = Arc::new(Mutex::new(None));
1121 let attempt_retry_metric = pending_retry_metric.clone();
1122 let condition_retry_metric = pending_retry_metric;
1123 let metrics = self.metrics.clone();
1124 let rpc_in_flight = Arc::new(AtomicBool::new(false));
1128 let attempt_rpc_in_flight = rpc_in_flight.clone();
1129 let result = operation_budget_until(
1130 operation_deadline,
1131 retry_policy.retry_if(
1132 || {
1133 if let Some(retry_metric) = take_pending_retry_metric(&attempt_retry_metric) {
1136 retry_metric.send(&metrics);
1137 }
1138 let attempt_deadline = std::cmp::min(
1143 tokio::time::Instant::now() + policy.attempt_timeout,
1144 operation_deadline,
1145 );
1146 let bigtable = bigtable.clone();
1147 let metadata = metadata.clone();
1148 let message = message.clone();
1149 let channel = self.pool.next_channel();
1150 let call = call.clone();
1151 let rpc_in_flight = attempt_rpc_in_flight.clone();
1152 async move {
1153 let request = bigtable
1154 .request(message, &metadata, attempt_deadline)
1155 .await?;
1156 rpc_in_flight.store(true, Ordering::Relaxed);
1157 let result =
1158 rpc_attempt_until(attempt_deadline, call(channel, request)).await;
1159 rpc_in_flight.store(false, Ordering::Relaxed);
1160 result
1161 }
1162 },
1163 move |error: &error::BigTableError| {
1164 if let Some(retry_metric) = classify_retry(policy.retry_kind, error) {
1165 set_pending_retry_metric(&condition_retry_metric, retry_metric);
1166 true
1167 } else {
1168 false
1169 }
1170 },
1171 ),
1172 )
1173 .await;
1174
1175 if policy.breaker == BreakerPolicy::Track {
1176 let operation_timed_out_in_rpc = rpc_in_flight.load(Ordering::Relaxed);
1177 match &result {
1178 Ok(_) => self.circuit_breaker.record_success(),
1179 Err(error) if counts_toward_breaker(error, operation_timed_out_in_rpc) => {
1180 self.circuit_breaker.record_failure();
1181 }
1182 Err(_) => {}
1183 }
1184 }
1185 result
1186 }
1187
1188 async fn mutate_row(
1190 &self,
1191 req: bigtable::MutateRowRequest,
1192 ) -> Result<(), error::BigTableError> {
1193 let policy = self.rpc_policy(
1194 RpcClass::Point,
1195 RetryKind::IdempotentWrite,
1196 BreakerPolicy::Track,
1197 );
1198 self.execute_rpc(req, policy, |channel, request| async move {
1199 let mut client = BigtableDb::client(channel);
1200 client
1201 .mutate_row(request)
1202 .await
1203 .map_err(error::BigTableError::Write)?;
1204 Ok(())
1205 })
1206 .await
1207 }
1208
1209 async fn read_row(
1211 &self,
1212 req: bigtable::ReadRowsRequest,
1213 ) -> Result<Option<row::Row>, error::BigTableError> {
1214 let mut rows = self.read_rows_with_class(req, RpcClass::Point).await?;
1215 Ok(rows.pop_first().map(|(_, v)| v))
1216 }
1217
1218 async fn read_rows(
1222 &self,
1223 req: bigtable::ReadRowsRequest,
1224 ) -> Result<BTreeMap<RowKey, row::Row>, error::BigTableError> {
1225 self.read_rows_with_class(req, RpcClass::Scan).await
1226 }
1227
1228 async fn read_rows_with_class(
1229 &self,
1230 req: bigtable::ReadRowsRequest,
1231 class: RpcClass,
1232 ) -> Result<BTreeMap<RowKey, row::Row>, error::BigTableError> {
1233 self.read_rows_with_policy(req, class, BreakerPolicy::Track)
1234 .await
1235 }
1236
1237 async fn read_rows_with_policy(
1238 &self,
1239 req: bigtable::ReadRowsRequest,
1240 class: RpcClass,
1241 breaker: BreakerPolicy,
1242 ) -> Result<BTreeMap<RowKey, row::Row>, error::BigTableError> {
1243 let policy = self.rpc_policy(class, RetryKind::Read, breaker);
1244 self.execute_rpc(req, policy, |channel, request| async move {
1245 let mut client = BigtableDb::client(channel);
1246 let response = client
1247 .read_rows(request)
1248 .await
1249 .map_err(error::BigTableError::Read)?
1250 .into_inner();
1251 merge::RowMerger::process_chunks(response).await
1252 })
1253 .await
1254 }
1255
1256 async fn write_row(&self, row: row::Row) -> Result<(), error::BigTableError> {
1260 let mut req = self.mutate_row_request(&row.row_key);
1261 req.mutations = self.get_mutations(row.cells)?;
1266 self.mutate_row(req).await?;
1267 Ok(())
1268 }
1269
1270 fn get_mutations(
1272 &self,
1273 cells: HashMap<FamilyId, Vec<crate::db::bigtable::bigtable_client::cell::Cell>>,
1274 ) -> Result<Vec<bigtable::Mutation>, error::BigTableError> {
1275 let mut mutations = Vec::new();
1276 for (family_id, cells) in cells {
1277 for cell in cells {
1278 let timestamp = cell
1279 .timestamp
1280 .duration_since(SystemTime::UNIX_EPOCH)
1281 .map_err(error::BigTableError::WriteTime)?;
1282 debug!("🉑 expiring in {:?}", timestamp.as_millis());
1283 mutations.push(bigtable::Mutation {
1284 mutation: Some(bigtable::mutation::Mutation::SetCell(
1285 bigtable::mutation::SetCell {
1286 family_name: family_id.clone(),
1287 column_qualifier: cell.qualifier.clone().into_bytes(),
1288 timestamp_micros: (timestamp.as_millis() * 1000) as i64,
1292 value: cell.value,
1293 },
1294 )),
1295 });
1296 }
1297 }
1298 Ok(mutations)
1299 }
1300
1301 async fn check_and_mutate_row(
1309 &self,
1310 row: row::Row,
1311 filter: bigtable::RowFilter,
1312 state: bool,
1313 ) -> Result<bool, error::BigTableError> {
1314 let mut req = self.check_and_mutate_row_request(&row.row_key);
1315 let mutations = self.get_mutations(row.cells)?;
1316 req.predicate_filter = Some(filter);
1317 if state {
1318 req.true_mutations = mutations;
1319 } else {
1320 req.false_mutations = mutations;
1321 }
1322 self.check_and_mutate(req).await
1323 }
1324
1325 async fn check_and_mutate(
1326 &self,
1327 req: bigtable::CheckAndMutateRowRequest,
1328 ) -> Result<bool, error::BigTableError> {
1329 let policy = self.rpc_policy(
1330 RpcClass::Point,
1331 RetryKind::ConditionalWrite,
1332 BreakerPolicy::Track,
1333 );
1334 let predicate_matched = self
1335 .execute_rpc(req, policy, |channel, request| async move {
1336 let mut client = BigtableDb::client(channel);
1337 let response = client
1338 .check_and_mutate_row(request)
1339 .await
1340 .map_err(error::BigTableError::Write)?;
1341 Ok(response.into_inner().predicate_matched)
1342 })
1343 .await?;
1344 debug!("🉑 Predicate Matched: {predicate_matched}");
1345 Ok(predicate_matched)
1346 }
1347
1348 fn get_delete_mutations(
1349 &self,
1350 family: &str,
1351 column_names: &[&str],
1352 time_range: Option<&bigtable::TimestampRange>,
1353 ) -> Result<Vec<bigtable::Mutation>, error::BigTableError> {
1354 let mut mutations = Vec::new();
1355 for column in column_names {
1356 mutations.push(bigtable::Mutation {
1360 mutation: Some(bigtable::mutation::Mutation::DeleteFromColumn(
1361 bigtable::mutation::DeleteFromColumn {
1362 family_name: family.to_owned(),
1363 column_qualifier: column.as_bytes().to_vec(),
1364 time_range: time_range.cloned(),
1365 },
1366 )),
1367 });
1368 }
1369 Ok(mutations)
1370 }
1371
1372 async fn delete_row(&self, row_key: &str) -> Result<(), error::BigTableError> {
1374 let mut req = self.mutate_row_request(row_key);
1375 req.mutations = vec![bigtable::Mutation {
1376 mutation: Some(bigtable::mutation::Mutation::DeleteFromRow(
1377 bigtable::mutation::DeleteFromRow {},
1378 )),
1379 }];
1380 self.mutate_row(req).await
1381 }
1382
1383 fn rows_to_notifications(
1384 &self,
1385 rows: BTreeMap<String, Row>,
1386 ) -> Result<Vec<Notification>, DbError> {
1387 rows.into_iter()
1388 .map(|(row_key, row)| self.row_to_notification(&row_key, row))
1389 .collect()
1390 }
1391
1392 fn row_to_notification(&self, row_key: &str, mut row: Row) -> Result<Notification, DbError> {
1393 let Some((_, chidmessageid)) = row_key.split_once('#') else {
1394 return Err(DbError::Integrity(
1395 "rows_to_notification expected row_key: uaid:chidmessageid ".to_owned(),
1396 None,
1397 ));
1398 };
1399 let range_key = RangeKey::parse_chidmessageid(chidmessageid).map_err(|e| {
1400 DbError::Integrity(
1401 format!("rows_to_notification expected chidmessageid: {e}"),
1402 None,
1403 )
1404 })?;
1405
1406 let mut notif = Notification {
1408 channel_id: range_key.channel_id,
1409 topic: range_key.topic,
1410 sortkey_timestamp: range_key.sortkey_timestamp,
1411 version: to_string(row.take_required_cell("version")?.value, "version")?,
1412 ttl: to_u64(row.take_required_cell("ttl")?.value, "ttl")?,
1413 timestamp: to_u64(row.take_required_cell("timestamp")?.value, "timestamp")?,
1414 ..Default::default()
1415 };
1416
1417 if let Some(cell) = row.take_cell("data") {
1419 notif.data = Some(to_string(cell.value, "data")?);
1420 }
1421 #[cfg(feature = "reliable_report")]
1422 {
1423 if let Some(cell) = row.take_cell("reliability_id") {
1424 notif.reliability_id = Some(to_string(cell.value, "reliability_id")?);
1425 }
1426 if let Some(cell) = row.take_cell("reliable_state") {
1427 notif.reliable_state = Some(
1428 crate::reliability::ReliabilityState::from_str(&to_string(
1429 cell.value,
1430 "reliable_state",
1431 )?)
1432 .map_err(|e| {
1433 DbError::DeserializeString(format!("Could not parse reliable_state {e:?}"))
1434 })?,
1435 );
1436 }
1437 }
1438 if let Some(cell) = row.take_cell("headers") {
1439 notif.headers = Some(
1440 serde_json::from_str::<HashMap<String, String>>(&to_string(cell.value, "headers")?)
1441 .map_err(|e| DbError::Serialization(e.to_string()))?,
1442 );
1443 }
1444 #[cfg(feature = "reliable_report")]
1445 if let Some(cell) = row.take_cell("reliability_id") {
1446 trace!("🚣 Is reliable");
1447 notif.reliability_id = Some(to_string(cell.value, "reliability_id")?);
1448 }
1449
1450 trace!("🚣 Deserialized message row: {:?}", ¬if);
1451 Ok(notif)
1452 }
1453
1454 fn user_to_row(&self, user: &User, version: &Uuid) -> Row {
1460 let row_key = user.uaid.simple().to_string();
1461 let mut row = Row::new(row_key);
1462 let expiry = std::time::SystemTime::now() + self.router_ttl();
1463
1464 let mut cells: Vec<cell::Cell> = vec![
1465 cell::Cell {
1466 qualifier: "connected_at".to_owned(),
1467 value: user.connected_at.to_be_bytes().to_vec(),
1468 timestamp: expiry,
1469 ..Default::default()
1470 },
1471 cell::Cell {
1472 qualifier: "router_type".to_owned(),
1473 value: user.router_type.clone().into_bytes(),
1474 timestamp: expiry,
1475 ..Default::default()
1476 },
1477 cell::Cell {
1478 qualifier: "record_version".to_owned(),
1479 value: user
1480 .record_version
1481 .unwrap_or(USER_RECORD_VERSION)
1482 .to_be_bytes()
1483 .to_vec(),
1484 timestamp: expiry,
1485 ..Default::default()
1486 },
1487 cell::Cell {
1488 qualifier: "version".to_owned(),
1489 value: (*version).into(),
1490 timestamp: expiry,
1491 ..Default::default()
1492 },
1493 ];
1494
1495 if let Some(router_data) = &user.router_data {
1496 cells.push(cell::Cell {
1497 qualifier: "router_data".to_owned(),
1498 value: json!(router_data).to_string().as_bytes().to_vec(),
1499 timestamp: expiry,
1500 ..Default::default()
1501 });
1502 };
1503 if let Some(current_timestamp) = user.current_timestamp {
1504 cells.push(cell::Cell {
1505 qualifier: "current_timestamp".to_owned(),
1506 value: current_timestamp.to_be_bytes().to_vec(),
1507 timestamp: expiry,
1508 ..Default::default()
1509 });
1510 };
1511 if let Some(node_id) = &user.node_id {
1512 cells.push(cell::Cell {
1513 qualifier: "node_id".to_owned(),
1514 value: node_id.as_bytes().to_vec(),
1515 timestamp: expiry,
1516 ..Default::default()
1517 });
1518 };
1519
1520 cells.extend(channels_to_cells(
1521 Cow::Borrowed(&user.priv_channels),
1522 expiry,
1523 ));
1524
1525 row.add_cells(ROUTER_FAMILY, cells);
1526 row
1527 }
1528}
1529
1530#[derive(Clone)]
1531pub struct BigtableDb {
1532 auth_provider: Option<Arc<dyn TokenProvider>>,
1536}
1537
1538impl BigtableDb {
1539 pub fn new(auth_provider: Option<Arc<dyn TokenProvider>>) -> Self {
1540 Self { auth_provider }
1541 }
1542
1543 pub(super) fn client(channel: Channel) -> BigtableClient<Channel> {
1544 BigtableClient::new(channel)
1545 .max_decoding_message_size(MAX_MESSAGE_LEN)
1546 .max_encoding_message_size(MAX_MESSAGE_LEN)
1547 }
1548
1549 pub(super) async fn request<T>(
1560 &self,
1561 msg: T,
1562 metadata: &MetadataMap,
1563 deadline: tokio::time::Instant,
1564 ) -> Result<Request<T>, error::BigTableError> {
1565 let authorization = if let Some(provider) = &self.auth_provider {
1566 let token = tokio::time::timeout_at(deadline, provider.token(BIGTABLE_DATA_SCOPES))
1567 .await
1568 .map_err(|_| error::BigTableError::PreSendTimeout)?
1569 .map_err(error::BigTableError::Auth)?;
1570 Some(
1571 format!("Bearer {}", token.as_str())
1572 .parse::<AsciiMetadataValue>()
1573 .map_err(|e| {
1574 error::BigTableError::Config(format!("Invalid auth token: {e}"))
1575 })?,
1576 )
1577 } else {
1578 None
1579 };
1580
1581 let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
1582 if remaining.is_zero() {
1583 return Err(error::BigTableError::PreSendTimeout);
1584 }
1585
1586 let mut request = Request::new(msg);
1587 *request.metadata_mut() = metadata.clone();
1588 request.set_timeout(remaining);
1591 if let Some(value) = authorization {
1592 request.metadata_mut().insert("authorization", value);
1593 }
1594 Ok(request)
1595 }
1596}
1597
1598#[async_trait]
1599impl DbClient for BigTableClientImpl {
1600 async fn add_user(&self, user: &User) -> DbResult<()> {
1602 trace!("🉑 Adding user");
1603 let Some(ref version) = user.version else {
1604 return Err(DbError::General(
1605 "add_user expected a user version field".to_owned(),
1606 ));
1607 };
1608 let row = self.user_to_row(user, version);
1609
1610 let row_key_filter = bigtable::RowFilter {
1612 filter: Some(bigtable::row_filter::Filter::RowKeyRegexFilter(
1613 format!("^{}$", row.row_key).into_bytes(),
1614 )),
1615 };
1616 let mut filters = router_gc_policy_filter()?;
1617 filters.push(row_key_filter);
1618 let filter = filter_chain(filters);
1619
1620 if self.check_and_mutate_row(row, filter, false).await? {
1621 return Err(DbError::Conditional);
1622 }
1623 Ok(())
1624 }
1625
1626 async fn update_user(&self, user: &mut User) -> DbResult<bool> {
1642 let Some(ref version) = user.version else {
1643 return Err(DbError::General(
1644 "update_user expected a user version field".to_owned(),
1645 ));
1646 };
1647
1648 let mut filters = router_gc_policy_filter()?;
1649 filters.extend(version_filter(version));
1650 let filter = filter_chain(filters);
1651
1652 let new_version = Uuid::new_v4();
1653 let row = self.user_to_row(user, &new_version);
1655
1656 let predicate_matched = self.check_and_mutate_row(row, filter, true).await?;
1657 user.version = Some(new_version);
1658 Ok(predicate_matched)
1659 }
1660
1661 async fn get_user(&self, uaid: &Uuid) -> DbResult<Option<User>> {
1662 let row_key = uaid.as_simple().to_string();
1663 let mut req = self.read_row_request(&row_key);
1664 let mut filters = router_gc_policy_filter()?;
1669 filters.push(family_filter(format!("^{ROUTER_FAMILY}$")));
1670 req.filter = Some(filter_chain(filters));
1671 let Some(mut row) = self.read_row(req).await? else {
1672 return Ok(None);
1673 };
1674
1675 trace!("🉑 Found a record for {}", row_key);
1676
1677 let connected_at_cell = match row.take_required_cell("connected_at") {
1678 Ok(cell) => cell,
1679 Err(_) => {
1680 if !is_incomplete_router_record(&row.cells) {
1681 return Err(DbError::Integrity(
1682 "Expected column: connected_at".to_owned(),
1683 Some(format!("{row:#?}")),
1684 ));
1685 }
1686 trace!("🉑 Dropping an incomplete user record for {}", row_key);
1691 self.metrics
1692 .incr_with_tags(MetricName::DatabaseDropUser)
1693 .with_tag("reason", "incomplete_record")
1694 .send();
1695 self.remove_user(uaid).await?;
1696 return Ok(None);
1697 }
1698 };
1699
1700 let mut result = User {
1701 uaid: *uaid,
1702 connected_at: to_u64(connected_at_cell.value, "connected_at")?,
1703 router_type: to_string(row.take_required_cell("router_type")?.value, "router_type")?,
1704 record_version: Some(to_u64(
1705 row.take_required_cell("record_version")?.value,
1706 "record_version",
1707 )?),
1708 version: Some(
1709 row.take_required_cell("version")?
1710 .value
1711 .try_into()
1712 .map_err(|e| {
1713 DbError::Serialization(format!("Could not deserialize version: {e:?}"))
1714 })?,
1715 ),
1716 ..Default::default()
1717 };
1718
1719 if let Some(cell) = row.take_cell("router_data") {
1720 result.router_data = from_str(&to_string(cell.value, "router_type")?).map_err(|e| {
1721 DbError::Serialization(format!("Could not deserialize router_type: {e:?}"))
1722 })?;
1723 }
1724
1725 if let Some(cell) = row.take_cell("node_id") {
1726 result.node_id = Some(to_string(cell.value, "node_id")?);
1727 }
1728
1729 if let Some(cell) = row.take_cell("current_timestamp") {
1730 result.current_timestamp = Some(to_u64(cell.value, "current_timestamp")?)
1731 }
1732
1733 result.priv_channels = channels_from_cells(&row.cells)?;
1735
1736 Ok(Some(result))
1737 }
1738
1739 async fn remove_user(&self, uaid: &Uuid) -> DbResult<()> {
1740 let row_key = uaid.simple().to_string();
1741 self.delete_row(&row_key).await?;
1742 Ok(())
1743 }
1744
1745 async fn add_channel(&self, uaid: &Uuid, channel_id: &Uuid) -> DbResult<()> {
1746 let channels = HashSet::from_iter([channel_id.to_owned()]);
1747 self.add_channels(uaid, channels).await
1748 }
1749
1750 async fn add_channels(&self, uaid: &Uuid, channels: HashSet<Uuid>) -> DbResult<()> {
1753 let row_key = uaid.simple().to_string();
1767 let mut row = Row::new(row_key);
1768 let expiry = std::time::SystemTime::now() + self.router_ttl();
1769
1770 row.add_cells(
1774 ROUTER_FAMILY,
1775 channels_to_cells(Cow::Owned(channels), expiry),
1776 );
1777
1778 self.write_row(row).await?;
1779 Ok(())
1780 }
1781
1782 async fn get_channels(&self, uaid: &Uuid) -> DbResult<HashSet<Uuid>> {
1783 let row_key = uaid.simple().to_string();
1784 let mut req = self.read_row_request(&row_key);
1785
1786 let cq_filter = bigtable::RowFilter {
1787 filter: Some(bigtable::row_filter::Filter::ColumnQualifierRegexFilter(
1788 "^chid:.*$".as_bytes().to_vec(),
1789 )),
1790 };
1791 let mut filters = router_gc_policy_filter()?;
1792 filters.push(family_filter(format!("^{ROUTER_FAMILY}$")));
1793 filters.push(cq_filter);
1794 req.filter = Some(filter_chain(filters));
1795
1796 let Some(row) = self.read_row(req).await? else {
1797 return Ok(Default::default());
1798 };
1799 channels_from_cells(&row.cells)
1800 }
1801
1802 async fn remove_channel(&self, uaid: &Uuid, channel_id: &Uuid) -> DbResult<bool> {
1804 let row_key = uaid.simple().to_string();
1805 let mut req = self.check_and_mutate_row_request(&row_key);
1806
1807 let column = format!("chid:{}", channel_id.as_hyphenated());
1809 let mut mutations = self.get_delete_mutations(ROUTER_FAMILY, &[column.as_ref()], None)?;
1810
1811 let mut row = Row::new(row_key);
1813 let expiry = std::time::SystemTime::now() + self.router_ttl();
1814 row.cells
1815 .insert(ROUTER_FAMILY.to_owned(), vec![new_version_cell(expiry)]);
1816 mutations.extend(self.get_mutations(row.cells)?);
1817
1818 let cq_filter = bigtable::RowFilter {
1820 filter: Some(bigtable::row_filter::Filter::ColumnQualifierRegexFilter(
1821 format!("^{column}$").into_bytes(),
1822 )),
1823 };
1824 let mut filters = router_gc_policy_filter()?;
1825 filters.push(cq_filter);
1826 req.predicate_filter = Some(filter_chain(filters));
1827 req.true_mutations = mutations;
1828
1829 Ok(self.check_and_mutate(req).await?)
1830 }
1831
1832 async fn remove_node_id(
1834 &self,
1835 uaid: &Uuid,
1836 _node_id: &str,
1837 _connected_at: u64,
1838 version: &Option<Uuid>,
1839 ) -> DbResult<bool> {
1840 let row_key = uaid.simple().to_string();
1841 trace!("🉑 Removing node_id for: {row_key} (version: {version:?}) ",);
1842 let Some(version) = version else {
1843 return Err(DbError::General("Expected a user version field".to_owned()));
1844 };
1845
1846 let mut req = self.check_and_mutate_row_request(&row_key);
1847
1848 let mut filters = router_gc_policy_filter()?;
1849 filters.extend(version_filter(version));
1850 req.predicate_filter = Some(filter_chain(filters));
1851 req.true_mutations = self.get_delete_mutations(ROUTER_FAMILY, &["node_id"], None)?;
1852
1853 Ok(self.check_and_mutate(req).await?)
1854 }
1855
1856 async fn save_message(&self, uaid: &Uuid, message: Notification) -> DbResult<()> {
1858 let row_key = format!("{}#{}", uaid.simple(), message.chidmessageid());
1859 debug!("🗄️ Saving message {} :: {:?}", &row_key, &message);
1860 trace!(
1861 "🉑 timestamp: {:?}",
1862 &message.timestamp.to_be_bytes().to_vec()
1863 );
1864 let mut row = Row::new(row_key);
1865
1866 let expiry = SystemTime::now() + Duration::from_secs(message.ttl);
1870 trace!(
1871 "🉑 Message Expiry {}",
1872 expiry
1873 .duration_since(SystemTime::UNIX_EPOCH)
1874 .unwrap_or_default()
1875 .as_millis()
1876 );
1877
1878 let mut cells: Vec<cell::Cell> = Vec::new();
1879
1880 let is_topic = message.topic.is_some();
1881 let family = if is_topic {
1882 MESSAGE_TOPIC_FAMILY
1883 } else {
1884 MESSAGE_FAMILY
1885 };
1886 cells.extend(vec![
1887 cell::Cell {
1888 qualifier: "ttl".to_owned(),
1889 value: message.ttl.to_be_bytes().to_vec(),
1890 timestamp: expiry,
1891 ..Default::default()
1892 },
1893 cell::Cell {
1894 qualifier: "timestamp".to_owned(),
1895 value: message.timestamp.to_be_bytes().to_vec(),
1896 timestamp: expiry,
1897 ..Default::default()
1898 },
1899 cell::Cell {
1900 qualifier: "version".to_owned(),
1901 value: message.version.into_bytes(),
1902 timestamp: expiry,
1903 ..Default::default()
1904 },
1905 ]);
1906 if let Some(headers) = message.headers
1907 && !headers.is_empty()
1908 {
1909 cells.push(cell::Cell {
1910 qualifier: "headers".to_owned(),
1911 value: json!(headers).to_string().into_bytes(),
1912 timestamp: expiry,
1913 ..Default::default()
1914 });
1915 }
1916 #[cfg(feature = "reliable_report")]
1917 {
1918 if let Some(reliability_id) = message.reliability_id {
1919 trace!("🔍 FOUND RELIABILITY ID: {}", reliability_id);
1920 cells.push(cell::Cell {
1921 qualifier: "reliability_id".to_owned(),
1922 value: reliability_id.into_bytes(),
1923 timestamp: expiry,
1924 ..Default::default()
1925 });
1926 }
1927 if let Some(reliable_state) = message.reliable_state {
1928 cells.push(cell::Cell {
1929 qualifier: "reliable_state".to_owned(),
1930 value: reliable_state.to_string().into_bytes(),
1931 timestamp: expiry,
1932 ..Default::default()
1933 });
1934 }
1935 }
1936 if let Some(data) = message.data {
1937 cells.push(cell::Cell {
1938 qualifier: "data".to_owned(),
1939 value: data.into_bytes(),
1940 timestamp: expiry,
1941 ..Default::default()
1942 });
1943 }
1944
1945 row.add_cells(family, cells);
1946 trace!("🉑 Adding row");
1947 self.write_row(row).await?;
1948
1949 self.metrics
1950 .incr_with_tags(MetricName::NotificationMessageStored)
1951 .with_tag("topic", &is_topic.to_string())
1952 .with_tag("database", &self.name())
1953 .send();
1954 Ok(())
1955 }
1956
1957 async fn save_messages(&self, uaid: &Uuid, messages: Vec<Notification>) -> DbResult<()> {
1962 for message in messages {
1964 self.save_message(uaid, message).await?;
1965 }
1966 Ok(())
1967 }
1968
1969 async fn increment_storage(&self, uaid: &Uuid, timestamp: u64) -> DbResult<()> {
1983 let row_key = uaid.simple().to_string();
1984 debug!(
1985 "🉑 Updating {} current_timestamp: {:?}",
1986 &row_key,
1987 timestamp.to_be_bytes().to_vec()
1988 );
1989 let expiry = std::time::SystemTime::now() + self.router_ttl();
1990 let mut row = Row::new(row_key.clone());
1991
1992 row.cells.insert(
1993 ROUTER_FAMILY.to_owned(),
1994 vec![
1995 cell::Cell {
1996 qualifier: "current_timestamp".to_owned(),
1997 value: timestamp.to_be_bytes().to_vec(),
1998 timestamp: expiry,
1999 ..Default::default()
2000 },
2001 new_version_cell(expiry),
2002 ],
2003 );
2004
2005 self.write_row(row).await?;
2006
2007 Ok(())
2008 }
2009
2010 async fn remove_message(&self, uaid: &Uuid, chidmessageid: &str) -> DbResult<()> {
2012 trace!(
2013 "🉑 attemping to delete {:?} :: {:?}",
2014 uaid.to_string(),
2015 chidmessageid
2016 );
2017 let row_key = format!("{}#{}", uaid.simple(), chidmessageid);
2018 debug!("🉑🔥 Deleting message {}", &row_key);
2019 self.delete_row(&row_key).await?;
2020 self.metrics
2021 .incr_with_tags(MetricName::NotificationMessageDeleted)
2022 .with_tag("database", &self.name())
2023 .send();
2024 Ok(())
2025 }
2026
2027 async fn fetch_topic_messages(
2029 &self,
2030 uaid: &Uuid,
2031 limit: usize,
2032 ) -> DbResult<FetchMessageResponse> {
2033 let start_key = format!("{}#01:", uaid.simple());
2034 let end_key = format!("{}#02:", uaid.simple());
2035 let mut req = bigtable::ReadRowsRequest {
2036 table_name: self.settings.table_name.clone(),
2037 app_profile_id: self.settings.app_profile_id.clone(),
2038 rows: Some(bigtable::RowSet {
2039 row_keys: Vec::new(),
2040 row_ranges: vec![bigtable::RowRange {
2041 start_key: Some(bigtable::row_range::StartKey::StartKeyOpen(
2042 start_key.into_bytes(),
2043 )),
2044 end_key: Some(bigtable::row_range::EndKey::EndKeyOpen(
2045 end_key.into_bytes(),
2046 )),
2047 }],
2048 }),
2049 ..Default::default()
2050 };
2051
2052 let mut filters = message_gc_policy_filter()?;
2053 filters.push(family_filter(format!("^{MESSAGE_TOPIC_FAMILY}$")));
2054
2055 req.filter = Some(filter_chain(filters));
2056 if limit > 0 {
2057 trace!("🉑 Setting limit to {limit}");
2058 req.rows_limit = limit as i64;
2059 }
2060 let rows = self.read_rows(req).await?;
2061 debug!(
2062 "🉑 Fetch Topic Messages. Found {} row(s) of {}",
2063 rows.len(),
2064 limit
2065 );
2066
2067 let messages = self.rows_to_notifications(rows)?;
2068
2069 Ok(FetchMessageResponse {
2073 messages,
2074 timestamp: None,
2075 })
2076 }
2077
2078 async fn fetch_timestamp_messages(
2081 &self,
2082 uaid: &Uuid,
2083 timestamp: Option<u64>,
2084 limit: usize,
2085 ) -> DbResult<FetchMessageResponse> {
2086 let start_key = if let Some(ts) = timestamp {
2087 format!("{}#02:{}z", uaid.simple(), ts)
2090 } else {
2091 format!("{}#02:", uaid.simple())
2092 };
2093 let end_key = format!("{}#03:", uaid.simple());
2094 let mut req = bigtable::ReadRowsRequest {
2095 table_name: self.settings.table_name.clone(),
2096 app_profile_id: self.settings.app_profile_id.clone(),
2097 rows: Some(bigtable::RowSet {
2098 row_keys: Vec::new(),
2099 row_ranges: vec![bigtable::RowRange {
2100 start_key: Some(bigtable::row_range::StartKey::StartKeyOpen(
2101 start_key.into_bytes(),
2102 )),
2103 end_key: Some(bigtable::row_range::EndKey::EndKeyOpen(
2104 end_key.into_bytes(),
2105 )),
2106 }],
2107 }),
2108 ..Default::default()
2109 };
2110
2111 let mut filters = message_gc_policy_filter()?;
2125 filters.push(family_filter(format!("^{MESSAGE_FAMILY}$")));
2126
2127 req.filter = Some(filter_chain(filters));
2128 if limit > 0 {
2129 req.rows_limit = limit as i64;
2130 }
2131 let rows = self.read_rows(req).await?;
2132 debug!(
2133 "🉑 Fetch Timestamp Messages ({:?}) Found {} row(s) of {}",
2134 timestamp,
2135 rows.len(),
2136 limit,
2137 );
2138
2139 let messages = self.rows_to_notifications(rows)?;
2140 let timestamp = messages.last().and_then(|m| m.sortkey_timestamp);
2142 Ok(FetchMessageResponse {
2143 messages,
2144 timestamp,
2145 })
2146 }
2147
2148 async fn health_check(&self) -> DbResult<bool> {
2149 let random_uaid = Uuid::new_v4().simple().to_string();
2152 let mut req = read_row_request(
2153 &self.settings.table_name,
2154 &self.settings.app_profile_id,
2155 &random_uaid,
2156 );
2157 req.filter = Some(bigtable::RowFilter {
2158 filter: Some(bigtable::row_filter::Filter::BlockAllFilter(true)),
2159 });
2160 self.read_rows_with_policy(req, RpcClass::Point, BreakerPolicy::Ignore)
2163 .await?;
2164 Ok(true)
2165 }
2166
2167 async fn router_table_exists(&self) -> DbResult<bool> {
2170 Ok(true)
2171 }
2172
2173 async fn message_table_exists(&self) -> DbResult<bool> {
2176 Ok(true)
2177 }
2178
2179 #[cfg(feature = "reliable_report")]
2180 async fn log_report(
2181 &self,
2182 reliability_id: &str,
2183 new_state: crate::reliability::ReliabilityState,
2184 ) -> DbResult<()> {
2185 let row_key = reliability_id.to_owned();
2186
2187 let mut row = Row::new(row_key);
2188 let expiry = SystemTime::now() + Duration::from_secs(RELIABLE_LOG_TTL.num_seconds() as u64);
2189
2190 let cells: Vec<cell::Cell> = vec![cell::Cell {
2192 qualifier: new_state.to_string(),
2193 value: crate::util::ms_since_epoch().to_be_bytes().to_vec(),
2194 timestamp: expiry,
2195 ..Default::default()
2196 }];
2197
2198 row.add_cells(RELIABLE_LOG_FAMILY, cells);
2199
2200 self.write_row(row).await?;
2201
2202 Ok(())
2203 }
2204
2205 fn box_clone(&self) -> Box<dyn DbClient> {
2206 Box::new(self.clone())
2207 }
2208
2209 fn name(&self) -> String {
2210 "Bigtable".to_owned()
2211 }
2212
2213 fn pool_status(&self) -> Option<deadpool::Status> {
2214 Some(self.pool.pool.status())
2215 }
2216
2217 fn configured_channel_count(&self) -> Option<usize> {
2218 Some(self.pool.configured_channel_count())
2219 }
2220}
2221
2222#[cfg(all(test, feature = "emulator"))]
2223mod tests {
2224
2225 use std::sync::Arc;
2230 use std::time::SystemTime;
2231
2232 use cadence::StatsdClient;
2233 use uuid;
2234
2235 use super::*;
2236 use crate::{db::DbSettings, test_support::gen_test_uaid, util::ms_since_epoch};
2237
2238 const TEST_USER: &str = "DEADBEEF-0000-0000-0000-0123456789AB";
2239 const TEST_CHID: &str = "DECAFBAD-0000-0000-0000-0123456789AB";
2240 const TOPIC_CHID: &str = "DECAFBAD-1111-0000-0000-0123456789AB";
2241
2242 fn now() -> u64 {
2243 SystemTime::now()
2244 .duration_since(SystemTime::UNIX_EPOCH)
2245 .unwrap()
2246 .as_secs()
2247 }
2248
2249 fn new_client() -> DbResult<BigTableClientImpl> {
2250 let env_dsn = format!(
2251 "grpc://{}",
2252 std::env::var("BIGTABLE_EMULATOR_HOST").unwrap_or("localhost:8080".to_owned())
2253 );
2254 let settings = DbSettings {
2255 dsn: Some(env_dsn),
2261 db_settings: json!({"table_name": "projects/test/instances/test/tables/autopush"})
2262 .to_string(),
2263 };
2264
2265 let metrics = Arc::new(StatsdClient::builder("", cadence::NopMetricSink).build());
2266
2267 BigTableClientImpl::new(metrics, &settings)
2268 }
2269
2270 #[test]
2271 fn escape_bytes_for_regex() {
2272 let b = b"hi";
2273 assert_eq!(escape_bytes(b), b.to_vec());
2274 assert_eq!(escape_bytes(b"h.*i!"), b"h\\.\\*i\\!".to_vec());
2275 let b = b"\xe2\x80\xb3";
2276 assert_eq!(escape_bytes(b), b.to_vec());
2277 let b = [b'f', b'o', b'\0', b'2', b'2', b'o'];
2279 assert_eq!(escape_bytes(&b), b"fo\\x0022o".to_vec());
2280 let b = b"\xc0";
2281 assert_eq!(escape_bytes(b), b.to_vec());
2282 assert_eq!(escape_bytes(b"\x03"), b"\\\x03".to_vec());
2283 }
2284
2285 #[actix_rt::test]
2286 async fn health_check() {
2287 let client = new_client().unwrap();
2288
2289 let result = client.health_check().await;
2290 assert!(result.is_ok());
2291 assert!(result.unwrap());
2292 }
2293
2294 #[actix_rt::test]
2297 async fn timestamp_granularity_rejects_sub_millisecond_micros() {
2298 let client = new_client().unwrap();
2299 let uaid = gen_test_uaid();
2300 let row_key = uaid.simple().to_string();
2301 let _ = client.remove_user(&uaid).await;
2302
2303 let mut req = client.mutate_row_request(&row_key);
2304 req.mutations = vec![bigtable::Mutation {
2305 mutation: Some(bigtable::mutation::Mutation::SetCell(
2306 bigtable::mutation::SetCell {
2307 family_name: ROUTER_FAMILY.to_owned(),
2308 column_qualifier: b"granularity_probe".to_vec(),
2309 timestamp_micros: 1,
2310 value: b"x".to_vec(),
2311 },
2312 )),
2313 }];
2314
2315 assert!(
2316 client.mutate_row(req).await.is_err(),
2317 "expected granularity mismatch for timestamp_micros=1"
2318 );
2319
2320 let _ = client.remove_user(&uaid).await;
2321 }
2322
2323 #[actix_rt::test]
2326 async fn run_gauntlet() -> DbResult<()> {
2327 let client = new_client()?;
2328
2329 let connected_at = ms_since_epoch();
2330
2331 let uaid = Uuid::parse_str(TEST_USER).unwrap();
2332 let chid = Uuid::parse_str(TEST_CHID).unwrap();
2333 let topic_chid = Uuid::parse_str(TOPIC_CHID).unwrap();
2334
2335 let node_id = "test_node".to_owned();
2336
2337 let _ = client.remove_user(&uaid).await;
2339
2340 let test_user = User {
2341 uaid,
2342 router_type: "webpush".to_owned(),
2343 connected_at,
2344 router_data: None,
2345 node_id: Some(node_id.clone()),
2346 ..Default::default()
2347 };
2348
2349 let _ = client.remove_user(&uaid).await;
2352
2353 client.add_user(&test_user).await?;
2355 let fetched = client.get_user(&uaid).await?;
2356 assert!(fetched.is_some());
2357 let fetched = fetched.unwrap();
2358 assert_eq!(fetched.router_type, "webpush".to_owned());
2359
2360 let connected_at = ms_since_epoch();
2362
2363 client.add_channel(&uaid, &chid).await?;
2365 let channels = client.get_channels(&uaid).await?;
2366 assert!(channels.contains(&chid));
2367
2368 let mut new_channels: HashSet<Uuid> = HashSet::new();
2370 new_channels.insert(chid);
2371 for _ in 1..10 {
2372 new_channels.insert(uuid::Uuid::new_v4());
2373 }
2374 let chid_to_remove = uuid::Uuid::new_v4();
2375 new_channels.insert(chid_to_remove);
2376 client.add_channels(&uaid, new_channels.clone()).await?;
2377 let channels = client.get_channels(&uaid).await?;
2378 assert_eq!(channels, new_channels);
2379
2380 assert!(client.remove_channel(&uaid, &chid_to_remove).await?);
2382 assert!(!client.remove_channel(&uaid, &chid_to_remove).await?);
2383 new_channels.remove(&chid_to_remove);
2384 let channels = client.get_channels(&uaid).await?;
2385 assert_eq!(channels, new_channels);
2386
2387 let mut updated = User {
2391 connected_at,
2392 ..test_user.clone()
2393 };
2394 let result = client.update_user(&mut updated).await;
2395 assert!(result.is_ok());
2396 assert!(!result.unwrap());
2397
2398 let fetched2 = client.get_user(&fetched.uaid).await?.unwrap();
2400 assert_eq!(fetched.connected_at, fetched2.connected_at);
2401
2402 let mut updated = User {
2404 connected_at: fetched.connected_at + 300,
2405 ..fetched2
2406 };
2407 let result = client.update_user(&mut updated).await;
2408 assert!(result.is_ok());
2409 assert!(result.unwrap());
2410 assert_ne!(
2411 fetched2.connected_at,
2412 client.get_user(&uaid).await?.unwrap().connected_at
2413 );
2414
2415 client
2417 .increment_storage(
2418 &fetched.uaid,
2419 SystemTime::now()
2420 .duration_since(SystemTime::UNIX_EPOCH)
2421 .unwrap()
2422 .as_secs(),
2423 )
2424 .await?;
2425
2426 let test_data = "An_encrypted_pile_of_crap".to_owned();
2427 let timestamp = now();
2428 let sort_key = now();
2429 let test_notification = crate::db::Notification {
2431 channel_id: chid,
2432 version: "test".to_owned(),
2433 ttl: 300,
2434 timestamp,
2435 data: Some(test_data.clone()),
2436 sortkey_timestamp: Some(sort_key),
2437 ..Default::default()
2438 };
2439 let res = client.save_message(&uaid, test_notification.clone()).await;
2440 assert!(res.is_ok());
2441
2442 let mut fetched = client.fetch_timestamp_messages(&uaid, None, 999).await?;
2443 assert_ne!(fetched.messages.len(), 0);
2444 let fm = fetched.messages.pop().unwrap();
2445 assert_eq!(fm.channel_id, test_notification.channel_id);
2446 assert_eq!(fm.data, Some(test_data));
2447
2448 let fetched = client
2450 .fetch_timestamp_messages(&uaid, Some(timestamp - 10), 999)
2451 .await?;
2452 assert_ne!(fetched.messages.len(), 0);
2453
2454 let fetched = client
2456 .fetch_timestamp_messages(&uaid, Some(timestamp + 10), 999)
2457 .await?;
2458 assert_eq!(fetched.messages.len(), 0);
2459
2460 assert!(
2462 client
2463 .remove_message(&uaid, &test_notification.chidmessageid())
2464 .await
2465 .is_ok()
2466 );
2467
2468 assert!(client.remove_channel(&uaid, &chid).await.is_ok());
2469
2470 client.add_channel(&uaid, &topic_chid).await?;
2472 let test_data = "An_encrypted_pile_of_crap_with_a_topic".to_owned();
2473 let timestamp = now();
2474 let sort_key = now();
2475 let test_notification = crate::db::Notification {
2477 channel_id: topic_chid,
2478 version: "test".to_owned(),
2479 ttl: 300,
2480 topic: Some("topic".to_owned()),
2481 timestamp,
2482 data: Some(test_data.clone()),
2483 sortkey_timestamp: Some(sort_key),
2484 ..Default::default()
2485 };
2486 assert!(
2487 client
2488 .save_message(&uaid, test_notification.clone())
2489 .await
2490 .is_ok()
2491 );
2492
2493 let mut fetched = client.fetch_topic_messages(&uaid, 999).await?;
2494 assert_ne!(fetched.messages.len(), 0);
2495 let fm = fetched.messages.pop().unwrap();
2496 assert_eq!(fm.channel_id, test_notification.channel_id);
2497 assert_eq!(fm.data, Some(test_data));
2498
2499 let fetched = client.fetch_topic_messages(&uaid, 999).await?;
2501 assert_ne!(fetched.messages.len(), 0);
2502
2503 assert!(
2505 client
2506 .remove_message(&uaid, &test_notification.chidmessageid())
2507 .await
2508 .is_ok()
2509 );
2510
2511 assert!(client.remove_channel(&uaid, &topic_chid).await.is_ok());
2512
2513 let msgs = client
2514 .fetch_timestamp_messages(&uaid, None, 999)
2515 .await?
2516 .messages;
2517 assert!(msgs.is_empty());
2518
2519 let fetched = client.get_user(&uaid).await?.unwrap();
2520 assert!(
2521 client
2522 .remove_node_id(&uaid, &node_id, connected_at, &fetched.version)
2523 .await
2524 .is_ok()
2525 );
2526 let fetched = client.get_user(&uaid).await?.unwrap();
2528 assert_eq!(fetched.node_id, None);
2529
2530 assert!(client.remove_user(&uaid).await.is_ok());
2531
2532 assert!(client.get_user(&uaid).await?.is_none());
2533
2534 Ok(())
2535 }
2536
2537 #[actix_rt::test]
2538 async fn read_cells_family_id() -> DbResult<()> {
2539 let client = new_client().unwrap();
2540 let uaid = gen_test_uaid();
2541 client.remove_user(&uaid).await.unwrap();
2542
2543 let qualifier = "foo".to_owned();
2544
2545 let row_key = uaid.simple().to_string();
2546 let mut row = Row::new(row_key.clone());
2547 row.cells.insert(
2548 ROUTER_FAMILY.to_owned(),
2549 vec![cell::Cell {
2550 qualifier: qualifier.to_owned(),
2551 value: "bar".as_bytes().to_vec(),
2552 ..Default::default()
2553 }],
2554 );
2555 client.write_row(row).await.unwrap();
2556 let req = client.read_row_request(&row_key);
2557 let Some(row) = client.read_row(req).await.unwrap() else {
2558 panic!("Expected row");
2559 };
2560 assert_eq!(row.cells.len(), 1);
2561 assert_eq!(row.cells.keys().next().unwrap(), qualifier.as_str());
2562 client.remove_user(&uaid).await
2563 }
2564
2565 #[actix_rt::test]
2566 async fn add_user_existing() {
2567 let client = new_client().unwrap();
2568 let uaid = gen_test_uaid();
2569 let user = User {
2570 uaid,
2571 ..Default::default()
2572 };
2573 client.remove_user(&uaid).await.unwrap();
2574
2575 client.add_user(&user).await.unwrap();
2576 let err = client.add_user(&user).await.unwrap_err();
2577 assert!(matches!(err, DbError::Conditional));
2578 }
2579
2580 #[actix_rt::test]
2581 async fn version_check() {
2582 let client = new_client().unwrap();
2583 let uaid = gen_test_uaid();
2584 let user = User {
2585 uaid,
2586 ..Default::default()
2587 };
2588 client.remove_user(&uaid).await.unwrap();
2589
2590 client.add_user(&user).await.unwrap();
2591 let mut user = client.get_user(&uaid).await.unwrap().unwrap();
2592 assert!(client.update_user(&mut user.clone()).await.unwrap());
2593
2594 let fetched = client.get_user(&uaid).await.unwrap().unwrap();
2595 assert_ne!(user.version, fetched.version);
2596 assert!(!client.update_user(&mut user).await.unwrap());
2598
2599 client.remove_user(&uaid).await.unwrap();
2600 }
2601
2602 #[actix_rt::test]
2603 async fn lingering_chid_record() {
2604 let client = new_client().unwrap();
2605 let uaid = gen_test_uaid();
2606 let chid = Uuid::parse_str(TEST_CHID).unwrap();
2607 let user = User {
2608 uaid,
2609 ..Default::default()
2610 };
2611 client.remove_user(&uaid).await.unwrap();
2612
2613 client.add_channel(&uaid, &chid).await.unwrap();
2615
2616 assert!(client.get_user(&uaid).await.unwrap().is_none());
2619
2620 client.add_user(&user).await.unwrap();
2621 assert!(client.get_channels(&uaid).await.unwrap().is_empty());
2623
2624 client.remove_user(&uaid).await.unwrap();
2625 }
2626
2627 #[actix_rt::test]
2628 async fn lingering_current_timestamp() {
2629 let client = new_client().unwrap();
2630 let uaid = gen_test_uaid();
2631 client.remove_user(&uaid).await.unwrap();
2632
2633 client
2634 .increment_storage(&uaid, crate::util::sec_since_epoch())
2635 .await
2636 .unwrap();
2637 assert!(client.get_user(&uaid).await.unwrap().is_none());
2638
2639 client.remove_user(&uaid).await.unwrap();
2640 }
2641
2642 #[actix_rt::test]
2643 async fn lingering_chid_w_version_record() {
2644 let client = new_client().unwrap();
2645 let uaid = gen_test_uaid();
2646 let chid = Uuid::parse_str(TEST_CHID).unwrap();
2647 client.remove_user(&uaid).await.unwrap();
2648
2649 client.add_channel(&uaid, &chid).await.unwrap();
2650 assert!(client.remove_channel(&uaid, &chid).await.unwrap());
2651 assert!(client.get_user(&uaid).await.unwrap().is_none());
2652
2653 client.remove_user(&uaid).await.unwrap();
2654 }
2655
2656 #[actix_rt::test]
2657 async fn channel_and_current_timestamp_ttl_updates() {
2658 let client = new_client().unwrap();
2659 let uaid = gen_test_uaid();
2660 let chid = Uuid::parse_str(TEST_CHID).unwrap();
2661 client.remove_user(&uaid).await.unwrap();
2662
2663 let user = User {
2665 uaid,
2666 ..Default::default()
2667 };
2668 client.add_user(&user).await.unwrap();
2669
2670 client.add_channel(&uaid, &chid).await.unwrap();
2671 client
2672 .add_channel(&uaid, &uuid::Uuid::new_v4())
2673 .await
2674 .unwrap();
2675
2676 client
2677 .increment_storage(
2678 &uaid,
2679 SystemTime::now()
2680 .duration_since(SystemTime::UNIX_EPOCH)
2681 .unwrap()
2682 .as_secs(),
2683 )
2684 .await
2685 .unwrap();
2686
2687 let req = client.read_row_request(&uaid.as_simple().to_string());
2688 let Some(mut row) = client.read_row(req).await.unwrap() else {
2689 panic!("Expected row");
2690 };
2691
2692 let ca_expiry = row.take_required_cell("connected_at").unwrap().timestamp;
2695 for mut cells in row.cells.into_values() {
2696 let Some(cell) = cells.pop() else {
2697 continue;
2698 };
2699 assert!(
2700 cell.timestamp >= ca_expiry,
2701 "{} cell timestamp should >= connected_at's",
2702 cell.qualifier
2703 );
2704 }
2705
2706 let mut user = client.get_user(&uaid).await.unwrap().unwrap();
2707
2708 tokio::time::sleep(Duration::from_secs_f32(0.2)).await;
2710 client.update_user(&mut user).await.unwrap();
2711
2712 let req = client.read_row_request(&uaid.as_simple().to_string());
2714 let Some(mut row) = client.read_row(req).await.unwrap() else {
2715 panic!("Expected row");
2716 };
2717
2718 let ca_expiry2 = row.take_required_cell("connected_at").unwrap().timestamp;
2719
2720 assert!(ca_expiry2 > ca_expiry);
2721
2722 for mut cells in row.cells.into_values() {
2723 let Some(cell) = cells.pop() else {
2724 continue;
2725 };
2726 assert!(
2727 cell.timestamp >= ca_expiry2,
2728 "{} cell timestamp expiry should exceed connected_at's",
2729 cell.qualifier
2730 );
2731 }
2732
2733 client.remove_user(&uaid).await.unwrap();
2734 }
2735
2736 #[actix_rt::test]
2741 async fn expired_router_record_is_dropped() {
2742 let client = new_client().unwrap();
2743 let uaid = gen_test_uaid();
2744 let chid = Uuid::parse_str(TEST_CHID).unwrap();
2745 client.remove_user(&uaid).await.unwrap();
2746
2747 let row_key = uaid.simple().to_string();
2752 let past = SystemTime::now() - Duration::from_secs(10);
2753 let future = SystemTime::now() + Duration::from_secs(3600);
2754 let mut row = Row::new(row_key.clone());
2755 let mut cells = vec![
2756 cell::Cell {
2757 qualifier: "connected_at".to_owned(),
2758 value: 0u64.to_be_bytes().to_vec(),
2759 timestamp: past,
2760 ..Default::default()
2761 },
2762 cell::Cell {
2763 qualifier: "version".to_owned(),
2764 value: Uuid::new_v4().into_bytes().to_vec(),
2765 timestamp: future,
2766 ..Default::default()
2767 },
2768 ];
2769 cells.extend(channels_to_cells(Cow::Owned(HashSet::from([chid])), future));
2770 row.add_cells(ROUTER_FAMILY, cells);
2771 client.write_row(row).await.unwrap();
2772
2773 assert!(client.get_user(&uaid).await.unwrap().is_none());
2776
2777 let req = client.read_row_request(&row_key);
2779 assert!(client.read_row(req).await.unwrap().is_none());
2780 }
2781}