Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(pd): add integration test for grpc reflection #4426

Merged
merged 1 commit into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
sh -c "$(curl --location https://raw.githubusercontent.com/F1bonacc1/process-compose/main/scripts/get-pc.sh)" --
-d -b ~/bin

- name: Install grpcurl
run: ./deployments/scripts/install-grpcurl

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cratelyn N.B. I'd much rather use something like nix flakes or devbox to manage these dependencies. Not going down that rabbit hole now, but flagging that it'd be less work overall to track tooling deps in a holistic way, cf. #4264.

- name: Run the smoke test suite
run: |
export PATH="$HOME/bin:$PATH"
Expand Down
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/bin/pd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,6 @@ penumbra-proof-params = { workspace = true, features = [
"bundled-proving-keys",
"download-proving-keys",
], default-features = true }
assert_cmd = { workspace = true }
predicates = "2.1"
prost-reflect = "0.13.1"
74 changes: 74 additions & 0 deletions crates/bin/pd/tests/network_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
//! headers in all contexts. Does NOT evaluate application logic; see the
//! integration tests for pcli/pclientd for that.

use assert_cmd::Command;
use http::StatusCode;
use penumbra_proto::FILE_DESCRIPTOR_SET;
use predicates::prelude::*;
use prost_reflect::{DescriptorPool, ServiceDescriptor};
use url::Url;

#[ignore]
#[tokio::test]
Expand Down Expand Up @@ -39,3 +44,72 @@ async fn check_minifront_http_ok() -> anyhow::Result<()> {
assert_eq!(r.status(), StatusCode::OK);
Ok(())
}

#[ignore]
#[tokio::test]
/// Validate that gRPC server reflection is enabled and working, by calling out
/// to `grpcurl` and verifying that it can view methods. See GH4392 for context.
async fn check_grpc_server_reflection() -> anyhow::Result<()> {
let pd_url: Url = std::env::var("PENUMBRA_NODE_PD_URL")
.unwrap_or("http://localhost:8080".to_string())
.parse()
.unwrap();
let pd_hostname = format!("{}:{}", pd_url.host().unwrap(), pd_url.port().unwrap());
let mut args = Vec::<String>::new();
if pd_url.scheme() == "http" {
args.push("-plaintext".to_owned());
}
args.push(pd_hostname);
// grpcurl takes `list` as a command, to inspect the server reflection API.
args.push("list".to_owned());

// Permit override of the fullpath to the `grpcurl` binary, in case we want
// to test multiple versions in CI.
let grpcurl_path = std::env::var("GRPCURL_PATH").unwrap_or("grpcurl".to_string());
let std_cmd = std::process::Command::new(grpcurl_path);
let mut cmd = Command::from_std(std_cmd);
cmd.args(args);

// Here we hardcode a few specific checks, to verify they're present.
// This ensures reflection is ostensibly working, and doesn't assume
// that the FILE_DESCRIPTOR tonic-build logic is wired up.
let methods = vec![
"penumbra.core.app.v1.QueryService",
// "grpc.reflection.v1alpha.ServerReflection",
"grpc.reflection.v1.ServerReflection",
"ibc.core.channel.v1.Query",
];
for m in methods {
cmd.assert().stdout(predicate::str::contains(m));
}

// Here we look up the gRPC services exported from the proto crate,
// as FILE_DESCRIPTOR_SET. All of these methods should be visible
// to the `grpcurl` list command, if reflection is working.
let grpc_service_names = get_all_grpc_services()?;
// Sanity-check that we actually got results.
assert!(grpc_service_names.len() > 5);
for m in grpc_service_names {
cmd.assert().stdout(predicate::str::contains(m));
}
Ok(())
}

/// Returns a Vec<String> where each String is a fully qualified gRPC query service name,
/// such as:
///
/// - penumbra.core.component.community_pool.v1.QueryService
/// - penumbra.view.v1.ViewService
/// - penumbra.core.component.dex.v1.SimulationService
///
/// The gRPC service names are read from the [penumbra_proto] crate's [FILE_DESCRIPTOR_SET],
/// which is exported at build time.
fn get_all_grpc_services() -> anyhow::Result<Vec<String>> {
// Intentionally verbose to be explicit.
let services: Vec<ServiceDescriptor> = DescriptorPool::decode(FILE_DESCRIPTOR_SET)?
.services()
.into_iter()
.collect();
let service_names: Vec<String> = services.iter().map(|x| x.full_name().to_owned()).collect();
Ok(service_names)
}
34 changes: 34 additions & 0 deletions deployments/scripts/install-grpcurl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash
# Utility script to download a specific version of grpcurl for use
# in testing Penumbra, specifically in validating the gRPC reflection
# APIs via integration tests.
# Designed to be used in CI contexts, to bootstrap a testing setup quickly.
set -euo pipefail


# Sane defaults
GRPCURL_VERSION="${GRPCURL_VERSION:-1.9.1}"

# Download and extract
grpcurl_download_url="https://github.com/fullstorydev/grpcurl/releases/download/v${GRPCURL_VERSION}/grpcurl_${GRPCURL_VERSION}_linux_x86_64.tar.gz"
grpcurl_temp_dir="$(mktemp -d)"
pushd "$grpcurl_temp_dir" > /dev/null
curl -sSfL -O "$grpcurl_download_url"
tar -xzf "grpcurl_${GRPCURL_VERSION}_linux_x86_64.tar.gz" grpcurl
trap 'rm -r "$grpcurl_temp_dir"' EXIT

# Try to write to system-wide location.
if [[ -w /usr/local/bin/ ]] ; then
mv -v grpcurl /usr/local/bin/
else
grpcurl_install_dir="${HOME:?}/bin"
>&2 echo "WARNING: /usr/local/bin/ not writable, installing grpcurl to $grpcurl_install_dir"
mkdir -p "$grpcurl_install_dir"
mv -v grpcurl "${grpcurl_install_dir}/"
export PATH="$PATH:$grpcurl_install_dir"
fi

# Sanity checks
echo "Checking that grpcurl is installed:"
which grpcurl
grpcurl --version
6 changes: 6 additions & 0 deletions deployments/scripts/smoke-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ if ! hash process-compose > /dev/null 2>&1 ; then
exit 1
fi

if ! hash grpcurl > /dev/null 2>&1 ; then
>&2 echo "ERROR: grpcurl not found in PATH"
>&2 echo "Install it via https://github.com/fullstorydev/grpcurl/"
exit 1
fi

# Check for interactive terminal session, enable TUI if yes.
if [[ -t 1 ]] ; then
use_tui="true"
Expand Down
Loading