From 500fd903cf632c5a589b606333034216c8ccdef0 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:20:27 +0100 Subject: [PATCH] test(proto): test grpc server with tenderdash in docker --- abci/Cargo.toml | 2 + abci/tests/common/docker.rs | 38 +++++++--- abci/tests/grpc.rs | 146 ++++++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 abci/tests/grpc.rs diff --git a/abci/Cargo.toml b/abci/Cargo.toml index 152126a..66bfbc0 100644 --- a/abci/Cargo.toml +++ b/abci/Cargo.toml @@ -66,3 +66,5 @@ futures = { version = "0.3.26" } tokio = { version = "1", features = ["macros", "signal", "time", "io-std"] } hex = { version = "0.4" } lazy_static = { version = "1.4" } +# For tests of gRPC server +tonic = { version = "0.11" } diff --git a/abci/tests/common/docker.rs b/abci/tests/common/docker.rs index 9d084ca..1645dc4 100644 --- a/abci/tests/common/docker.rs +++ b/abci/tests/common/docker.rs @@ -6,7 +6,8 @@ use bollard::{ Docker, API_DEFAULT_VERSION, }; use futures::StreamExt; -use tokio::{io::AsyncWriteExt, runtime::Runtime, time::timeout}; +use tenderdash_abci::ServerRuntime; +use tokio::{io::AsyncWriteExt, time::timeout}; use tracing::{debug, error, info}; use url::Url; @@ -16,7 +17,7 @@ pub struct TenderdashDocker { name: String, docker: Docker, image: String, - runtime: Runtime, + runtime: ServerRuntime, } impl TenderdashDocker { /// new() creates and starts new Tenderdash docker container for provided @@ -31,8 +32,8 @@ impl TenderdashDocker { /// /// * `tag` - Docker tag to use; provide empty string to use default /// * `app_address` - address of ABCI app server; for example, - /// `tcp://172.17.0.1:4567`, `tcp://[::ffff:ac11:1]:5678` or - /// `unix:///path/to/file` + /// `tcp://172.17.0.1:4567`, `tcp://[::ffff:ac11:1]:5678`, + /// `grpc://172.17.01:5678` or `unix:///path/to/file` pub(crate) fn new( container_name: &str, tag: Option<&str>, @@ -45,14 +46,14 @@ impl TenderdashDocker { }; let app_address = url::Url::parse(app_address).expect("invalid app address"); - if app_address.scheme() != "tcp" && app_address.scheme() != "unix" { - panic!("app_address must be either tcp:// or unix://"); + if app_address.scheme() != "tcp" + && app_address.scheme() != "unix" + && app_address.scheme() != "grpc" + { + panic!("app_address must be either grpc://, tcp:// or unix://"); } - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .expect("cannot initialize tokio runtime"); + let runtime = tenderdash_abci::ServerRuntime::default(); info!("Starting Tenderdash docker container"); @@ -152,12 +153,24 @@ impl TenderdashDocker { None }; - let app_address = app_address.to_string().replace('/', "\\/"); + let (abci, app_address) = match app_address.scheme() { + "grpc" => { + let address = app_address + .to_string() + .replace("grpc://", "") + .replace('/', "\\/"); + ("grpc", address) + }, + _ => ("socket", app_address.to_string().replace('/', "\\/")), + }; debug!("Tenderdash will connect to ABCI address: {}", app_address); let container_config = Config { image: Some(self.image.clone()), - env: Some(vec![format!("PROXY_APP={}", app_address)]), + env: Some(vec![ + format!("PROXY_APP={}", app_address), + format!("ABCI={}", abci), + ]), host_config: Some(HostConfig { binds, ..Default::default() @@ -263,6 +276,7 @@ impl Drop for TenderdashDocker { } } } + /// Use custom panic handler to dump logs on panic #[allow(dead_code)] pub fn setup_td_logs_panic(td_docker: &Arc) { diff --git a/abci/tests/grpc.rs b/abci/tests/grpc.rs new file mode 100644 index 0000000..1bf736b --- /dev/null +++ b/abci/tests/grpc.rs @@ -0,0 +1,146 @@ +//! Test gRPC server for ABCI protocol. +//! +//! This test verifies that the gRPC server generated with tonic as part of the +//! tenderdash-proto crate can successfully connect to Tenderdash instance. +//! +//! This test should be implemented in the tenderdash-proto crate; however, it +//! is implemented here to use already existing docker container testing +//! logic. +use std::sync::Arc; + +use proto::abci::{ + abci_application_server::AbciApplication, RequestEcho, RequestInfo, ResponseInfo, +}; +use tenderdash_abci::CancellationToken; +mod common; +use tenderdash_abci::proto; +use tonic::{async_trait, Response, Status}; + +#[cfg(feature = "docker-tests")] +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +/// Test server listening on ipv4 address. +/// +/// See [tcp_server_test()]. +async fn test_ipv4_server() { + // we assume the host uses default Docker network configuration, with the host + // using 172.17.0.1 + let bind_address = "172.17.0.1:1234".to_string(); + + grpc_server_test("v4", bind_address.as_str()).await; +} + +#[cfg(feature = "docker-tests")] +#[tokio::test] +/// Test server listening on ipv6 address. +/// +/// See [tcp_server_test()]. +async fn test_ipv6_server() { + // we assume the host uses default Docker network configuration, with the host + // using 172.17.0.1. This is IPv6 notation of the IPv4 address. + let bind_address = "[::ffff:ac11:1]:5678".to_string(); + + grpc_server_test("v6", bind_address.as_str()).await; +} + +#[cfg(feature = "docker-tests")] +/// Feature: ABCI App TCO server +/// +/// * Given that we have Tenderdash instance using TCP connection to communicate +/// with ABCI APP +/// * When we estabilish connection with Tenderdash +/// * Then Tenderdash sends Info request +async fn grpc_server_test(test_name: &str, bind_address: &str) { + use core::panic; + + use proto::abci::abci_application_server::AbciApplicationServer; + use tonic::transport::Server; + + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::new("debug")) + .with_ansi(true) + .try_init() + .ok(); + + let cancel = CancellationToken::new(); + let app = TestApp { + cancel: cancel.clone(), + }; + + let addr = bind_address.parse().expect("address must be valid"); + let server_cancel = cancel.clone(); + let server_handle = tokio::spawn(async move { + tracing::debug!("starting gRPC server"); + Server::builder() + .add_service(AbciApplicationServer::new(app)) + .serve_with_shutdown(addr, server_cancel.cancelled()) + .await + .expect("server failed"); + tracing::debug!("gRPC server stopped"); + }); + + let socket_uri = format!("grpc://{}", bind_address); + let container_name = format!("tenderdash_{}", test_name); + + let td = tokio::task::spawn_blocking(move || { + tracing::debug!("starting Tenderdash in Docker container"); + let td = Arc::new(common::docker::TenderdashDocker::new( + &container_name, + Some("feat-ABCI-protocol-env-var"), + &socket_uri, + )); + common::docker::setup_td_logs_panic(&td); + tracing::debug!("started Tenderdash in Docker container"); + + td + }) + .await + .expect("start tenderdash"); + + // tracing::debug!(?result, "connection handled"); + // assert!(matches!(result, Ok(()))); + tokio::select! { + _ = tokio::time::sleep(tokio::time::Duration::from_secs(60)) => { + panic!("Test timed out"); + } + _ = cancel.cancelled() => { + tracing::debug!("CancellationToken cancelled"); + } + ret = server_handle => { + ret.expect("gRPC server failed"); + } + } + + tokio::task::spawn_blocking(move || drop(td)) + .await + .expect("tenderdash cleanup"); + + tracing::info!("Test finished successfully"); +} + +pub struct TestApp { + // when test succeeds, we cancel this token to finish it + cancel: CancellationToken, +} +#[async_trait] +impl AbciApplication for TestApp { + async fn echo( + &self, + request: tonic::Request, + ) -> Result, Status> { + tracing::info!(?request, "Echo called"); + Ok(Response::new(proto::abci::ResponseEcho { + message: request.into_inner().message, + })) + } + async fn info( + &self, + _request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + tracing::info!("Info called, test successful"); + let resp = ResponseInfo { + ..Default::default() + }; + self.cancel.cancel(); + Ok(Response::new(resp)) + } +}