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 router_gc_policy_filter() -> bigtable::RowFilter {
180 bigtable::RowFilter {
181 filter: Some(bigtable::row_filter::Filter::CellsPerColumnLimitFilter(1)),
182 }
183}
184
185fn message_gc_policy_filter() -> Result<Vec<bigtable::RowFilter>, error::BigTableError> {
188 let bt_now: i64 = SystemTime::now()
189 .duration_since(SystemTime::UNIX_EPOCH)
190 .map_err(error::BigTableError::WriteTime)?
191 .as_millis() as i64;
192 let timestamp_filter = bigtable::RowFilter {
193 filter: Some(bigtable::row_filter::Filter::TimestampRangeFilter(
194 bigtable::TimestampRange {
195 start_timestamp_micros: bt_now * 1000,
196 end_timestamp_micros: 0,
197 },
198 )),
199 };
200
201 Ok(vec![router_gc_policy_filter(), timestamp_filter])
202}
203
204fn family_filter(regex: String) -> bigtable::RowFilter {
206 bigtable::RowFilter {
207 filter: Some(bigtable::row_filter::Filter::FamilyNameRegexFilter(regex)),
208 }
209}
210
211fn escape_bytes(bytes: &[u8]) -> Vec<u8> {
215 let mut vec = Vec::with_capacity(bytes.len() * 2);
216 for &b in bytes {
217 if !b.is_ascii_alphanumeric() && b != b'_' && (b & 128) == 0 {
218 if b == b'\0' {
219 vec.extend("\\x00".as_bytes());
224 continue;
225 }
226 vec.push(b'\\');
227 }
228 vec.push(b);
229 }
230 vec
231}
232
233fn version_filter(version: &Uuid) -> Vec<bigtable::RowFilter> {
236 let cq_filter = bigtable::RowFilter {
237 filter: Some(bigtable::row_filter::Filter::ColumnQualifierRegexFilter(
238 "^version$".as_bytes().to_vec(),
239 )),
240 };
241 let value_filter = bigtable::RowFilter {
242 filter: Some(bigtable::row_filter::Filter::ValueRegexFilter(
243 escape_bytes(version.as_bytes()),
244 )),
245 };
246
247 vec![
248 family_filter(format!("^{ROUTER_FAMILY}$")),
249 cq_filter,
250 value_filter,
251 ]
252}
253
254fn new_version_cell(timestamp: SystemTime) -> cell::Cell {
256 cell::Cell {
257 qualifier: "version".to_owned(),
258 value: Uuid::new_v4().into(),
259 timestamp,
260 ..Default::default()
261 }
262}
263
264fn filter_chain(filters: Vec<bigtable::RowFilter>) -> bigtable::RowFilter {
266 bigtable::RowFilter {
267 filter: Some(bigtable::row_filter::Filter::Chain(
268 bigtable::row_filter::Chain { filters },
269 )),
270 }
271}
272
273fn read_row_request(
275 table_name: &str,
276 app_profile_id: &str,
277 row_key: &str,
278) -> bigtable::ReadRowsRequest {
279 bigtable::ReadRowsRequest {
280 table_name: table_name.to_owned(),
281 app_profile_id: app_profile_id.to_owned(),
282 rows: Some(bigtable::RowSet {
283 row_keys: vec![row_key.as_bytes().to_vec()],
284 row_ranges: Vec::new(),
285 }),
286 ..Default::default()
287 }
288}
289
290fn to_u64(value: Vec<u8>, name: &str) -> Result<u64, DbError> {
291 let v: [u8; 8] = value
292 .try_into()
293 .map_err(|_| DbError::DeserializeU64(name.to_owned()))?;
294 Ok(u64::from_be_bytes(v))
295}
296
297fn to_string(value: Vec<u8>, name: &str) -> Result<String, DbError> {
298 String::from_utf8(value).map_err(|e| {
299 debug!("🉑 cannot read string {}: {:?}", name, e);
300 DbError::DeserializeString(name.to_owned())
301 })
302}
303
304fn channels_from_cells(cells: &RowCells) -> DbResult<HashSet<Uuid>> {
308 let mut result = HashSet::new();
309 for cells in cells.values() {
310 let Some(cell) = cells.last() else {
311 continue;
312 };
313 let Some((_, chid)) = cell.qualifier.split_once("chid:") else {
314 return Err(DbError::Integrity(
315 "get_channels expected: chid:<chid>".to_owned(),
316 None,
317 ));
318 };
319 result.insert(Uuid::from_str(chid).map_err(|e| DbError::General(e.to_string()))?);
320 }
321 Ok(result)
322}
323
324fn channels_to_cells(channels: Cow<HashSet<Uuid>>, expiry: SystemTime) -> Vec<cell::Cell> {
326 let channels = channels.into_owned();
327 let mut cells = Vec::with_capacity(channels.len().min(100_000));
328 for (i, channel_id) in channels.into_iter().enumerate() {
329 if i >= 100_000 {
333 break;
334 }
335 cells.push(cell::Cell {
336 qualifier: format!("chid:{}", channel_id.as_hyphenated()),
337 timestamp: expiry,
338 ..Default::default()
339 });
340 }
341 cells
342}
343
344pub fn retry_policy(max: usize) -> RetryPolicy {
345 RetryPolicy::default()
346 .with_max_retries(max)
347 .with_jitter(true)
348}
349
350#[derive(Clone, Copy, Debug, Eq, PartialEq)]
351enum RetryKind {
352 Read,
353 IdempotentWrite,
354 ConditionalWrite,
355}
356
357#[derive(Clone, Copy, Debug)]
358enum RpcClass {
359 Point,
360 Scan,
361}
362
363#[derive(Clone, Copy, Debug, Eq, PartialEq)]
364enum BreakerPolicy {
365 Track,
366 Ignore,
367}
368
369#[derive(Clone, Copy, Debug)]
370struct RpcPolicy {
371 attempt_timeout: Duration,
372 total_timeout: Duration,
373 retry_kind: RetryKind,
374 breaker: BreakerPolicy,
375}
376
377async fn rpc_attempt_until<T>(
378 deadline: tokio::time::Instant,
379 future: impl Future<Output = Result<T, error::BigTableError>>,
380) -> Result<T, error::BigTableError> {
381 tokio::time::timeout_at(deadline, future)
382 .await
383 .map_err(|_| error::BigTableError::AttemptTimeout)?
384}
385
386async fn operation_budget_until<T>(
387 deadline: tokio::time::Instant,
388 future: impl Future<Output = Result<T, error::BigTableError>>,
389) -> Result<T, error::BigTableError> {
390 tokio::time::timeout_at(deadline, future)
391 .await
392 .map_err(|_| error::BigTableError::OperationTimeout)?
393}
394
395fn retryable_pre_send_err(status: &Status) -> bool {
396 (status.code() == Code::Unknown
397 && status
398 .message()
399 .to_ascii_lowercase()
400 .starts_with("service was not ready:"))
401 || has_connect_source(status)
402}
403
404fn retryable_transient_err(status: &Status) -> bool {
405 if retryable_pre_send_err(status) {
406 return true;
407 }
408 match status.code() {
409 Code::Internal => {
410 let message = status.message().to_ascii_lowercase();
411 [
412 "rst_stream",
413 "rst stream",
414 "received unexpected eos on data frame from server",
415 ]
416 .iter()
417 .any(|fragment| message.contains(fragment))
418 }
419 Code::Unavailable | Code::DeadlineExceeded => true,
420 _ => false,
421 }
422}
423
424fn retryable_read_err(status: &Status) -> bool {
441 if retryable_transient_err(status) {
442 return true;
443 }
444 if matches!(status.code(), Code::Internal | Code::Unknown)
448 && status
449 .message()
450 .to_ascii_lowercase()
451 .starts_with("h2 protocol error:")
452 {
453 return true;
454 }
455 match status.code() {
456 Code::Cancelled => status
457 .message()
458 .eq_ignore_ascii_case("operation was canceled"),
459 Code::Unknown => has_transport_source(status),
462 _ => false,
463 }
464}
465
466fn has_transport_source(status: &Status) -> bool {
467 let mut source = StdError::source(status);
468 while let Some(error) = source {
469 if error.downcast_ref::<tonic::transport::Error>().is_some()
470 || error.downcast_ref::<std::io::Error>().is_some()
471 {
472 return true;
473 }
474 source = error.source();
475 }
476 false
477}
478
479fn has_connect_source(status: &Status) -> bool {
480 let mut source = StdError::source(status);
481 while let Some(error) = source {
482 if error.downcast_ref::<tonic::ConnectError>().is_some() {
483 return true;
484 }
485 source = error.source();
486 }
487 false
488}
489
490fn counts_toward_breaker(error: &error::BigTableError, operation_timed_out_in_rpc: bool) -> bool {
491 matches!(
492 error,
493 error::BigTableError::InvalidRowResponse(_)
494 | error::BigTableError::InvalidChunk(_)
495 | error::BigTableError::Read(_)
496 | error::BigTableError::Write(_)
497 | error::BigTableError::AttemptTimeout
498 | error::BigTableError::Status(_, _)
499 ) || matches!(error, error::BigTableError::OperationTimeout) && operation_timed_out_in_rpc
500}
501
502#[cfg(test)]
503mod retry_tests {
504 use std::convert::Infallible;
505 use std::sync::Mutex;
506 use std::sync::atomic::AtomicUsize;
507
508 use futures::StreamExt;
509 use http_body_util::StreamBody;
510 use hyper::body::{Bytes, Frame, Incoming};
511 use hyper::server::conn::http2;
512 use hyper::service::service_fn;
513 use hyper::{Request as HyperRequest, Response as HyperResponse};
514 use hyper_util::rt::{TokioExecutor, TokioIo};
515 use tokio::net::TcpListener;
516 use tokio::sync::oneshot;
517
518 use super::*;
519 #[derive(Debug, thiserror::Error)]
520 #[error("transport error")]
521 struct TestTransportError {
522 #[source]
523 source: std::io::Error,
524 }
525
526 fn broken_pipe_status() -> Status {
527 Status::from_error(Box::new(TestTransportError {
528 source: std::io::Error::from(std::io::ErrorKind::BrokenPipe),
529 }))
530 }
531
532 #[test]
533 fn retries_tonic_pre_send_readiness_errors() {
534 let status = Status::unknown("Service was not ready: transport error");
535 assert!(retryable_pre_send_err(&status));
536 }
537
538 #[test]
539 fn retries_source_backed_connect_errors_before_conditional_writes() {
540 let connect = tonic::ConnectError(Box::new(std::io::Error::from(
541 std::io::ErrorKind::ConnectionRefused,
542 )));
543 let status = Status::from_error(Box::new(connect));
544
545 assert_eq!(status.code(), Code::Unavailable);
546 assert!(retryable_pre_send_err(&status));
547 }
548
549 #[test]
550 fn does_not_retry_arbitrary_unknown_errors() {
551 let status = Status::unknown("operation outcome is unknown");
552 assert!(!retryable_pre_send_err(&status));
553 }
554
555 #[test]
556 fn retries_transient_bigtable_statuses() {
557 assert!(retryable_transient_err(&Status::unavailable(
558 "No zones were available"
559 )));
560 assert!(retryable_transient_err(&Status::deadline_exceeded(
561 "deadline exceeded"
562 )));
563 assert!(retryable_transient_err(&Status::internal(
564 "stream terminated by RST_STREAM before headers"
565 )));
566 }
567
568 #[test]
569 fn retries_observed_tonic_transport_failures_for_reads() {
570 let goaway = Status::internal("h2 protocol error: http2 error");
571 assert!(retryable_read_err(&goaway));
572 assert!(classify_retry(RetryKind::Read, &error::BigTableError::Read(goaway)).is_some());
573
574 let connection_closed = Status::cancelled("operation was canceled");
575 assert!(retryable_read_err(&connection_closed));
576 assert!(
577 classify_retry(
578 RetryKind::Read,
579 &error::BigTableError::Read(connection_closed)
580 )
581 .is_some()
582 );
583
584 let transport_error = broken_pipe_status();
588 assert_eq!(transport_error.code(), Code::Unknown);
589 assert_eq!(transport_error.message(), "transport error");
590 assert!(retryable_read_err(&transport_error));
591 assert!(
592 classify_retry(
593 RetryKind::Read,
594 &error::BigTableError::Read(transport_error)
595 )
596 .is_some()
597 );
598 }
599
600 #[test]
601 fn does_not_retry_non_transport_unknown_reads() {
602 assert!(!retryable_read_err(&Status::unknown(
605 "some application error"
606 )));
607 }
608
609 #[test]
610 fn retries_idempotent_but_not_conditional_writes() {
611 let goaway =
612 error::BigTableError::Write(Status::internal("h2 protocol error: http2 error"));
613 assert!(classify_retry(RetryKind::IdempotentWrite, &goaway).is_some());
614 let conditional_goaway =
615 error::BigTableError::Write(Status::internal("h2 protocol error: http2 error"));
616 assert!(classify_retry(RetryKind::ConditionalWrite, &conditional_goaway).is_none());
617
618 let connection_closed =
619 error::BigTableError::Write(Status::cancelled("operation was canceled"));
620 assert!(classify_retry(RetryKind::IdempotentWrite, &connection_closed).is_some());
621
622 let transport_error = error::BigTableError::Write(broken_pipe_status());
623 assert!(classify_retry(RetryKind::IdempotentWrite, &transport_error).is_some());
624
625 let unavailable = error::BigTableError::Write(Status::unavailable("try again"));
626 assert!(classify_retry(RetryKind::IdempotentWrite, &unavailable).is_some());
627 assert!(classify_retry(RetryKind::ConditionalWrite, &unavailable).is_none());
628
629 let attempt_timeout = error::BigTableError::AttemptTimeout;
630 assert!(classify_retry(RetryKind::Read, &attempt_timeout).is_some());
631 assert!(classify_retry(RetryKind::ConditionalWrite, &attempt_timeout).is_none());
632 }
633
634 #[actix_rt::test]
635 async fn attempt_and_operation_timeouts_are_enforced() {
636 let attempt = rpc_attempt_until::<()>(
637 tokio::time::Instant::now() + Duration::from_millis(1),
638 async { futures::future::pending().await },
639 )
640 .await;
641 assert!(matches!(attempt, Err(error::BigTableError::AttemptTimeout)));
642
643 let operation = operation_budget_until::<()>(
644 tokio::time::Instant::now() + Duration::from_millis(1),
645 async { futures::future::pending().await },
646 )
647 .await;
648 assert!(matches!(
649 operation,
650 Err(error::BigTableError::OperationTimeout)
651 ));
652 }
653
654 #[actix_rt::test]
655 async fn retries_a_midstream_drop_on_the_next_channel() {
656 let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
657 let address = listener.local_addr().unwrap();
658 let (drop_connection, wait_for_drop) = oneshot::channel::<()>();
659 let (finish_server, wait_for_finish) = oneshot::channel::<()>();
660
661 let server = tokio::spawn(async move {
662 let (socket, _) = listener.accept().await.unwrap();
666 let service = service_fn(|_request: HyperRequest<Incoming>| async move {
667 let first_message = Frame::data(Bytes::from_static(&[0, 0, 0, 0, 0]));
671 let body = StreamBody::new(
672 futures::stream::once(
673 async move { Ok::<Frame<Bytes>, Infallible>(first_message) },
674 )
675 .chain(futures::stream::pending::<Result<Frame<Bytes>, Infallible>>()),
676 );
677 Ok::<_, Infallible>(
678 HyperResponse::builder()
679 .status(200)
680 .header("content-type", "application/grpc")
681 .body(body)
682 .unwrap(),
683 )
684 });
685 {
686 let connection = http2::Builder::new(TokioExecutor::new())
687 .serve_connection(TokioIo::new(socket), service);
688 tokio::pin!(connection);
689 tokio::select! {
690 result = &mut connection => {
691 panic!("test HTTP/2 connection ended before it was dropped: {result:?}");
692 }
693 _ = wait_for_drop => {}
694 }
695 }
696
697 let (socket, _) = listener.accept().await.unwrap();
701 let service = service_fn(|_request: HyperRequest<Incoming>| async move {
702 let body =
705 StreamBody::new(futures::stream::empty::<Result<Frame<Bytes>, Infallible>>());
706 Ok::<_, Infallible>(
707 HyperResponse::builder()
708 .status(200)
709 .header("content-type", "application/grpc")
710 .header("grpc-status", "0")
711 .body(body)
712 .unwrap(),
713 )
714 });
715 let connection = http2::Builder::new(TokioExecutor::new())
716 .serve_connection(TokioIo::new(socket), service);
717 tokio::pin!(connection);
718 tokio::select! {
719 result = &mut connection => {
720 panic!("successful HTTP/2 connection ended unexpectedly: {result:?}");
721 }
722 _ = wait_for_finish => {}
723 }
724 });
725
726 let metrics = Arc::new(StatsdClient::builder("", cadence::NopMetricSink).build());
727 let settings = DbSettings {
728 dsn: Some(format!("grpc://localhost:{}", address.port())),
729 db_settings: json!({
730 "table_name": "projects/test/instances/test/tables/test",
731 "grpc_channel_count": 2,
732 "retry_count": 1,
733 "grpc_connect_timeout": 2,
734 "grpc_point_attempt_timeout": 5,
735 "grpc_point_total_timeout": 10
736 })
737 .to_string(),
738 };
739 let client = BigTableClientImpl::new(metrics, &settings).unwrap();
740 let drop_connection = Arc::new(Mutex::new(Some(drop_connection)));
741 let policy = client.rpc_policy(RpcClass::Point, RetryKind::Read, BreakerPolicy::Track);
742 let started = tokio::time::Instant::now();
743 tokio::time::timeout(
744 Duration::from_secs(8),
745 client.execute_rpc(
746 bigtable::ReadRowsRequest::default(),
747 policy,
748 move |channel, request| {
749 let drop_connection = drop_connection.clone();
750 async move {
751 let mut bigtable = BigtableDb::client(channel);
752 let mut stream = bigtable
753 .read_rows(request)
754 .await
755 .map_err(error::BigTableError::Read)?
756 .into_inner();
757
758 let signal = drop_connection
759 .lock()
760 .unwrap_or_else(|poisoned| poisoned.into_inner())
761 .take();
762 if let Some(signal) = signal {
763 assert!(stream.message().await.unwrap().is_some());
764 signal.send(()).unwrap();
765 }
766 while stream
767 .message()
768 .await
769 .map_err(error::BigTableError::Read)?
770 .is_some()
771 {}
772 Ok(())
773 }
774 },
775 ),
776 )
777 .await
778 .expect("Bigtable retry exceeded the test budget")
779 .expect("the second channel should complete the read");
780 assert!(
781 started.elapsed() < Duration::from_secs(5),
782 "the retry waited for the attempt deadline instead of classifying the transport drop"
783 );
784
785 finish_server.send(()).unwrap();
786 server.await.unwrap();
787 }
788
789 #[test]
790 fn default_retry_budget_is_bounded() {
791 assert_eq!(RETRY_COUNT, 2);
792 }
793
794 #[actix_rt::test]
795 async fn request_preserves_metadata_and_grpc_timeout() {
796 let db = BigtableDb::new(None);
797 let mut metadata = MetadataMap::new();
798 metadata.insert("x-goog-request-params", "table_name=test".parse().unwrap());
799
800 let request = db
801 .request(
802 bigtable::ReadRowsRequest::default(),
803 &metadata,
804 tokio::time::Instant::now() + Duration::from_secs(5),
805 )
806 .await
807 .unwrap();
808
809 assert_eq!(
810 request.metadata().get("x-goog-request-params").unwrap(),
811 "table_name=test"
812 );
813 assert!(request.metadata().get("grpc-timeout").is_some());
814 }
815
816 #[actix_rt::test]
817 async fn retry_metric_is_consumed_only_by_a_followup_attempt() {
818 let pending = Arc::new(Mutex::new(None));
819 let attempt_pending = pending.clone();
820 let condition_pending = pending.clone();
821 let attempts = Arc::new(AtomicUsize::new(0));
822 let attempt_count = attempts.clone();
823 let emitted = Arc::new(AtomicUsize::new(0));
824 let emitted_count = emitted.clone();
825
826 let result = RetryPolicy::fixed(Duration::ZERO)
827 .with_max_retries(1)
828 .retry_if(
829 || {
830 attempt_count.fetch_add(1, Ordering::Relaxed);
831 if take_pending_retry_metric(&attempt_pending).is_some() {
832 emitted_count.fetch_add(1, Ordering::Relaxed);
833 }
834 async { Err::<(), _>(error::BigTableError::AttemptTimeout) }
835 },
836 move |error: &error::BigTableError| {
837 if let Some(retry_metric) = classify_retry(RetryKind::Read, error) {
838 set_pending_retry_metric(&condition_pending, retry_metric);
839 true
840 } else {
841 false
842 }
843 },
844 )
845 .await;
846
847 assert!(matches!(result, Err(error::BigTableError::AttemptTimeout)));
848 assert_eq!(attempts.load(Ordering::Relaxed), 2);
849 assert_eq!(emitted.load(Ordering::Relaxed), 1);
850 }
851
852 #[test]
853 fn local_failures_do_not_count_toward_the_backend_breaker() {
854 assert!(!counts_toward_breaker(
855 &error::BigTableError::PreSendTimeout,
856 false,
857 ));
858 assert!(!counts_toward_breaker(
859 &error::BigTableError::CircuitBreakerOpen,
860 false,
861 ));
862 assert!(!counts_toward_breaker(
863 &error::BigTableError::OperationTimeout,
864 false,
865 ));
866 assert!(counts_toward_breaker(
867 &error::BigTableError::OperationTimeout,
868 true,
869 ));
870 assert!(counts_toward_breaker(
871 &error::BigTableError::Read(Status::unavailable("backend unavailable")),
872 false,
873 ));
874 }
875}
876
877pub fn metric(metrics: &Arc<StatsdClient>, err_type: &str, code: Option<&str>) {
878 let mut metric = metrics
879 .incr_with_tags(MetricName::DatabaseRetry)
880 .with_tag("error", err_type)
881 .with_tag("type", "bigtable");
882 if let Some(code) = code {
883 metric = metric.with_tag("code", code);
884 }
885 metric.send();
886}
887
888#[derive(Clone, Copy, Debug)]
889struct RetryMetric {
890 error: &'static str,
891 code: Option<Code>,
892}
893
894impl RetryMetric {
895 fn send(self, metrics: &Arc<StatsdClient>) {
896 let code = self.code.map(|code| format!("{code:?}"));
897 metric(metrics, self.error, code.as_deref());
898 }
899}
900
901fn take_pending_retry_metric(pending: &Mutex<Option<RetryMetric>>) -> Option<RetryMetric> {
902 pending
903 .lock()
904 .unwrap_or_else(|poisoned| poisoned.into_inner())
905 .take()
906}
907
908fn set_pending_retry_metric(pending: &Mutex<Option<RetryMetric>>, metric: RetryMetric) {
909 *pending
910 .lock()
911 .unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(metric);
912}
913
914fn classify_retry(kind: RetryKind, err: &error::BigTableError) -> Option<RetryMetric> {
917 debug!("🉑 Checking BigTableError...{err}");
918 match err {
919 error::BigTableError::InvalidRowResponse(status)
920 | error::BigTableError::Read(status)
921 | error::BigTableError::Write(status) => {
922 let retry = match kind {
923 RetryKind::Read | RetryKind::IdempotentWrite => retryable_read_err(status),
924 RetryKind::ConditionalWrite => retryable_pre_send_err(status),
925 };
926 info!("GRPC Failure: {:?}", status);
927 retry.then_some(RetryMetric {
928 error: "RpcFailure",
929 code: Some(status.code()),
930 })
931 }
932 error::BigTableError::AttemptTimeout if kind != RetryKind::ConditionalWrite => {
933 Some(RetryMetric {
934 error: "AttemptTimeout",
935 code: None,
936 })
937 }
938 error::BigTableError::PreSendTimeout => Some(RetryMetric {
942 error: "PreSendTimeout",
943 code: None,
944 }),
945 error::BigTableError::Auth(_) => Some(RetryMetric {
948 error: "Auth",
949 code: None,
950 }),
951 _ => None,
952 }
953}
954
955fn is_incomplete_router_record(cells: &RowCells) -> bool {
969 cells
970 .keys()
971 .all(|k| ["current_timestamp", "version"].contains(&k.as_str()) || k.starts_with("chid:"))
972}
973
974impl BigTableClientImpl {
999 pub fn new(metrics: Arc<StatsdClient>, settings: &DbSettings) -> DbResult<Self> {
1000 debug!("🏊 BT Pool new");
1001 let db_settings = BigTableDbSettings::try_from(settings.db_settings.as_ref())?;
1002 info!("🉑 {:#?}", db_settings);
1003 let pool = BigTablePool::new(settings, &metrics)?;
1004
1005 let metadata = db_settings.metadata()?;
1007 Ok(Self {
1008 settings: db_settings,
1009 metrics,
1010 metadata,
1011 pool,
1012 circuit_breaker: Arc::new(CircuitBreaker::default()),
1013 })
1014 }
1015
1016 fn router_ttl(&self) -> Duration {
1017 self.settings
1018 .max_router_ttl
1019 .unwrap_or(Duration::from_secs(MAX_ROUTER_TTL_SECS))
1020 }
1021
1022 fn read_row_request(&self, row_key: &str) -> bigtable::ReadRowsRequest {
1024 read_row_request(
1025 &self.settings.table_name,
1026 &self.settings.app_profile_id,
1027 row_key,
1028 )
1029 }
1030
1031 fn mutate_row_request(&self, row_key: &str) -> bigtable::MutateRowRequest {
1033 bigtable::MutateRowRequest {
1034 table_name: self.settings.table_name.clone(),
1035 app_profile_id: self.settings.app_profile_id.clone(),
1036 row_key: row_key.as_bytes().to_vec(),
1037 ..Default::default()
1038 }
1039 }
1040
1041 fn check_and_mutate_row_request(&self, row_key: &str) -> bigtable::CheckAndMutateRowRequest {
1043 bigtable::CheckAndMutateRowRequest {
1044 table_name: self.settings.table_name.clone(),
1045 app_profile_id: self.settings.app_profile_id.clone(),
1046 row_key: row_key.as_bytes().to_vec(),
1047 ..Default::default()
1048 }
1049 }
1050
1051 fn rpc_policy(
1052 &self,
1053 class: RpcClass,
1054 retry_kind: RetryKind,
1055 breaker: BreakerPolicy,
1056 ) -> RpcPolicy {
1057 let (attempt_timeout, total_timeout) = match class {
1058 RpcClass::Point => (
1059 self.settings.grpc_point_attempt_timeout,
1060 self.settings.grpc_point_total_timeout,
1061 ),
1062 RpcClass::Scan => (
1063 self.settings.grpc_scan_attempt_timeout,
1064 self.settings.grpc_scan_total_timeout,
1065 ),
1066 };
1067 RpcPolicy {
1068 attempt_timeout,
1069 total_timeout,
1070 retry_kind,
1071 breaker,
1072 }
1073 }
1074
1075 async fn execute_rpc<M, T, Call, CallFuture>(
1082 &self,
1083 message: M,
1084 policy: RpcPolicy,
1085 call: Call,
1086 ) -> Result<T, error::BigTableError>
1087 where
1088 M: Clone,
1089 Call: Fn(Channel, Request<M>) -> CallFuture + Clone,
1090 CallFuture: Future<Output = Result<T, error::BigTableError>>,
1091 {
1092 if policy.breaker == BreakerPolicy::Track && !self.circuit_breaker.allow_request() {
1093 return Err(error::BigTableError::CircuitBreakerOpen);
1094 }
1095
1096 let operation_deadline = tokio::time::Instant::now() + policy.total_timeout;
1097 let pooled = match tokio::time::timeout_at(operation_deadline, self.pool.get()).await {
1098 Ok(result) => result?,
1099 Err(_) => return Err(error::BigTableError::OperationTimeout),
1100 };
1101 let bigtable = (*pooled).clone();
1102 let metadata = self.metadata.clone();
1103 let retry_policy = retry_policy(self.settings.retry_count);
1104 let pending_retry_metric = Arc::new(Mutex::new(None));
1105 let attempt_retry_metric = pending_retry_metric.clone();
1106 let condition_retry_metric = pending_retry_metric;
1107 let metrics = self.metrics.clone();
1108 let rpc_in_flight = Arc::new(AtomicBool::new(false));
1112 let attempt_rpc_in_flight = rpc_in_flight.clone();
1113 let result = operation_budget_until(
1114 operation_deadline,
1115 retry_policy.retry_if(
1116 || {
1117 if let Some(retry_metric) = take_pending_retry_metric(&attempt_retry_metric) {
1120 retry_metric.send(&metrics);
1121 }
1122 let attempt_deadline = std::cmp::min(
1127 tokio::time::Instant::now() + policy.attempt_timeout,
1128 operation_deadline,
1129 );
1130 let bigtable = bigtable.clone();
1131 let metadata = metadata.clone();
1132 let message = message.clone();
1133 let channel = self.pool.next_channel();
1134 let call = call.clone();
1135 let rpc_in_flight = attempt_rpc_in_flight.clone();
1136 async move {
1137 let request = bigtable
1138 .request(message, &metadata, attempt_deadline)
1139 .await?;
1140 rpc_in_flight.store(true, Ordering::Relaxed);
1141 let result =
1142 rpc_attempt_until(attempt_deadline, call(channel, request)).await;
1143 rpc_in_flight.store(false, Ordering::Relaxed);
1144 result
1145 }
1146 },
1147 move |error: &error::BigTableError| {
1148 if let Some(retry_metric) = classify_retry(policy.retry_kind, error) {
1149 set_pending_retry_metric(&condition_retry_metric, retry_metric);
1150 true
1151 } else {
1152 false
1153 }
1154 },
1155 ),
1156 )
1157 .await;
1158
1159 if policy.breaker == BreakerPolicy::Track {
1160 let operation_timed_out_in_rpc = rpc_in_flight.load(Ordering::Relaxed);
1161 match &result {
1162 Ok(_) => self.circuit_breaker.record_success(),
1163 Err(error) if counts_toward_breaker(error, operation_timed_out_in_rpc) => {
1164 self.circuit_breaker.record_failure();
1165 }
1166 Err(_) => {}
1167 }
1168 }
1169 result
1170 }
1171
1172 async fn mutate_row(
1174 &self,
1175 req: bigtable::MutateRowRequest,
1176 ) -> Result<(), error::BigTableError> {
1177 let policy = self.rpc_policy(
1178 RpcClass::Point,
1179 RetryKind::IdempotentWrite,
1180 BreakerPolicy::Track,
1181 );
1182 self.execute_rpc(req, policy, |channel, request| async move {
1183 let mut client = BigtableDb::client(channel);
1184 client
1185 .mutate_row(request)
1186 .await
1187 .map_err(error::BigTableError::Write)?;
1188 Ok(())
1189 })
1190 .await
1191 }
1192
1193 async fn read_row(
1195 &self,
1196 req: bigtable::ReadRowsRequest,
1197 ) -> Result<Option<row::Row>, error::BigTableError> {
1198 let mut rows = self.read_rows_with_class(req, RpcClass::Point).await?;
1199 Ok(rows.pop_first().map(|(_, v)| v))
1200 }
1201
1202 async fn read_rows(
1206 &self,
1207 req: bigtable::ReadRowsRequest,
1208 ) -> Result<BTreeMap<RowKey, row::Row>, error::BigTableError> {
1209 self.read_rows_with_class(req, RpcClass::Scan).await
1210 }
1211
1212 async fn read_rows_with_class(
1213 &self,
1214 req: bigtable::ReadRowsRequest,
1215 class: RpcClass,
1216 ) -> Result<BTreeMap<RowKey, row::Row>, error::BigTableError> {
1217 self.read_rows_with_policy(req, class, BreakerPolicy::Track)
1218 .await
1219 }
1220
1221 async fn read_rows_with_policy(
1222 &self,
1223 req: bigtable::ReadRowsRequest,
1224 class: RpcClass,
1225 breaker: BreakerPolicy,
1226 ) -> Result<BTreeMap<RowKey, row::Row>, error::BigTableError> {
1227 let policy = self.rpc_policy(class, RetryKind::Read, breaker);
1228 self.execute_rpc(req, policy, |channel, request| async move {
1229 let mut client = BigtableDb::client(channel);
1230 let response = client
1231 .read_rows(request)
1232 .await
1233 .map_err(error::BigTableError::Read)?
1234 .into_inner();
1235 merge::RowMerger::process_chunks(response).await
1236 })
1237 .await
1238 }
1239
1240 async fn write_row(&self, row: row::Row) -> Result<(), error::BigTableError> {
1244 let mut req = self.mutate_row_request(&row.row_key);
1245 req.mutations = self.get_mutations(row.cells)?;
1250 self.mutate_row(req).await?;
1251 Ok(())
1252 }
1253
1254 fn get_mutations(
1256 &self,
1257 cells: HashMap<FamilyId, Vec<crate::db::bigtable::bigtable_client::cell::Cell>>,
1258 ) -> Result<Vec<bigtable::Mutation>, error::BigTableError> {
1259 let mut mutations = Vec::new();
1260 for (family_id, cells) in cells {
1261 for cell in cells {
1262 let timestamp = cell
1263 .timestamp
1264 .duration_since(SystemTime::UNIX_EPOCH)
1265 .map_err(error::BigTableError::WriteTime)?;
1266 debug!("🉑 expiring in {:?}", timestamp.as_millis());
1267 mutations.push(bigtable::Mutation {
1268 mutation: Some(bigtable::mutation::Mutation::SetCell(
1269 bigtable::mutation::SetCell {
1270 family_name: family_id.clone(),
1271 column_qualifier: cell.qualifier.clone().into_bytes(),
1272 timestamp_micros: (timestamp.as_millis() * 1000) as i64,
1276 value: cell.value,
1277 },
1278 )),
1279 });
1280 }
1281 }
1282 Ok(mutations)
1283 }
1284
1285 async fn check_and_mutate_row(
1293 &self,
1294 row: row::Row,
1295 filter: bigtable::RowFilter,
1296 state: bool,
1297 ) -> Result<bool, error::BigTableError> {
1298 let mut req = self.check_and_mutate_row_request(&row.row_key);
1299 let mutations = self.get_mutations(row.cells)?;
1300 req.predicate_filter = Some(filter);
1301 if state {
1302 req.true_mutations = mutations;
1303 } else {
1304 req.false_mutations = mutations;
1305 }
1306 self.check_and_mutate(req).await
1307 }
1308
1309 async fn check_and_mutate(
1310 &self,
1311 req: bigtable::CheckAndMutateRowRequest,
1312 ) -> Result<bool, error::BigTableError> {
1313 let policy = self.rpc_policy(
1314 RpcClass::Point,
1315 RetryKind::ConditionalWrite,
1316 BreakerPolicy::Track,
1317 );
1318 let predicate_matched = self
1319 .execute_rpc(req, policy, |channel, request| async move {
1320 let mut client = BigtableDb::client(channel);
1321 let response = client
1322 .check_and_mutate_row(request)
1323 .await
1324 .map_err(error::BigTableError::Write)?;
1325 Ok(response.into_inner().predicate_matched)
1326 })
1327 .await?;
1328 debug!("🉑 Predicate Matched: {predicate_matched}");
1329 Ok(predicate_matched)
1330 }
1331
1332 fn get_delete_mutations(
1333 &self,
1334 family: &str,
1335 column_names: &[&str],
1336 time_range: Option<&bigtable::TimestampRange>,
1337 ) -> Result<Vec<bigtable::Mutation>, error::BigTableError> {
1338 let mut mutations = Vec::new();
1339 for column in column_names {
1340 mutations.push(bigtable::Mutation {
1344 mutation: Some(bigtable::mutation::Mutation::DeleteFromColumn(
1345 bigtable::mutation::DeleteFromColumn {
1346 family_name: family.to_owned(),
1347 column_qualifier: column.as_bytes().to_vec(),
1348 time_range: time_range.cloned(),
1349 },
1350 )),
1351 });
1352 }
1353 Ok(mutations)
1354 }
1355
1356 async fn delete_row(&self, row_key: &str) -> Result<(), error::BigTableError> {
1358 let mut req = self.mutate_row_request(row_key);
1359 req.mutations = vec![bigtable::Mutation {
1360 mutation: Some(bigtable::mutation::Mutation::DeleteFromRow(
1361 bigtable::mutation::DeleteFromRow {},
1362 )),
1363 }];
1364 self.mutate_row(req).await
1365 }
1366
1367 fn rows_to_notifications(
1368 &self,
1369 rows: BTreeMap<String, Row>,
1370 ) -> Result<Vec<Notification>, DbError> {
1371 rows.into_iter()
1372 .map(|(row_key, row)| self.row_to_notification(&row_key, row))
1373 .collect()
1374 }
1375
1376 fn row_to_notification(&self, row_key: &str, mut row: Row) -> Result<Notification, DbError> {
1377 let Some((_, chidmessageid)) = row_key.split_once('#') else {
1378 return Err(DbError::Integrity(
1379 "rows_to_notification expected row_key: uaid:chidmessageid ".to_owned(),
1380 None,
1381 ));
1382 };
1383 let range_key = RangeKey::parse_chidmessageid(chidmessageid).map_err(|e| {
1384 DbError::Integrity(
1385 format!("rows_to_notification expected chidmessageid: {e}"),
1386 None,
1387 )
1388 })?;
1389
1390 let mut notif = Notification {
1392 channel_id: range_key.channel_id,
1393 topic: range_key.topic,
1394 sortkey_timestamp: range_key.sortkey_timestamp,
1395 version: to_string(row.take_required_cell("version")?.value, "version")?,
1396 ttl: to_u64(row.take_required_cell("ttl")?.value, "ttl")?,
1397 timestamp: to_u64(row.take_required_cell("timestamp")?.value, "timestamp")?,
1398 ..Default::default()
1399 };
1400
1401 if let Some(cell) = row.take_cell("data") {
1403 notif.data = Some(to_string(cell.value, "data")?);
1404 }
1405 #[cfg(feature = "reliable_report")]
1406 {
1407 if let Some(cell) = row.take_cell("reliability_id") {
1408 notif.reliability_id = Some(to_string(cell.value, "reliability_id")?);
1409 }
1410 if let Some(cell) = row.take_cell("reliable_state") {
1411 notif.reliable_state = Some(
1412 crate::reliability::ReliabilityState::from_str(&to_string(
1413 cell.value,
1414 "reliable_state",
1415 )?)
1416 .map_err(|e| {
1417 DbError::DeserializeString(format!("Could not parse reliable_state {e:?}"))
1418 })?,
1419 );
1420 }
1421 }
1422 if let Some(cell) = row.take_cell("headers") {
1423 notif.headers = Some(
1424 serde_json::from_str::<HashMap<String, String>>(&to_string(cell.value, "headers")?)
1425 .map_err(|e| DbError::Serialization(e.to_string()))?,
1426 );
1427 }
1428 #[cfg(feature = "reliable_report")]
1429 if let Some(cell) = row.take_cell("reliability_id") {
1430 trace!("🚣 Is reliable");
1431 notif.reliability_id = Some(to_string(cell.value, "reliability_id")?);
1432 }
1433
1434 trace!("🚣 Deserialized message row: {:?}", ¬if);
1435 Ok(notif)
1436 }
1437
1438 fn user_to_row(&self, user: &User, version: &Uuid) -> Row {
1444 let row_key = user.uaid.simple().to_string();
1445 let mut row = Row::new(row_key);
1446 let expiry = std::time::SystemTime::now() + self.router_ttl();
1447
1448 let mut cells: Vec<cell::Cell> = vec![
1449 cell::Cell {
1450 qualifier: "connected_at".to_owned(),
1451 value: user.connected_at.to_be_bytes().to_vec(),
1452 timestamp: expiry,
1453 ..Default::default()
1454 },
1455 cell::Cell {
1456 qualifier: "router_type".to_owned(),
1457 value: user.router_type.clone().into_bytes(),
1458 timestamp: expiry,
1459 ..Default::default()
1460 },
1461 cell::Cell {
1462 qualifier: "record_version".to_owned(),
1463 value: user
1464 .record_version
1465 .unwrap_or(USER_RECORD_VERSION)
1466 .to_be_bytes()
1467 .to_vec(),
1468 timestamp: expiry,
1469 ..Default::default()
1470 },
1471 cell::Cell {
1472 qualifier: "version".to_owned(),
1473 value: (*version).into(),
1474 timestamp: expiry,
1475 ..Default::default()
1476 },
1477 ];
1478
1479 if let Some(router_data) = &user.router_data {
1480 cells.push(cell::Cell {
1481 qualifier: "router_data".to_owned(),
1482 value: json!(router_data).to_string().as_bytes().to_vec(),
1483 timestamp: expiry,
1484 ..Default::default()
1485 });
1486 };
1487 if let Some(current_timestamp) = user.current_timestamp {
1488 cells.push(cell::Cell {
1489 qualifier: "current_timestamp".to_owned(),
1490 value: current_timestamp.to_be_bytes().to_vec(),
1491 timestamp: expiry,
1492 ..Default::default()
1493 });
1494 };
1495 if let Some(node_id) = &user.node_id {
1496 cells.push(cell::Cell {
1497 qualifier: "node_id".to_owned(),
1498 value: node_id.as_bytes().to_vec(),
1499 timestamp: expiry,
1500 ..Default::default()
1501 });
1502 };
1503
1504 cells.extend(channels_to_cells(
1505 Cow::Borrowed(&user.priv_channels),
1506 expiry,
1507 ));
1508
1509 row.add_cells(ROUTER_FAMILY, cells);
1510 row
1511 }
1512}
1513
1514#[derive(Clone)]
1515pub struct BigtableDb {
1516 auth_provider: Option<Arc<dyn TokenProvider>>,
1520}
1521
1522impl BigtableDb {
1523 pub fn new(auth_provider: Option<Arc<dyn TokenProvider>>) -> Self {
1524 Self { auth_provider }
1525 }
1526
1527 pub(super) fn client(channel: Channel) -> BigtableClient<Channel> {
1528 BigtableClient::new(channel)
1529 .max_decoding_message_size(MAX_MESSAGE_LEN)
1530 .max_encoding_message_size(MAX_MESSAGE_LEN)
1531 }
1532
1533 pub(super) async fn request<T>(
1544 &self,
1545 msg: T,
1546 metadata: &MetadataMap,
1547 deadline: tokio::time::Instant,
1548 ) -> Result<Request<T>, error::BigTableError> {
1549 let authorization = if let Some(provider) = &self.auth_provider {
1550 let token = tokio::time::timeout_at(deadline, provider.token(BIGTABLE_DATA_SCOPES))
1551 .await
1552 .map_err(|_| error::BigTableError::PreSendTimeout)?
1553 .map_err(error::BigTableError::Auth)?;
1554 Some(
1555 format!("Bearer {}", token.as_str())
1556 .parse::<AsciiMetadataValue>()
1557 .map_err(|e| {
1558 error::BigTableError::Config(format!("Invalid auth token: {e}"))
1559 })?,
1560 )
1561 } else {
1562 None
1563 };
1564
1565 let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
1566 if remaining.is_zero() {
1567 return Err(error::BigTableError::PreSendTimeout);
1568 }
1569
1570 let mut request = Request::new(msg);
1571 *request.metadata_mut() = metadata.clone();
1572 request.set_timeout(remaining);
1575 if let Some(value) = authorization {
1576 request.metadata_mut().insert("authorization", value);
1577 }
1578 Ok(request)
1579 }
1580}
1581
1582#[async_trait]
1583impl DbClient for BigTableClientImpl {
1584 async fn add_user(&self, user: &User) -> DbResult<()> {
1586 trace!("🉑 Adding user");
1587 let Some(ref version) = user.version else {
1588 return Err(DbError::General(
1589 "add_user expected a user version field".to_owned(),
1590 ));
1591 };
1592 let row = self.user_to_row(user, version);
1593
1594 let row_key_filter = bigtable::RowFilter {
1596 filter: Some(bigtable::row_filter::Filter::RowKeyRegexFilter(
1597 format!("^{}$", row.row_key).into_bytes(),
1598 )),
1599 };
1600 let filter = filter_chain(vec![router_gc_policy_filter(), row_key_filter]);
1601
1602 if self.check_and_mutate_row(row, filter, false).await? {
1603 return Err(DbError::Conditional);
1604 }
1605 Ok(())
1606 }
1607
1608 async fn update_user(&self, user: &mut User) -> DbResult<bool> {
1624 let Some(ref version) = user.version else {
1625 return Err(DbError::General(
1626 "update_user expected a user version field".to_owned(),
1627 ));
1628 };
1629
1630 let mut filters = vec![router_gc_policy_filter()];
1631 filters.extend(version_filter(version));
1632 let filter = filter_chain(filters);
1633
1634 let new_version = Uuid::new_v4();
1635 let row = self.user_to_row(user, &new_version);
1637
1638 let predicate_matched = self.check_and_mutate_row(row, filter, true).await?;
1639 user.version = Some(new_version);
1640 Ok(predicate_matched)
1641 }
1642
1643 async fn get_user(&self, uaid: &Uuid) -> DbResult<Option<User>> {
1644 let row_key = uaid.as_simple().to_string();
1645 let mut req = self.read_row_request(&row_key);
1646 let mut filters = vec![router_gc_policy_filter()];
1647 filters.push(family_filter(format!("^{ROUTER_FAMILY}$")));
1648 req.filter = Some(filter_chain(filters));
1649 let Some(mut row) = self.read_row(req).await? else {
1650 return Ok(None);
1651 };
1652
1653 trace!("🉑 Found a record for {}", row_key);
1654
1655 let connected_at_cell = match row.take_required_cell("connected_at") {
1656 Ok(cell) => cell,
1657 Err(_) => {
1658 if !is_incomplete_router_record(&row.cells) {
1659 return Err(DbError::Integrity(
1660 "Expected column: connected_at".to_owned(),
1661 Some(format!("{row:#?}")),
1662 ));
1663 }
1664 trace!("🉑 Dropping an incomplete user record for {}", row_key);
1669 self.metrics
1670 .incr_with_tags(MetricName::DatabaseDropUser)
1671 .with_tag("reason", "incomplete_record")
1672 .send();
1673 self.remove_user(uaid).await?;
1674 return Ok(None);
1675 }
1676 };
1677
1678 let mut result = User {
1679 uaid: *uaid,
1680 connected_at: to_u64(connected_at_cell.value, "connected_at")?,
1681 router_type: to_string(row.take_required_cell("router_type")?.value, "router_type")?,
1682 record_version: Some(to_u64(
1683 row.take_required_cell("record_version")?.value,
1684 "record_version",
1685 )?),
1686 version: Some(
1687 row.take_required_cell("version")?
1688 .value
1689 .try_into()
1690 .map_err(|e| {
1691 DbError::Serialization(format!("Could not deserialize version: {e:?}"))
1692 })?,
1693 ),
1694 ..Default::default()
1695 };
1696
1697 if let Some(cell) = row.take_cell("router_data") {
1698 result.router_data = from_str(&to_string(cell.value, "router_type")?).map_err(|e| {
1699 DbError::Serialization(format!("Could not deserialize router_type: {e:?}"))
1700 })?;
1701 }
1702
1703 if let Some(cell) = row.take_cell("node_id") {
1704 result.node_id = Some(to_string(cell.value, "node_id")?);
1705 }
1706
1707 if let Some(cell) = row.take_cell("current_timestamp") {
1708 result.current_timestamp = Some(to_u64(cell.value, "current_timestamp")?)
1709 }
1710
1711 result.priv_channels = channels_from_cells(&row.cells)?;
1713
1714 Ok(Some(result))
1715 }
1716
1717 async fn remove_user(&self, uaid: &Uuid) -> DbResult<()> {
1718 let row_key = uaid.simple().to_string();
1719 self.delete_row(&row_key).await?;
1720 Ok(())
1721 }
1722
1723 async fn add_channel(&self, uaid: &Uuid, channel_id: &Uuid) -> DbResult<()> {
1724 let channels = HashSet::from_iter([channel_id.to_owned()]);
1725 self.add_channels(uaid, channels).await
1726 }
1727
1728 async fn add_channels(&self, uaid: &Uuid, channels: HashSet<Uuid>) -> DbResult<()> {
1731 let row_key = uaid.simple().to_string();
1745 let mut row = Row::new(row_key);
1746 let expiry = std::time::SystemTime::now() + self.router_ttl();
1747
1748 row.add_cells(
1752 ROUTER_FAMILY,
1753 channels_to_cells(Cow::Owned(channels), expiry),
1754 );
1755
1756 self.write_row(row).await?;
1757 Ok(())
1758 }
1759
1760 async fn get_channels(&self, uaid: &Uuid) -> DbResult<HashSet<Uuid>> {
1761 let row_key = uaid.simple().to_string();
1762 let mut req = self.read_row_request(&row_key);
1763
1764 let cq_filter = bigtable::RowFilter {
1765 filter: Some(bigtable::row_filter::Filter::ColumnQualifierRegexFilter(
1766 "^chid:.*$".as_bytes().to_vec(),
1767 )),
1768 };
1769 req.filter = Some(filter_chain(vec![
1770 router_gc_policy_filter(),
1771 family_filter(format!("^{ROUTER_FAMILY}$")),
1772 cq_filter,
1773 ]));
1774
1775 let Some(row) = self.read_row(req).await? else {
1776 return Ok(Default::default());
1777 };
1778 channels_from_cells(&row.cells)
1779 }
1780
1781 async fn remove_channel(&self, uaid: &Uuid, channel_id: &Uuid) -> DbResult<bool> {
1783 let row_key = uaid.simple().to_string();
1784 let mut req = self.check_and_mutate_row_request(&row_key);
1785
1786 let column = format!("chid:{}", channel_id.as_hyphenated());
1788 let mut mutations = self.get_delete_mutations(ROUTER_FAMILY, &[column.as_ref()], None)?;
1789
1790 let mut row = Row::new(row_key);
1792 let expiry = std::time::SystemTime::now() + self.router_ttl();
1793 row.cells
1794 .insert(ROUTER_FAMILY.to_owned(), vec![new_version_cell(expiry)]);
1795 mutations.extend(self.get_mutations(row.cells)?);
1796
1797 let cq_filter = bigtable::RowFilter {
1799 filter: Some(bigtable::row_filter::Filter::ColumnQualifierRegexFilter(
1800 format!("^{column}$").into_bytes(),
1801 )),
1802 };
1803 req.predicate_filter = Some(filter_chain(vec![router_gc_policy_filter(), cq_filter]));
1804 req.true_mutations = mutations;
1805
1806 Ok(self.check_and_mutate(req).await?)
1807 }
1808
1809 async fn remove_node_id(
1811 &self,
1812 uaid: &Uuid,
1813 _node_id: &str,
1814 _connected_at: u64,
1815 version: &Option<Uuid>,
1816 ) -> DbResult<bool> {
1817 let row_key = uaid.simple().to_string();
1818 trace!("🉑 Removing node_id for: {row_key} (version: {version:?}) ",);
1819 let Some(version) = version else {
1820 return Err(DbError::General("Expected a user version field".to_owned()));
1821 };
1822
1823 let mut req = self.check_and_mutate_row_request(&row_key);
1824
1825 let mut filters = vec![router_gc_policy_filter()];
1826 filters.extend(version_filter(version));
1827 req.predicate_filter = Some(filter_chain(filters));
1828 req.true_mutations = self.get_delete_mutations(ROUTER_FAMILY, &["node_id"], None)?;
1829
1830 Ok(self.check_and_mutate(req).await?)
1831 }
1832
1833 async fn save_message(&self, uaid: &Uuid, message: Notification) -> DbResult<()> {
1835 let row_key = format!("{}#{}", uaid.simple(), message.chidmessageid());
1836 debug!("🗄️ Saving message {} :: {:?}", &row_key, &message);
1837 trace!(
1838 "🉑 timestamp: {:?}",
1839 &message.timestamp.to_be_bytes().to_vec()
1840 );
1841 let mut row = Row::new(row_key);
1842
1843 let expiry = SystemTime::now() + Duration::from_secs(message.ttl);
1847 trace!(
1848 "🉑 Message Expiry {}",
1849 expiry
1850 .duration_since(SystemTime::UNIX_EPOCH)
1851 .unwrap_or_default()
1852 .as_millis()
1853 );
1854
1855 let mut cells: Vec<cell::Cell> = Vec::new();
1856
1857 let is_topic = message.topic.is_some();
1858 let family = if is_topic {
1859 MESSAGE_TOPIC_FAMILY
1860 } else {
1861 MESSAGE_FAMILY
1862 };
1863 cells.extend(vec![
1864 cell::Cell {
1865 qualifier: "ttl".to_owned(),
1866 value: message.ttl.to_be_bytes().to_vec(),
1867 timestamp: expiry,
1868 ..Default::default()
1869 },
1870 cell::Cell {
1871 qualifier: "timestamp".to_owned(),
1872 value: message.timestamp.to_be_bytes().to_vec(),
1873 timestamp: expiry,
1874 ..Default::default()
1875 },
1876 cell::Cell {
1877 qualifier: "version".to_owned(),
1878 value: message.version.into_bytes(),
1879 timestamp: expiry,
1880 ..Default::default()
1881 },
1882 ]);
1883 if let Some(headers) = message.headers
1884 && !headers.is_empty()
1885 {
1886 cells.push(cell::Cell {
1887 qualifier: "headers".to_owned(),
1888 value: json!(headers).to_string().into_bytes(),
1889 timestamp: expiry,
1890 ..Default::default()
1891 });
1892 }
1893 #[cfg(feature = "reliable_report")]
1894 {
1895 if let Some(reliability_id) = message.reliability_id {
1896 trace!("🔍 FOUND RELIABILITY ID: {}", reliability_id);
1897 cells.push(cell::Cell {
1898 qualifier: "reliability_id".to_owned(),
1899 value: reliability_id.into_bytes(),
1900 timestamp: expiry,
1901 ..Default::default()
1902 });
1903 }
1904 if let Some(reliable_state) = message.reliable_state {
1905 cells.push(cell::Cell {
1906 qualifier: "reliable_state".to_owned(),
1907 value: reliable_state.to_string().into_bytes(),
1908 timestamp: expiry,
1909 ..Default::default()
1910 });
1911 }
1912 }
1913 if let Some(data) = message.data {
1914 cells.push(cell::Cell {
1915 qualifier: "data".to_owned(),
1916 value: data.into_bytes(),
1917 timestamp: expiry,
1918 ..Default::default()
1919 });
1920 }
1921
1922 row.add_cells(family, cells);
1923 trace!("🉑 Adding row");
1924 self.write_row(row).await?;
1925
1926 self.metrics
1927 .incr_with_tags(MetricName::NotificationMessageStored)
1928 .with_tag("topic", &is_topic.to_string())
1929 .with_tag("database", &self.name())
1930 .send();
1931 Ok(())
1932 }
1933
1934 async fn save_messages(&self, uaid: &Uuid, messages: Vec<Notification>) -> DbResult<()> {
1939 for message in messages {
1941 self.save_message(uaid, message).await?;
1942 }
1943 Ok(())
1944 }
1945
1946 async fn increment_storage(&self, uaid: &Uuid, timestamp: u64) -> DbResult<()> {
1960 let row_key = uaid.simple().to_string();
1961 debug!(
1962 "🉑 Updating {} current_timestamp: {:?}",
1963 &row_key,
1964 timestamp.to_be_bytes().to_vec()
1965 );
1966 let expiry = std::time::SystemTime::now() + self.router_ttl();
1967 let mut row = Row::new(row_key.clone());
1968
1969 row.cells.insert(
1970 ROUTER_FAMILY.to_owned(),
1971 vec![
1972 cell::Cell {
1973 qualifier: "current_timestamp".to_owned(),
1974 value: timestamp.to_be_bytes().to_vec(),
1975 timestamp: expiry,
1976 ..Default::default()
1977 },
1978 new_version_cell(expiry),
1979 ],
1980 );
1981
1982 self.write_row(row).await?;
1983
1984 Ok(())
1985 }
1986
1987 async fn remove_message(&self, uaid: &Uuid, chidmessageid: &str) -> DbResult<()> {
1989 trace!(
1990 "🉑 attemping to delete {:?} :: {:?}",
1991 uaid.to_string(),
1992 chidmessageid
1993 );
1994 let row_key = format!("{}#{}", uaid.simple(), chidmessageid);
1995 debug!("🉑🔥 Deleting message {}", &row_key);
1996 self.delete_row(&row_key).await?;
1997 self.metrics
1998 .incr_with_tags(MetricName::NotificationMessageDeleted)
1999 .with_tag("database", &self.name())
2000 .send();
2001 Ok(())
2002 }
2003
2004 async fn fetch_topic_messages(
2006 &self,
2007 uaid: &Uuid,
2008 limit: usize,
2009 ) -> DbResult<FetchMessageResponse> {
2010 let start_key = format!("{}#01:", uaid.simple());
2011 let end_key = format!("{}#02:", uaid.simple());
2012 let mut req = bigtable::ReadRowsRequest {
2013 table_name: self.settings.table_name.clone(),
2014 app_profile_id: self.settings.app_profile_id.clone(),
2015 rows: Some(bigtable::RowSet {
2016 row_keys: Vec::new(),
2017 row_ranges: vec![bigtable::RowRange {
2018 start_key: Some(bigtable::row_range::StartKey::StartKeyOpen(
2019 start_key.into_bytes(),
2020 )),
2021 end_key: Some(bigtable::row_range::EndKey::EndKeyOpen(
2022 end_key.into_bytes(),
2023 )),
2024 }],
2025 }),
2026 ..Default::default()
2027 };
2028
2029 let mut filters = message_gc_policy_filter()?;
2030 filters.push(family_filter(format!("^{MESSAGE_TOPIC_FAMILY}$")));
2031
2032 req.filter = Some(filter_chain(filters));
2033 if limit > 0 {
2034 trace!("🉑 Setting limit to {limit}");
2035 req.rows_limit = limit as i64;
2036 }
2037 let rows = self.read_rows(req).await?;
2038 debug!(
2039 "🉑 Fetch Topic Messages. Found {} row(s) of {}",
2040 rows.len(),
2041 limit
2042 );
2043
2044 let messages = self.rows_to_notifications(rows)?;
2045
2046 Ok(FetchMessageResponse {
2050 messages,
2051 timestamp: None,
2052 })
2053 }
2054
2055 async fn fetch_timestamp_messages(
2058 &self,
2059 uaid: &Uuid,
2060 timestamp: Option<u64>,
2061 limit: usize,
2062 ) -> DbResult<FetchMessageResponse> {
2063 let start_key = if let Some(ts) = timestamp {
2064 format!("{}#02:{}z", uaid.simple(), ts)
2067 } else {
2068 format!("{}#02:", uaid.simple())
2069 };
2070 let end_key = format!("{}#03:", uaid.simple());
2071 let mut req = bigtable::ReadRowsRequest {
2072 table_name: self.settings.table_name.clone(),
2073 app_profile_id: self.settings.app_profile_id.clone(),
2074 rows: Some(bigtable::RowSet {
2075 row_keys: Vec::new(),
2076 row_ranges: vec![bigtable::RowRange {
2077 start_key: Some(bigtable::row_range::StartKey::StartKeyOpen(
2078 start_key.into_bytes(),
2079 )),
2080 end_key: Some(bigtable::row_range::EndKey::EndKeyOpen(
2081 end_key.into_bytes(),
2082 )),
2083 }],
2084 }),
2085 ..Default::default()
2086 };
2087
2088 let mut filters = message_gc_policy_filter()?;
2102 filters.push(family_filter(format!("^{MESSAGE_FAMILY}$")));
2103
2104 req.filter = Some(filter_chain(filters));
2105 if limit > 0 {
2106 req.rows_limit = limit as i64;
2107 }
2108 let rows = self.read_rows(req).await?;
2109 debug!(
2110 "🉑 Fetch Timestamp Messages ({:?}) Found {} row(s) of {}",
2111 timestamp,
2112 rows.len(),
2113 limit,
2114 );
2115
2116 let messages = self.rows_to_notifications(rows)?;
2117 let timestamp = messages.last().and_then(|m| m.sortkey_timestamp);
2119 Ok(FetchMessageResponse {
2120 messages,
2121 timestamp,
2122 })
2123 }
2124
2125 async fn health_check(&self) -> DbResult<bool> {
2126 let random_uaid = Uuid::new_v4().simple().to_string();
2129 let mut req = read_row_request(
2130 &self.settings.table_name,
2131 &self.settings.app_profile_id,
2132 &random_uaid,
2133 );
2134 req.filter = Some(bigtable::RowFilter {
2135 filter: Some(bigtable::row_filter::Filter::BlockAllFilter(true)),
2136 });
2137 self.read_rows_with_policy(req, RpcClass::Point, BreakerPolicy::Ignore)
2140 .await?;
2141 Ok(true)
2142 }
2143
2144 async fn router_table_exists(&self) -> DbResult<bool> {
2147 Ok(true)
2148 }
2149
2150 async fn message_table_exists(&self) -> DbResult<bool> {
2153 Ok(true)
2154 }
2155
2156 #[cfg(feature = "reliable_report")]
2157 async fn log_report(
2158 &self,
2159 reliability_id: &str,
2160 new_state: crate::reliability::ReliabilityState,
2161 ) -> DbResult<()> {
2162 let row_key = reliability_id.to_owned();
2163
2164 let mut row = Row::new(row_key);
2165 let expiry = SystemTime::now() + Duration::from_secs(RELIABLE_LOG_TTL.num_seconds() as u64);
2166
2167 let cells: Vec<cell::Cell> = vec![cell::Cell {
2169 qualifier: new_state.to_string(),
2170 value: crate::util::ms_since_epoch().to_be_bytes().to_vec(),
2171 timestamp: expiry,
2172 ..Default::default()
2173 }];
2174
2175 row.add_cells(RELIABLE_LOG_FAMILY, cells);
2176
2177 self.write_row(row).await?;
2178
2179 Ok(())
2180 }
2181
2182 fn box_clone(&self) -> Box<dyn DbClient> {
2183 Box::new(self.clone())
2184 }
2185
2186 fn name(&self) -> String {
2187 "Bigtable".to_owned()
2188 }
2189
2190 fn pool_status(&self) -> Option<deadpool::Status> {
2191 Some(self.pool.pool.status())
2192 }
2193
2194 fn configured_channel_count(&self) -> Option<usize> {
2195 Some(self.pool.configured_channel_count())
2196 }
2197}
2198
2199#[cfg(all(test, feature = "emulator"))]
2200mod tests {
2201
2202 use std::sync::Arc;
2207 use std::time::SystemTime;
2208
2209 use cadence::StatsdClient;
2210 use uuid;
2211
2212 use super::*;
2213 use crate::{db::DbSettings, test_support::gen_test_uaid, util::ms_since_epoch};
2214
2215 const TEST_USER: &str = "DEADBEEF-0000-0000-0000-0123456789AB";
2216 const TEST_CHID: &str = "DECAFBAD-0000-0000-0000-0123456789AB";
2217 const TOPIC_CHID: &str = "DECAFBAD-1111-0000-0000-0123456789AB";
2218
2219 fn now() -> u64 {
2220 SystemTime::now()
2221 .duration_since(SystemTime::UNIX_EPOCH)
2222 .unwrap()
2223 .as_secs()
2224 }
2225
2226 fn new_client() -> DbResult<BigTableClientImpl> {
2227 let env_dsn = format!(
2228 "grpc://{}",
2229 std::env::var("BIGTABLE_EMULATOR_HOST").unwrap_or("localhost:8080".to_owned())
2230 );
2231 let settings = DbSettings {
2232 dsn: Some(env_dsn),
2238 db_settings: json!({"table_name": "projects/test/instances/test/tables/autopush"})
2239 .to_string(),
2240 };
2241
2242 let metrics = Arc::new(StatsdClient::builder("", cadence::NopMetricSink).build());
2243
2244 BigTableClientImpl::new(metrics, &settings)
2245 }
2246
2247 #[test]
2248 fn escape_bytes_for_regex() {
2249 let b = b"hi";
2250 assert_eq!(escape_bytes(b), b.to_vec());
2251 assert_eq!(escape_bytes(b"h.*i!"), b"h\\.\\*i\\!".to_vec());
2252 let b = b"\xe2\x80\xb3";
2253 assert_eq!(escape_bytes(b), b.to_vec());
2254 let b = [b'f', b'o', b'\0', b'2', b'2', b'o'];
2256 assert_eq!(escape_bytes(&b), b"fo\\x0022o".to_vec());
2257 let b = b"\xc0";
2258 assert_eq!(escape_bytes(b), b.to_vec());
2259 assert_eq!(escape_bytes(b"\x03"), b"\\\x03".to_vec());
2260 }
2261
2262 #[actix_rt::test]
2263 async fn health_check() {
2264 let client = new_client().unwrap();
2265
2266 let result = client.health_check().await;
2267 assert!(result.is_ok());
2268 assert!(result.unwrap());
2269 }
2270
2271 #[actix_rt::test]
2274 async fn timestamp_granularity_rejects_sub_millisecond_micros() {
2275 let client = new_client().unwrap();
2276 let uaid = gen_test_uaid();
2277 let row_key = uaid.simple().to_string();
2278 let _ = client.remove_user(&uaid).await;
2279
2280 let mut req = client.mutate_row_request(&row_key);
2281 req.mutations = vec![bigtable::Mutation {
2282 mutation: Some(bigtable::mutation::Mutation::SetCell(
2283 bigtable::mutation::SetCell {
2284 family_name: ROUTER_FAMILY.to_owned(),
2285 column_qualifier: b"granularity_probe".to_vec(),
2286 timestamp_micros: 1,
2287 value: b"x".to_vec(),
2288 },
2289 )),
2290 }];
2291
2292 assert!(
2293 client.mutate_row(req).await.is_err(),
2294 "expected granularity mismatch for timestamp_micros=1"
2295 );
2296
2297 let _ = client.remove_user(&uaid).await;
2298 }
2299
2300 #[actix_rt::test]
2303 async fn run_gauntlet() -> DbResult<()> {
2304 let client = new_client()?;
2305
2306 let connected_at = ms_since_epoch();
2307
2308 let uaid = Uuid::parse_str(TEST_USER).unwrap();
2309 let chid = Uuid::parse_str(TEST_CHID).unwrap();
2310 let topic_chid = Uuid::parse_str(TOPIC_CHID).unwrap();
2311
2312 let node_id = "test_node".to_owned();
2313
2314 let _ = client.remove_user(&uaid).await;
2316
2317 let test_user = User {
2318 uaid,
2319 router_type: "webpush".to_owned(),
2320 connected_at,
2321 router_data: None,
2322 node_id: Some(node_id.clone()),
2323 ..Default::default()
2324 };
2325
2326 let _ = client.remove_user(&uaid).await;
2329
2330 client.add_user(&test_user).await?;
2332 let fetched = client.get_user(&uaid).await?;
2333 assert!(fetched.is_some());
2334 let fetched = fetched.unwrap();
2335 assert_eq!(fetched.router_type, "webpush".to_owned());
2336
2337 let connected_at = ms_since_epoch();
2339
2340 client.add_channel(&uaid, &chid).await?;
2342 let channels = client.get_channels(&uaid).await?;
2343 assert!(channels.contains(&chid));
2344
2345 let mut new_channels: HashSet<Uuid> = HashSet::new();
2347 new_channels.insert(chid);
2348 for _ in 1..10 {
2349 new_channels.insert(uuid::Uuid::new_v4());
2350 }
2351 let chid_to_remove = uuid::Uuid::new_v4();
2352 new_channels.insert(chid_to_remove);
2353 client.add_channels(&uaid, new_channels.clone()).await?;
2354 let channels = client.get_channels(&uaid).await?;
2355 assert_eq!(channels, new_channels);
2356
2357 assert!(client.remove_channel(&uaid, &chid_to_remove).await?);
2359 assert!(!client.remove_channel(&uaid, &chid_to_remove).await?);
2360 new_channels.remove(&chid_to_remove);
2361 let channels = client.get_channels(&uaid).await?;
2362 assert_eq!(channels, new_channels);
2363
2364 let mut updated = User {
2368 connected_at,
2369 ..test_user.clone()
2370 };
2371 let result = client.update_user(&mut updated).await;
2372 assert!(result.is_ok());
2373 assert!(!result.unwrap());
2374
2375 let fetched2 = client.get_user(&fetched.uaid).await?.unwrap();
2377 assert_eq!(fetched.connected_at, fetched2.connected_at);
2378
2379 let mut updated = User {
2381 connected_at: fetched.connected_at + 300,
2382 ..fetched2
2383 };
2384 let result = client.update_user(&mut updated).await;
2385 assert!(result.is_ok());
2386 assert!(result.unwrap());
2387 assert_ne!(
2388 fetched2.connected_at,
2389 client.get_user(&uaid).await?.unwrap().connected_at
2390 );
2391
2392 client
2394 .increment_storage(
2395 &fetched.uaid,
2396 SystemTime::now()
2397 .duration_since(SystemTime::UNIX_EPOCH)
2398 .unwrap()
2399 .as_secs(),
2400 )
2401 .await?;
2402
2403 let test_data = "An_encrypted_pile_of_crap".to_owned();
2404 let timestamp = now();
2405 let sort_key = now();
2406 let test_notification = crate::db::Notification {
2408 channel_id: chid,
2409 version: "test".to_owned(),
2410 ttl: 300,
2411 timestamp,
2412 data: Some(test_data.clone()),
2413 sortkey_timestamp: Some(sort_key),
2414 ..Default::default()
2415 };
2416 let res = client.save_message(&uaid, test_notification.clone()).await;
2417 assert!(res.is_ok());
2418
2419 let mut fetched = client.fetch_timestamp_messages(&uaid, None, 999).await?;
2420 assert_ne!(fetched.messages.len(), 0);
2421 let fm = fetched.messages.pop().unwrap();
2422 assert_eq!(fm.channel_id, test_notification.channel_id);
2423 assert_eq!(fm.data, Some(test_data));
2424
2425 let fetched = client
2427 .fetch_timestamp_messages(&uaid, Some(timestamp - 10), 999)
2428 .await?;
2429 assert_ne!(fetched.messages.len(), 0);
2430
2431 let fetched = client
2433 .fetch_timestamp_messages(&uaid, Some(timestamp + 10), 999)
2434 .await?;
2435 assert_eq!(fetched.messages.len(), 0);
2436
2437 assert!(
2439 client
2440 .remove_message(&uaid, &test_notification.chidmessageid())
2441 .await
2442 .is_ok()
2443 );
2444
2445 assert!(client.remove_channel(&uaid, &chid).await.is_ok());
2446
2447 client.add_channel(&uaid, &topic_chid).await?;
2449 let test_data = "An_encrypted_pile_of_crap_with_a_topic".to_owned();
2450 let timestamp = now();
2451 let sort_key = now();
2452 let test_notification = crate::db::Notification {
2454 channel_id: topic_chid,
2455 version: "test".to_owned(),
2456 ttl: 300,
2457 topic: Some("topic".to_owned()),
2458 timestamp,
2459 data: Some(test_data.clone()),
2460 sortkey_timestamp: Some(sort_key),
2461 ..Default::default()
2462 };
2463 assert!(
2464 client
2465 .save_message(&uaid, test_notification.clone())
2466 .await
2467 .is_ok()
2468 );
2469
2470 let mut fetched = client.fetch_topic_messages(&uaid, 999).await?;
2471 assert_ne!(fetched.messages.len(), 0);
2472 let fm = fetched.messages.pop().unwrap();
2473 assert_eq!(fm.channel_id, test_notification.channel_id);
2474 assert_eq!(fm.data, Some(test_data));
2475
2476 let fetched = client.fetch_topic_messages(&uaid, 999).await?;
2478 assert_ne!(fetched.messages.len(), 0);
2479
2480 assert!(
2482 client
2483 .remove_message(&uaid, &test_notification.chidmessageid())
2484 .await
2485 .is_ok()
2486 );
2487
2488 assert!(client.remove_channel(&uaid, &topic_chid).await.is_ok());
2489
2490 let msgs = client
2491 .fetch_timestamp_messages(&uaid, None, 999)
2492 .await?
2493 .messages;
2494 assert!(msgs.is_empty());
2495
2496 let fetched = client.get_user(&uaid).await?.unwrap();
2497 assert!(
2498 client
2499 .remove_node_id(&uaid, &node_id, connected_at, &fetched.version)
2500 .await
2501 .is_ok()
2502 );
2503 let fetched = client.get_user(&uaid).await?.unwrap();
2505 assert_eq!(fetched.node_id, None);
2506
2507 assert!(client.remove_user(&uaid).await.is_ok());
2508
2509 assert!(client.get_user(&uaid).await?.is_none());
2510
2511 Ok(())
2512 }
2513
2514 #[actix_rt::test]
2515 async fn read_cells_family_id() -> DbResult<()> {
2516 let client = new_client().unwrap();
2517 let uaid = gen_test_uaid();
2518 client.remove_user(&uaid).await.unwrap();
2519
2520 let qualifier = "foo".to_owned();
2521
2522 let row_key = uaid.simple().to_string();
2523 let mut row = Row::new(row_key.clone());
2524 row.cells.insert(
2525 ROUTER_FAMILY.to_owned(),
2526 vec![cell::Cell {
2527 qualifier: qualifier.to_owned(),
2528 value: "bar".as_bytes().to_vec(),
2529 ..Default::default()
2530 }],
2531 );
2532 client.write_row(row).await.unwrap();
2533 let req = client.read_row_request(&row_key);
2534 let Some(row) = client.read_row(req).await.unwrap() else {
2535 panic!("Expected row");
2536 };
2537 assert_eq!(row.cells.len(), 1);
2538 assert_eq!(row.cells.keys().next().unwrap(), qualifier.as_str());
2539 client.remove_user(&uaid).await
2540 }
2541
2542 #[actix_rt::test]
2543 async fn add_user_existing() {
2544 let client = new_client().unwrap();
2545 let uaid = gen_test_uaid();
2546 let user = User {
2547 uaid,
2548 ..Default::default()
2549 };
2550 client.remove_user(&uaid).await.unwrap();
2551
2552 client.add_user(&user).await.unwrap();
2553 let err = client.add_user(&user).await.unwrap_err();
2554 assert!(matches!(err, DbError::Conditional));
2555 }
2556
2557 #[actix_rt::test]
2558 async fn version_check() {
2559 let client = new_client().unwrap();
2560 let uaid = gen_test_uaid();
2561 let user = User {
2562 uaid,
2563 ..Default::default()
2564 };
2565 client.remove_user(&uaid).await.unwrap();
2566
2567 client.add_user(&user).await.unwrap();
2568 let mut user = client.get_user(&uaid).await.unwrap().unwrap();
2569 assert!(client.update_user(&mut user.clone()).await.unwrap());
2570
2571 let fetched = client.get_user(&uaid).await.unwrap().unwrap();
2572 assert_ne!(user.version, fetched.version);
2573 assert!(!client.update_user(&mut user).await.unwrap());
2575
2576 client.remove_user(&uaid).await.unwrap();
2577 }
2578
2579 #[actix_rt::test]
2580 async fn lingering_chid_record() {
2581 let client = new_client().unwrap();
2582 let uaid = gen_test_uaid();
2583 let chid = Uuid::parse_str(TEST_CHID).unwrap();
2584 let user = User {
2585 uaid,
2586 ..Default::default()
2587 };
2588 client.remove_user(&uaid).await.unwrap();
2589
2590 client.add_channel(&uaid, &chid).await.unwrap();
2592
2593 assert!(client.get_user(&uaid).await.unwrap().is_none());
2596
2597 client.add_user(&user).await.unwrap();
2598 assert!(client.get_channels(&uaid).await.unwrap().is_empty());
2600
2601 client.remove_user(&uaid).await.unwrap();
2602 }
2603
2604 #[actix_rt::test]
2605 async fn lingering_current_timestamp() {
2606 let client = new_client().unwrap();
2607 let uaid = gen_test_uaid();
2608 client.remove_user(&uaid).await.unwrap();
2609
2610 client
2611 .increment_storage(&uaid, crate::util::sec_since_epoch())
2612 .await
2613 .unwrap();
2614 assert!(client.get_user(&uaid).await.unwrap().is_none());
2615
2616 client.remove_user(&uaid).await.unwrap();
2617 }
2618
2619 #[actix_rt::test]
2620 async fn lingering_chid_w_version_record() {
2621 let client = new_client().unwrap();
2622 let uaid = gen_test_uaid();
2623 let chid = Uuid::parse_str(TEST_CHID).unwrap();
2624 client.remove_user(&uaid).await.unwrap();
2625
2626 client.add_channel(&uaid, &chid).await.unwrap();
2627 assert!(client.remove_channel(&uaid, &chid).await.unwrap());
2628 assert!(client.get_user(&uaid).await.unwrap().is_none());
2629
2630 client.remove_user(&uaid).await.unwrap();
2631 }
2632
2633 #[actix_rt::test]
2634 async fn channel_and_current_timestamp_ttl_updates() {
2635 let client = new_client().unwrap();
2636 let uaid = gen_test_uaid();
2637 let chid = Uuid::parse_str(TEST_CHID).unwrap();
2638 client.remove_user(&uaid).await.unwrap();
2639
2640 let user = User {
2642 uaid,
2643 ..Default::default()
2644 };
2645 client.add_user(&user).await.unwrap();
2646
2647 client.add_channel(&uaid, &chid).await.unwrap();
2648 client
2649 .add_channel(&uaid, &uuid::Uuid::new_v4())
2650 .await
2651 .unwrap();
2652
2653 client
2654 .increment_storage(
2655 &uaid,
2656 SystemTime::now()
2657 .duration_since(SystemTime::UNIX_EPOCH)
2658 .unwrap()
2659 .as_secs(),
2660 )
2661 .await
2662 .unwrap();
2663
2664 let req = client.read_row_request(&uaid.as_simple().to_string());
2665 let Some(mut row) = client.read_row(req).await.unwrap() else {
2666 panic!("Expected row");
2667 };
2668
2669 let ca_expiry = row.take_required_cell("connected_at").unwrap().timestamp;
2672 for mut cells in row.cells.into_values() {
2673 let Some(cell) = cells.pop() else {
2674 continue;
2675 };
2676 assert!(
2677 cell.timestamp >= ca_expiry,
2678 "{} cell timestamp should >= connected_at's",
2679 cell.qualifier
2680 );
2681 }
2682
2683 let mut user = client.get_user(&uaid).await.unwrap().unwrap();
2684
2685 tokio::time::sleep(Duration::from_secs_f32(0.2)).await;
2687 client.update_user(&mut user).await.unwrap();
2688
2689 let req = client.read_row_request(&uaid.as_simple().to_string());
2691 let Some(mut row) = client.read_row(req).await.unwrap() else {
2692 panic!("Expected row");
2693 };
2694
2695 let ca_expiry2 = row.take_required_cell("connected_at").unwrap().timestamp;
2696
2697 assert!(ca_expiry2 > ca_expiry);
2698
2699 for mut cells in row.cells.into_values() {
2700 let Some(cell) = cells.pop() else {
2701 continue;
2702 };
2703 assert!(
2704 cell.timestamp >= ca_expiry2,
2705 "{} cell timestamp expiry should exceed connected_at's",
2706 cell.qualifier
2707 );
2708 }
2709
2710 client.remove_user(&uaid).await.unwrap();
2711 }
2712}