diff --git a/src/main.rs b/src/main.rs index 969ed8ce29bd47df87ff82363f98de6c2258f19e..b6b7bfcd5844b8fa1bc21e36d644615f90b06177 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,6 +56,39 @@ fn picolib_setup(args: &args::Run) { luamod.set("VERSION", env!("CARGO_PKG_VERSION")); luamod.set("args", args); + luamod.set( + "whoami", + tlua::function0(|| -> Result<_, Error> { + let node = traft::node::global()?; + let raft_storage = &node.storage.raft; + + Ok(tlua::AsTable(( + ("raft_id", raft_storage.raft_id()?), + ("cluster_id", raft_storage.cluster_id()?), + ("instance_id", raft_storage.instance_id()?), + ))) + }), + ); + + luamod.set( + "peer_info", + tlua::function1(|iid: String| -> Result<_, Error> { + let node = traft::node::global()?; + let peer = node.storage.peers.get(&InstanceId::from(iid))?; + + Ok(tlua::AsTable(( + ("raft_id", peer.raft_id), + ("advertise_address", peer.peer_address), + ("instance_id", peer.instance_id.0), + ("instance_uuid", peer.instance_uuid), + ("replicaset_id", peer.replicaset_id), + ("replicaset_uuid", peer.replicaset_uuid), + ("current_grade", peer.current_grade), + ("target_grade", peer.target_grade), + ))) + }), + ); + luamod.set( "raft_status", tlua::function0(|| traft::node::global().map(|n| n.status())), diff --git a/test/int/test_basics.py b/test/int/test_basics.py index 57c8f48fb32fb6c1e9d0f2b18946216044e64441..0e199994e6e21b3d8d3131a097e2065eb96362f7 100644 --- a/test/int/test_basics.py +++ b/test/int/test_basics.py @@ -173,3 +173,26 @@ def test_graceful_stop(instance: Instance): ) with open(os.path.join(instance.data_dir, last_xlog), "rb") as f: assert f.read()[-4:] == b"\xd5\x10\xad\xed" + + +def test_whoami(instance: Instance): + assert instance.call("picolib.whoami") == { + "raft_id": 1, + "instance_id": "i1", + "cluster_id": instance.cluster_id, + } + + +def test_peer_info(instance: Instance): + def peer_info(iid: str): + return instance.call("picolib.peer_info", iid) + + # Don't compare entire structure, a couple of fields is enough + myself = peer_info("i1") + assert myself["raft_id"] == 1 + assert myself["instance_id"] == "i1" + assert myself["replicaset_id"] == "r1" + + with pytest.raises(ReturnError) as e: + peer_info("i2") + assert e.value.args == ('peer with id "i2" not found',) diff --git a/test/int/test_uninitialized.py b/test/int/test_uninitialized.py new file mode 100644 index 0000000000000000000000000000000000000000..9e16a994aaef8ef77b365ebae73ab62a82f26d1d --- /dev/null +++ b/test/int/test_uninitialized.py @@ -0,0 +1,44 @@ +import funcy # type: ignore +import pytest + +from typing import Any, Callable, Generator + +from conftest import ( + eprint, + Cluster, + Instance, + ReturnError, +) + + +@pytest.fixture +def uninitialized_instance(cluster: Cluster) -> Generator[Instance, None, None]: + """Returns a running instance that is stuck in discovery phase.""" + + # Connecting TCP/0 always results in "Connection refused" + instance = cluster.add_instance(peers=[":0"], wait_online=False) + instance.start() + + @funcy.retry(tries=30, timeout=0.2) + def wait_running(): + assert instance.eval("return box.info.status") == "running" + eprint(f"{instance} is running (but stuck in discovery phase)") + + wait_running() + yield instance + + +def test_raft_api(uninitialized_instance: Instance): + functions: list[Callable[[Instance], Any]] = [ + lambda i: i._raft_status(), + lambda i: i.call("picolib.raft_propose_nop"), + lambda i: i.call("picolib.raft_propose_info", "who cares"), + lambda i: i.call("picolib.whoami"), + lambda i: i.call("picolib.peer_info", "i1"), + lambda i: i.call("picolib.peer_info", "i2"), + ] + + for f in functions: + with pytest.raises(ReturnError) as e: + f(uninitialized_instance) + assert e.value.args == ("uninitialized yet",)