diff --git a/src/info.rs b/src/info.rs index 975fd36fb33bfb6d58a8bde78034f68abec2af49..f63201ff786aa318de728b62a6f97b10283d0b97 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,3 +1,9 @@ +use crate::instance::Grade; +use crate::instance::InstanceId; +use crate::replicaset::ReplicasetId; +use crate::traft::error::Error; +use crate::traft::node; +use crate::traft::RaftId; use std::borrow::Cow; use tarantool::proc; @@ -41,3 +47,96 @@ impl VersionInfo<'static> { pub fn proc_version_info() -> VersionInfo<'static> { VersionInfo::current() } + +//////////////////////////////////////////////////////////////////////////////// +// InstanceInfo +//////////////////////////////////////////////////////////////////////////////// + +/// Info returned from [`.proc_instance_info`]. +/// +/// [`.proc_instance_info`]: proc_instance_info +#[derive(Clone, Debug, ::serde::Serialize, ::serde::Deserialize)] +pub struct InstanceInfo { + pub raft_id: RaftId, + pub advertise_address: String, + pub instance_id: InstanceId, + pub instance_uuid: String, + pub replicaset_id: ReplicasetId, + pub replicaset_uuid: String, + pub cluster_id: String, + pub current_grade: Grade, + pub target_grade: Grade, + pub tier: String, +} + +impl tarantool::tuple::Encode for InstanceInfo {} + +impl InstanceInfo { + pub fn try_get(node: &node::Node, instance_id: Option<&InstanceId>) -> Result<Self, Error> { + let instance; + match instance_id { + None => { + let instance_id = node.raft_storage.instance_id()?; + let instance_id = + instance_id.expect("should be persisted before Node is initialized"); + instance = node.storage.instances.get(&instance_id)?; + } + Some(instance_id) => { + instance = node.storage.instances.get(instance_id)?; + } + } + + let peer_address = node + .storage + .peer_addresses + .get(instance.raft_id)? + .unwrap_or_else(|| "<unknown>".into()); + + let cluster_id = node.raft_storage.cluster_id()?; + + Ok(InstanceInfo { + raft_id: instance.raft_id, + advertise_address: peer_address, + instance_id: instance.instance_id, + instance_uuid: instance.instance_uuid, + replicaset_id: instance.replicaset_id, + replicaset_uuid: instance.replicaset_uuid, + cluster_id, + current_grade: instance.current_grade, + target_grade: instance.target_grade, + tier: instance.tier, + }) + } +} + +//////////////////////////////////////////////////////////////////////////////// +// .proc_instance_info +//////////////////////////////////////////////////////////////////////////////// + +#[proc(packed_args)] +pub fn proc_instance_info(request: InstanceInfoRequest) -> Result<InstanceInfo, Error> { + let node = node::global()?; + + let instance_id = match &request { + InstanceInfoRequest::CurrentInstance(_) => None, + InstanceInfoRequest::ByInstanceId([instance_id]) => Some(instance_id), + }; + InstanceInfo::try_get(node, instance_id) +} + +#[derive(Debug, ::serde::Deserialize, ::serde::Serialize)] +#[serde(untagged)] +enum InstanceInfoRequest { + // FIXME: this is the simplest way I found to support a single optional + // parameter to the stored procedure. We should probably do something about + // it in our custom `Encode`/`Decode` traits. + CurrentInstance([(); 0]), + ByInstanceId([InstanceId; 1]), +} + +impl ::tarantool::tuple::Encode for InstanceInfoRequest {} + +impl crate::rpc::RequestArgs for InstanceInfoRequest { + const PROC_NAME: &'static str = crate::stringify_cfunc!(proc_instance_info); + type Response = InstanceInfo; +} diff --git a/src/lib.rs b/src/lib.rs index 1a386726ea359f32e4a3b6763460abfa6bdf17a7..55b7c8778bdbfec9f49f8dffca9ee7ca8cfaa1fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::let_and_return)] #![allow(clippy::needless_return)] +#![allow(clippy::needless_late_init)] #![allow(clippy::unwrap_or_default)] #![allow(clippy::redundant_static_lifetimes)] use serde::{Deserialize, Serialize}; diff --git a/src/luamod.rs b/src/luamod.rs index 238f4ad49b6ed925315ecfd07de235b7eb696db6..baee8b54446c5806696d0507d4ba06b0df25a8d8 100644 --- a/src/luamod.rs +++ b/src/luamod.rs @@ -152,13 +152,13 @@ pub(crate) fn setup(args: &args::Run) { "}, tlua::function0(|| -> traft::Result<_> { let node = traft::node::global()?; - let raft_storage = &node.raft_storage; + let info = crate::info::InstanceInfo::try_get(node, None)?; Ok(tlua::AsTable(( - ("raft_id", raft_storage.raft_id()?), - ("cluster_id", raft_storage.cluster_id()?), - ("instance_id", raft_storage.instance_id()?), - ("tier", raft_storage.tier()?), + ("raft_id", info.raft_id), + ("cluster_id", info.cluster_id), + ("instance_id", info.instance_id), + ("tier", info.tier), ))) }), ); @@ -219,24 +219,18 @@ pub(crate) fn setup(args: &args::Run) { "}, tlua::function1(|iid: Option<InstanceId>| -> traft::Result<_> { let node = traft::node::global()?; - let iid = iid.unwrap_or(node.raft_storage.instance_id()?.unwrap()); - let instance = node.storage.instances.get(&iid)?; - let peer_address = node - .storage - .peer_addresses - .get(instance.raft_id)? - .unwrap_or_else(|| "<unknown>".into()); + let info = crate::info::InstanceInfo::try_get(node, iid.as_ref())?; Ok(tlua::AsTable(( - ("raft_id", instance.raft_id), - ("advertise_address", peer_address), - ("instance_id", instance.instance_id.0), - ("instance_uuid", instance.instance_uuid), - ("replicaset_id", instance.replicaset_id), - ("replicaset_uuid", instance.replicaset_uuid), - ("current_grade", instance.current_grade), - ("target_grade", instance.target_grade), - ("tier", instance.tier), + ("raft_id", info.raft_id), + ("advertise_address", info.advertise_address), + ("instance_id", info.instance_id.0), + ("instance_uuid", info.instance_uuid), + ("replicaset_id", info.replicaset_id), + ("replicaset_uuid", info.replicaset_uuid), + ("current_grade", info.current_grade), + ("target_grade", info.target_grade), + ("tier", info.tier), ))) }), ); diff --git a/src/traft/raft_storage.rs b/src/traft/raft_storage.rs index 212a4a03a9c63595861e2b939240b492d9e18956..4ddc8214d6ad721bf33c59600384856bd662ef77 100644 --- a/src/traft/raft_storage.rs +++ b/src/traft/raft_storage.rs @@ -94,6 +94,10 @@ impl RaftSpaceAccess { Ok(res) } + /// Returns the persisted `InstanceId` of the current instance. + /// This should be persisted before the global [`Node`] is initialized. + /// + /// [`Node`]: crate::traft::node::Node #[inline(always)] pub fn instance_id(&self) -> tarantool::Result<Option<InstanceId>> { let res = self.try_get_raft_state("instance_id")?; diff --git a/test/int/test_basics.py b/test/int/test_basics.py index 281301a48144fdbbf52e2ae43823ca01d24d16bf..0c50a7d8096989f5eb75220f4f9f35b6f8717a17 100644 --- a/test/int/test_basics.py +++ b/test/int/test_basics.py @@ -325,3 +325,51 @@ def test_governor_notices_restarts(instance: Instance): def test_proc_version_info(instance: Instance): info = instance.call(".proc_version_info") assert info.keys() == set(["picodata_version", "proc_api_version"]) # type: ignore + + +def test_proc_instance_info(cluster: Cluster): + cfg = { + "tier": { + "storage": {"replication_factor": 1}, + "router": {"replication_factor": 2}, + } + } + cluster.set_init_cfg(cfg) + + i1 = cluster.add_instance(tier="storage") + i2 = cluster.add_instance(tier="router") + + i1_info = i1.call(".proc_instance_info") + assert i1_info == dict( + raft_id=1, + advertise_address=f"{i1.host}:{i1.port}", + instance_id="i1", + instance_uuid=i1.instance_uuid(), + replicaset_id="r1", + replicaset_uuid=i1.replicaset_uuid(), + cluster_id=i1.cluster_id, + current_grade=dict(variant="Online", incarnation=1), + target_grade=dict(variant="Online", incarnation=1), + tier="storage", + ) + + info = i1.call(".proc_instance_info", "i1") + assert i1_info == info + + i2_info = i1.call(".proc_instance_info", "i2") + assert i2_info == dict( + raft_id=2, + advertise_address=f"{i2.host}:{i2.port}", + instance_id="i2", + instance_uuid=i2.instance_uuid(), + replicaset_id="r2", + replicaset_uuid=i2.replicaset_uuid(), + cluster_id=i1.cluster_id, + current_grade=dict(variant="Online", incarnation=1), + target_grade=dict(variant="Online", incarnation=1), + tier="router", + ) + + with pytest.raises(TarantoolError) as e: + i1.call(".proc_instance_info", "i3") + assert 'instance with id "i3" not found' in str(e)