mirror of
https://github.com/logos-blockchain/logos-blockchain-testing.git
synced 2026-05-05 09:23:07 +00:00
Add local stop-node support
This commit is contained in:
parent
160dbe1078
commit
26f312cf6b
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -6569,6 +6569,7 @@ dependencies = [
|
|||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-subscriber 0.3.22",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -309,11 +309,14 @@ build_bundle::prepare_circuits() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
build_bundle::build_binaries() {
|
build_bundle::build_binaries() {
|
||||||
BUILD_FEATURES_LABEL="all"
|
BUILD_FEATURES_LABEL="all,pol-dev-mode,verification-keys"
|
||||||
echo "==> Building binaries (platform=${PLATFORM})"
|
echo "==> Building binaries (platform=${PLATFORM})"
|
||||||
mkdir -p "${NODE_SRC}"
|
mkdir -p "${NODE_SRC}"
|
||||||
(
|
(
|
||||||
cd "${NODE_SRC}"
|
cd "${NODE_SRC}"
|
||||||
|
if [ -d "${NODE_TARGET}" ]; then
|
||||||
|
rm -rf "${NODE_TARGET}"
|
||||||
|
fi
|
||||||
if [ -n "${LOGOS_BLOCKCHAIN_NODE_PATH}" ]; then
|
if [ -n "${LOGOS_BLOCKCHAIN_NODE_PATH}" ]; then
|
||||||
echo "Using local logos-blockchain-node checkout at ${NODE_SRC} (no fetch/checkout)"
|
echo "Using local logos-blockchain-node checkout at ${NODE_SRC} (no fetch/checkout)"
|
||||||
else
|
else
|
||||||
@ -326,18 +329,16 @@ build_bundle::build_binaries() {
|
|||||||
git clean -fdx
|
git clean -fdx
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${LOGOS_BLOCKCHAIN_NODE_PATH}" ]; then
|
|
||||||
build_bundle::apply_nomos_node_patches "${NODE_SRC}"
|
|
||||||
fi
|
|
||||||
unset CARGO_FEATURE_BUILD_VERIFICATION_KEY
|
|
||||||
if [ -n "${BUNDLE_RUSTUP_TOOLCHAIN}" ]; then
|
if [ -n "${BUNDLE_RUSTUP_TOOLCHAIN}" ]; then
|
||||||
RUSTFLAGS='--cfg feature="pol-dev-mode"' \
|
RUSTFLAGS='--cfg feature="pol-dev-mode" --cfg feature="build-verification-key"' \
|
||||||
|
CARGO_FEATURE_BUILD_VERIFICATION_KEY=1 \
|
||||||
RUSTUP_TOOLCHAIN="${BUNDLE_RUSTUP_TOOLCHAIN}" \
|
RUSTUP_TOOLCHAIN="${BUNDLE_RUSTUP_TOOLCHAIN}" \
|
||||||
cargo build --all-features \
|
cargo build --all-features \
|
||||||
-p logos-blockchain-node \
|
-p logos-blockchain-node \
|
||||||
--target-dir "${NODE_TARGET}"
|
--target-dir "${NODE_TARGET}"
|
||||||
else
|
else
|
||||||
RUSTFLAGS='--cfg feature="pol-dev-mode"' \
|
RUSTFLAGS='--cfg feature="pol-dev-mode" --cfg feature="build-verification-key"' \
|
||||||
|
CARGO_FEATURE_BUILD_VERIFICATION_KEY=1 \
|
||||||
cargo build --all-features \
|
cargo build --all-features \
|
||||||
-p logos-blockchain-node \
|
-p logos-blockchain-node \
|
||||||
--target-dir "${NODE_TARGET}"
|
--target-dir "${NODE_TARGET}"
|
||||||
|
|||||||
@ -60,6 +60,7 @@ Environment:
|
|||||||
LOGOS_BLOCKCHAIN_TESTNET_IMAGE_PULL_POLICY K8s imagePullPolicy (default ${DEFAULT_PULL_POLICY_LOCAL}; set to ${DEFAULT_PULL_POLICY_ECR} for --ecr)
|
LOGOS_BLOCKCHAIN_TESTNET_IMAGE_PULL_POLICY K8s imagePullPolicy (default ${DEFAULT_PULL_POLICY_LOCAL}; set to ${DEFAULT_PULL_POLICY_ECR} for --ecr)
|
||||||
LOGOS_BLOCKCHAIN_BINARIES_TAR Path to prebuilt binaries tarball (default .tmp/nomos-binaries-<platform>-<version>.tar.gz)
|
LOGOS_BLOCKCHAIN_BINARIES_TAR Path to prebuilt binaries tarball (default .tmp/nomos-binaries-<platform>-<version>.tar.gz)
|
||||||
LOGOS_BLOCKCHAIN_CIRCUITS Directory containing circuits assets (defaults to ~/.logos-blockchain-circuits)
|
LOGOS_BLOCKCHAIN_CIRCUITS Directory containing circuits assets (defaults to ~/.logos-blockchain-circuits)
|
||||||
|
CARGO_FEATURE_BUILD_VERIFICATION_KEY Build flag to embed Groth16 verification keys in node binaries (recommended for host)
|
||||||
LOGOS_BLOCKCHAIN_SKIP_IMAGE_BUILD Set to 1 to skip rebuilding the compose/k8s image
|
LOGOS_BLOCKCHAIN_SKIP_IMAGE_BUILD Set to 1 to skip rebuilding the compose/k8s image
|
||||||
LOGOS_BLOCKCHAIN_FORCE_IMAGE_BUILD Set to 1 to force image rebuild even for k8s ECR mode
|
LOGOS_BLOCKCHAIN_FORCE_IMAGE_BUILD Set to 1 to force image rebuild even for k8s ECR mode
|
||||||
LOGOS_BLOCKCHAIN_METRICS_QUERY_URL PromQL base URL for the runner process (optional)
|
LOGOS_BLOCKCHAIN_METRICS_QUERY_URL PromQL base URL for the runner process (optional)
|
||||||
@ -301,8 +302,9 @@ run_examples::bundle_matches_expected() {
|
|||||||
local tar_path="$1"
|
local tar_path="$1"
|
||||||
[ -f "${tar_path}" ] || return 1
|
[ -f "${tar_path}" ] || return 1
|
||||||
[ -z "${LOGOS_BLOCKCHAIN_NODE_REV:-}" ] && return 0
|
[ -z "${LOGOS_BLOCKCHAIN_NODE_REV:-}" ] && return 0
|
||||||
|
local expected_features="${RUN_EXAMPLES_EXPECTED_BUNDLE_FEATURES:-all,pol-dev-mode,verification-keys}"
|
||||||
|
|
||||||
local meta tar_rev tar_head
|
local meta tar_rev tar_head tar_features
|
||||||
meta="$(tar -xOzf "${tar_path}" artifacts/nomos-bundle-meta.env 2>/dev/null || true)"
|
meta="$(tar -xOzf "${tar_path}" artifacts/nomos-bundle-meta.env 2>/dev/null || true)"
|
||||||
if [ -z "${meta}" ]; then
|
if [ -z "${meta}" ]; then
|
||||||
echo "Bundle meta missing in ${tar_path}; treating as stale and rebuilding." >&2
|
echo "Bundle meta missing in ${tar_path}; treating as stale and rebuilding." >&2
|
||||||
@ -310,6 +312,11 @@ run_examples::bundle_matches_expected() {
|
|||||||
fi
|
fi
|
||||||
tar_rev="$(echo "${meta}" | sed -n 's/^nomos_node_rev=//p' | head -n 1)"
|
tar_rev="$(echo "${meta}" | sed -n 's/^nomos_node_rev=//p' | head -n 1)"
|
||||||
tar_head="$(echo "${meta}" | sed -n 's/^nomos_node_git_head=//p' | head -n 1)"
|
tar_head="$(echo "${meta}" | sed -n 's/^nomos_node_git_head=//p' | head -n 1)"
|
||||||
|
tar_features="$(echo "${meta}" | sed -n 's/^features=//p' | head -n 1)"
|
||||||
|
if [ -n "${expected_features}" ] && [ "${tar_features}" != "${expected_features}" ]; then
|
||||||
|
echo "Bundle ${tar_path} features '${tar_features}' do not match expected '${expected_features}'; rebuilding." >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
if [ -n "${tar_rev}" ] && [ "${tar_rev}" != "${LOGOS_BLOCKCHAIN_NODE_REV}" ]; then
|
if [ -n "${tar_rev}" ] && [ "${tar_rev}" != "${LOGOS_BLOCKCHAIN_NODE_REV}" ]; then
|
||||||
echo "Bundle ${tar_path} is for logos-blockchain-node rev ${tar_rev}, expected ${LOGOS_BLOCKCHAIN_NODE_REV}; rebuilding." >&2
|
echo "Bundle ${tar_path} is for logos-blockchain-node rev ${tar_rev}, expected ${LOGOS_BLOCKCHAIN_NODE_REV}; rebuilding." >&2
|
||||||
return 1
|
return 1
|
||||||
@ -501,6 +508,8 @@ run_examples::run() {
|
|||||||
|
|
||||||
if [ "${MODE}" = "host" ]; then
|
if [ "${MODE}" = "host" ]; then
|
||||||
run_examples::ensure_circuits
|
run_examples::ensure_circuits
|
||||||
|
# Ensure Groth16 verification keys are embedded when building local node binaries.
|
||||||
|
export CARGO_FEATURE_BUILD_VERIFICATION_KEY=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> Running ${BIN} for ${RUN_SECS}s (mode=${MODE}, image=${IMAGE})"
|
echo "==> Running ${BIN} for ${RUN_SECS}s (mode=${MODE}, image=${IMAGE})"
|
||||||
|
|||||||
@ -133,8 +133,18 @@ impl Node {
|
|||||||
old_pid,
|
old_pid,
|
||||||
new_pid, "node restart readiness confirmed via consensus_info"
|
new_pid, "node restart readiness confirmed via consensus_info"
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Stop the node process without restarting it.
|
||||||
|
pub async fn stop(&mut self) {
|
||||||
|
let pid = self.pid();
|
||||||
|
debug!(pid, "stopping node process");
|
||||||
|
|
||||||
|
kill_child(&mut self.handle.child);
|
||||||
|
let _ = self.wait_for_exit(RESTART_SHUTDOWN_TIMEOUT).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeConfigCommon for Config {
|
impl NodeConfigCommon for Config {
|
||||||
|
|||||||
@ -24,3 +24,6 @@ testing-framework-core = { path = "../../core" }
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
|||||||
@ -77,6 +77,10 @@ impl LocalManualCluster {
|
|||||||
Ok(self.nodes.restart_node(index).await?)
|
Ok(self.nodes.restart_node(index).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn stop_node(&self, index: usize) -> Result<(), ManualClusterError> {
|
||||||
|
Ok(self.nodes.stop_node(index).await?)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn wait_network_ready(&self) -> Result<(), ReadinessError> {
|
pub async fn wait_network_ready(&self) -> Result<(), ReadinessError> {
|
||||||
let nodes = self.nodes.readiness_nodes();
|
let nodes = self.nodes.readiness_nodes();
|
||||||
if self.is_singleton(&nodes) {
|
if self.is_singleton(&nodes) {
|
||||||
@ -110,6 +114,10 @@ impl NodeControlHandle for LocalManualCluster {
|
|||||||
.map_err(|err| err.into())
|
.map_err(|err| err.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn stop_node(&self, index: usize) -> Result<(), DynError> {
|
||||||
|
self.nodes.stop_node(index).await.map_err(|err| err.into())
|
||||||
|
}
|
||||||
|
|
||||||
async fn start_node(&self, name: &str) -> Result<StartedNode, DynError> {
|
async fn start_node(&self, name: &str) -> Result<StartedNode, DynError> {
|
||||||
self.start_node_with(name, StartNodeOptions::default())
|
self.start_node_with(name, StartNodeOptions::default())
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -170,12 +170,17 @@ impl LocalNodeManager {
|
|||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn node_pid(&self, index: usize) -> Option<u32> {
|
pub fn node_pid(&self, index: usize) -> Option<u32> {
|
||||||
let state = self
|
let mut state = self
|
||||||
.state
|
.state
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||||
|
|
||||||
state.nodes.get(index).map(|node| node.pid())
|
let node = state.nodes.get_mut(index)?;
|
||||||
|
if node.is_running() {
|
||||||
|
Some(node.pid())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop_all(&self) {
|
pub fn stop_all(&self) {
|
||||||
@ -361,6 +366,35 @@ impl LocalNodeManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn stop_node(&self, index: usize) -> Result<(), LocalNodeManagerError> {
|
||||||
|
let mut node = {
|
||||||
|
let mut state = self
|
||||||
|
.state
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||||
|
|
||||||
|
if index >= state.nodes.len() {
|
||||||
|
return Err(LocalNodeManagerError::NodeIndex { index });
|
||||||
|
}
|
||||||
|
|
||||||
|
state.nodes.remove(index)
|
||||||
|
};
|
||||||
|
|
||||||
|
node.stop().await;
|
||||||
|
|
||||||
|
let mut state = self
|
||||||
|
.state
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||||
|
|
||||||
|
if index <= state.nodes.len() {
|
||||||
|
state.nodes.insert(index, node);
|
||||||
|
} else {
|
||||||
|
state.nodes.push(node);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn spawn_and_register_node(
|
async fn spawn_and_register_node(
|
||||||
&self,
|
&self,
|
||||||
node_name: &str,
|
node_name: &str,
|
||||||
@ -416,6 +450,10 @@ impl NodeControlHandle for LocalNodeManager {
|
|||||||
self.restart_node(index).await.map_err(|err| err.into())
|
self.restart_node(index).await.map_err(|err| err.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn stop_node(&self, index: usize) -> Result<(), DynError> {
|
||||||
|
self.stop_node(index).await.map_err(|err| err.into())
|
||||||
|
}
|
||||||
|
|
||||||
async fn start_node(&self, name: &str) -> Result<StartedNode, DynError> {
|
async fn start_node(&self, name: &str) -> Result<StartedNode, DynError> {
|
||||||
self.start_node_with(name, StartNodeOptions::default())
|
self.start_node_with(name, StartNodeOptions::default())
|
||||||
.await
|
.await
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use testing_framework_core::{
|
use testing_framework_core::{
|
||||||
|
nodes::common::node::SpawnNodeError,
|
||||||
scenario::{
|
scenario::{
|
||||||
BlockFeed, BlockFeedTask, Deployer, DynError, Metrics, NodeClients, NodeControlCapability,
|
BlockFeed, BlockFeedTask, Deployer, DynError, Metrics, NodeClients, NodeControlCapability,
|
||||||
RunContext, Runner, Scenario, ScenarioError, spawn_block_feed,
|
RunContext, Runner, Scenario, ScenarioError, spawn_block_feed,
|
||||||
@ -28,7 +29,7 @@ pub enum LocalDeployerError {
|
|||||||
#[error("failed to spawn local topology: {source}")]
|
#[error("failed to spawn local topology: {source}")]
|
||||||
Spawn {
|
Spawn {
|
||||||
#[source]
|
#[source]
|
||||||
source: testing_framework_core::nodes::common::node::SpawnNodeError,
|
source: SpawnNodeError,
|
||||||
},
|
},
|
||||||
#[error("readiness probe failed: {source}")]
|
#[error("readiness probe failed: {source}")]
|
||||||
ReadinessFailed {
|
ReadinessFailed {
|
||||||
@ -105,11 +106,14 @@ impl Deployer<NodeControlCapability> for LocalDeployer {
|
|||||||
|
|
||||||
if self.membership_check {
|
if self.membership_check {
|
||||||
let topology = Topology::from_nodes(nodes);
|
let topology = Topology::from_nodes(nodes);
|
||||||
|
|
||||||
wait_for_readiness(&topology).await.map_err(|source| {
|
wait_for_readiness(&topology).await.map_err(|source| {
|
||||||
debug!(error = ?source, "local readiness failed");
|
debug!(error = ?source, "local readiness failed");
|
||||||
LocalDeployerError::ReadinessFailed { source }
|
LocalDeployerError::ReadinessFailed { source }
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
nodes = topology.into_nodes();
|
nodes = topology.into_nodes();
|
||||||
|
|
||||||
info!("local nodes are ready");
|
info!("local nodes are ready");
|
||||||
} else {
|
} else {
|
||||||
info!("skipping local membership readiness checks");
|
info!("skipping local membership readiness checks");
|
||||||
@ -120,6 +124,7 @@ impl Deployer<NodeControlCapability> for LocalDeployer {
|
|||||||
NodeClients::default(),
|
NodeClients::default(),
|
||||||
LocalNodeManagerSeed::from_topology(scenario.topology()),
|
LocalNodeManagerSeed::from_topology(scenario.topology()),
|
||||||
));
|
));
|
||||||
|
|
||||||
node_control.initialize_with_nodes(nodes);
|
node_control.initialize_with_nodes(nodes);
|
||||||
let node_clients = node_control.node_clients();
|
let node_clients = node_control.node_clients();
|
||||||
|
|
||||||
|
|||||||
@ -5,10 +5,12 @@ use testing_framework_core::{
|
|||||||
topology::config::TopologyConfig,
|
topology::config::TopologyConfig,
|
||||||
};
|
};
|
||||||
use testing_framework_runner_local::LocalDeployer;
|
use testing_framework_runner_local::LocalDeployer;
|
||||||
|
use tracing_subscriber::fmt::try_init;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[ignore = "requires local node binary and open ports"]
|
#[ignore = "requires local node binary and open ports"]
|
||||||
async fn local_restart_node() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn local_restart_node() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let _ = try_init();
|
||||||
let mut scenario = ScenarioBuilder::topology_with(|t| t.nodes(1))
|
let mut scenario = ScenarioBuilder::topology_with(|t| t.nodes(1))
|
||||||
.enable_node_control()
|
.enable_node_control()
|
||||||
.with_run_duration(Duration::from_secs(1))
|
.with_run_duration(Duration::from_secs(1))
|
||||||
@ -27,11 +29,11 @@ async fn local_restart_node() -> Result<(), Box<dyn std::error::Error + Send + S
|
|||||||
let new_pid = control.node_pid(0).ok_or("missing node pid")?;
|
let new_pid = control.node_pid(0).ok_or("missing node pid")?;
|
||||||
assert_ne!(old_pid, new_pid, "expected a new process after restart");
|
assert_ne!(old_pid, new_pid, "expected a new process after restart");
|
||||||
|
|
||||||
let client = context
|
control.stop_node(0).await?;
|
||||||
.node_clients()
|
assert!(
|
||||||
.any_client()
|
control.node_pid(0).is_none(),
|
||||||
.ok_or("no node clients available")?;
|
"expected node pid to be absent after stop"
|
||||||
client.consensus_info().await?;
|
);
|
||||||
|
|
||||||
let _handle = runner.run(&mut scenario).await?;
|
let _handle = runner.run(&mut scenario).await?;
|
||||||
|
|
||||||
@ -41,6 +43,7 @@ async fn local_restart_node() -> Result<(), Box<dyn std::error::Error + Send + S
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[ignore = "requires local node binary and open ports"]
|
#[ignore = "requires local node binary and open ports"]
|
||||||
async fn manual_cluster_restart_node() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn manual_cluster_restart_node() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let _ = try_init();
|
||||||
let deployer = LocalDeployer::default();
|
let deployer = LocalDeployer::default();
|
||||||
let cluster = deployer.manual_cluster(TopologyConfig::with_node_numbers(1))?;
|
let cluster = deployer.manual_cluster(TopologyConfig::with_node_numbers(1))?;
|
||||||
|
|
||||||
@ -53,8 +56,11 @@ async fn manual_cluster_restart_node() -> Result<(), Box<dyn std::error::Error +
|
|||||||
let new_pid = cluster.node_pid(0).ok_or("missing node pid")?;
|
let new_pid = cluster.node_pid(0).ok_or("missing node pid")?;
|
||||||
assert_ne!(old_pid, new_pid, "expected a new process after restart");
|
assert_ne!(old_pid, new_pid, "expected a new process after restart");
|
||||||
|
|
||||||
let client = cluster.node_client("node-a").ok_or("missing node client")?;
|
cluster.stop_node(0).await?;
|
||||||
client.consensus_info().await?;
|
assert!(
|
||||||
|
cluster.node_pid(0).is_none(),
|
||||||
|
"expected node pid to be absent after stop"
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user