Skip to content
Snippets Groups Projects
Commit d2c9c011 authored by Georgy Moshkin's avatar Georgy Moshkin :speech_balloon:
Browse files

feat: validate configuration against storage

parent 6bbfa9c8
No related branches found
No related tags found
1 merge request!893feat: --config parameter to specify picodata configuration file
...@@ -195,6 +195,81 @@ impl PicodataConfig { ...@@ -195,6 +195,81 @@ impl PicodataConfig {
todo!() todo!()
} }
/// Does validation of configuration parameters which are persisted in the
/// storage.
pub fn validate_storage(
&self,
storage: &storage::Clusterwide,
raft_storage: &RaftSpaceAccess,
) -> Result<(), Error> {
// Cluster id
match (raft_storage.cluster_id()?, self.cluster_id()) {
(from_storage, from_config) if from_storage != from_config => {
return Err(Error::InvalidConfiguration(format!(
"instance restarted with a different `cluster_id`, which is not allowed, was: '{from_storage}' became: '{from_config}'"
)));
}
_ => {}
}
// Instance id
let mut instance_id = None;
match (raft_storage.instance_id()?, &self.instance.instance_id) {
(Some(from_storage), Some(from_config)) if from_storage != from_config => {
return Err(Error::InvalidConfiguration(format!(
"instance restarted with a different `instance_id`, which is not allowed, was: '{from_storage}' became: '{from_config}'"
)));
}
(Some(from_storage), _) => {
instance_id = Some(from_storage);
}
_ => {}
}
// Replicaset id
if let Some(instance_id) = &instance_id {
if let Ok(instance_info) = storage.instances.get(instance_id) {
match (&instance_info.replicaset_id, &self.instance.replicaset_id) {
(from_storage, Some(from_config)) if from_storage != from_config => {
return Err(Error::InvalidConfiguration(format!(
"instance restarted with a different `replicaset_id`, which is not allowed, was: '{from_storage}' became: '{from_config}'"
)));
}
_ => {}
}
}
}
// Tier
match (&raft_storage.tier()?, &self.instance.tier) {
(Some(from_storage), Some(from_config)) if from_storage != from_config => {
return Err(Error::InvalidConfiguration(format!(
"instance restarted with a different `tier`, which is not allowed, was: '{from_storage}' became: '{from_config}'"
)));
}
_ => {}
}
// Advertise address
if let Some(raft_id) = raft_storage.raft_id()? {
match (
storage.peer_addresses.get(raft_id)?,
&self.instance.advertise_address,
) {
(Some(from_storage), Some(from_config))
if from_storage != from_config.to_host_port() =>
{
return Err(Error::InvalidConfiguration(format!(
"instance restarted with a different `advertise_address`, which is not allowed, was: '{from_storage}' became: '{from_config}'"
)));
}
_ => {}
}
}
Ok(())
}
#[inline] #[inline]
pub fn cluster_id(&self) -> &str { pub fn cluster_id(&self) -> &str {
match (&self.instance.cluster_id, &self.cluster.cluster_id) { match (&self.instance.cluster_id, &self.cluster.cluster_id) {
......
...@@ -609,7 +609,6 @@ fn start_discover( ...@@ -609,7 +609,6 @@ fn start_discover(
cfg.listen = Some(config.instance.listen().to_host_port()); cfg.listen = Some(config.instance.listen().to_host_port());
tarantool::set_cfg(&cfg); tarantool::set_cfg(&cfg);
// TODO assert traft::Storage::instance_id == (null || args.instance_id)
if raft_storage.raft_id().unwrap().is_some() { if raft_storage.raft_id().unwrap().is_some() {
tarantool::set_cfg_field("read_only", true).unwrap(); tarantool::set_cfg_field("read_only", true).unwrap();
return postjoin(config, storage, raft_storage); return postjoin(config, storage, raft_storage);
...@@ -844,6 +843,8 @@ fn postjoin( ...@@ -844,6 +843,8 @@ fn postjoin(
) -> Result<(), Error> { ) -> Result<(), Error> {
tlog!(Info, "entering post-join phase"); tlog!(Info, "entering post-join phase");
config.validate_storage(&storage, &raft_storage)?;
if let Some(config) = &config.instance.audit { if let Some(config) = &config.instance.audit {
audit::init(config, &raft_storage); audit::init(config, &raft_storage);
} }
......
...@@ -168,6 +168,80 @@ def test_graceful_stop(instance: Instance): ...@@ -168,6 +168,80 @@ def test_graceful_stop(instance: Instance):
assert f.read()[-4:] == b"\xd5\x10\xad\xed" assert f.read()[-4:] == b"\xd5\x10\xad\xed"
def test_config_storage_conflicts_on_restart(instance: Instance):
instance.terminate()
#
# Change cluster_id
#
was = instance.cluster_id # type: ignore
instance.cluster_id = "new-cluster-id"
assert instance.cluster_id != was
err = f"""\
invalid configuration: instance restarted with a different `cluster_id`, which is not allowed, was: '{was}' became: 'new-cluster-id'
""" # noqa: E501
crawler = log_crawler(instance, err)
instance.fail_to_start()
assert crawler.matched
instance.cluster_id = was
#
# Change instance_id
#
was = instance.instance_id # type: ignore
instance.instance_id = "new-instance-id"
assert instance.instance_id != was
err = f"""\
invalid configuration: instance restarted with a different `instance_id`, which is not allowed, was: '{was}' became: 'new-instance-id'
""" # noqa: E501
crawler = log_crawler(instance, err)
instance.fail_to_start()
assert crawler.matched
instance.instance_id = was
#
# Change tier
#
was = instance.tier # type: ignore
instance.tier = "new-tier"
assert instance.tier != was
err = """\
invalid configuration: instance restarted with a different `tier`, which is not allowed, was: 'default' became: 'new-tier'
""" # noqa: E501
crawler = log_crawler(instance, err)
instance.fail_to_start()
assert crawler.matched
instance.tier = was
#
# Change replicaset_id
#
was = instance.replicaset_id # type: ignore
instance.replicaset_id = "new-replicaset-id"
assert instance.replicaset_id != was
err = """\
invalid configuration: instance restarted with a different `replicaset_id`, which is not allowed, was: 'r1' became: 'new-replicaset-id'
""" # noqa: E501
crawler = log_crawler(instance, err)
instance.fail_to_start()
assert crawler.matched
instance.replicaset_id = was
#
# Change advertise address
#
was = instance.listen # type: ignore
instance.env["PICODATA_ADVERTISE"] = "example.com:1234"
assert instance.env["PICODATA_ADVERTISE"] != was
err = f"""\
invalid configuration: instance restarted with a different `advertise_address`, which is not allowed, was: '{was}' became: 'example.com:1234'
""" # noqa: E501
crawler = log_crawler(instance, err)
instance.fail_to_start()
assert crawler.matched
del instance.env["PICODATA_ADVERTISE"]
def test_whoami(instance: Instance): def test_whoami(instance: Instance):
assert instance.call("pico.whoami") == { assert instance.call("pico.whoami") == {
"raft_id": 1, "raft_id": 1,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment