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

test: do not move PluginReflection to conftest.py

parent d0c5fa9a
No related branches found
No related tags found
1 merge request!1220some tests have been broken and we didn't know
...@@ -2253,252 +2253,6 @@ instance: ...@@ -2253,252 +2253,6 @@ instance:
return self.cluster.instances[0] return self.cluster.instances[0]
_PLUGIN = "testplug"
_PLUGIN_SERVICES = ["testservice_1", "testservice_2"]
_PLUGIN_SMALL = "testplug_small"
_PLUGIN_SMALL_SERVICES = ["testservice_1"]
_PLUGIN_SMALL_SERVICES_SVC2 = ["testservice_2"]
_PLUGIN_VERSION_1 = "0.1.0"
_PLUGIN_VERSION_2 = "0.2.0"
_DEFAULT_TIER = "default"
@dataclass
class PluginReflection:
"""PluginReflection used to describe the expected state of the plugin"""
# plugin name
name: str
# plugin version
version: str
# list of plugin services
services: List[str]
# instances in cluster
instances: List[Instance]
# plugin topology
topology: Dict[Instance, List[str]] = field(default_factory=dict)
# if True - assert_synced checks that plugin are installed
installed: bool = False
# if True - assert_synced checks that plugin are enabled
enabled: bool = False
# plugin data [table -> tuples] map
data: Dict[str, Optional[List[Any]]] = field(default_factory=dict)
def __post__init__(self):
for i in self.instances:
self.topology[i] = []
@staticmethod
def default(*instances):
"""Create reflection for default plugin with default topology"""
topology = {}
for i in instances:
topology[i] = _PLUGIN_SERVICES
return PluginReflection(
name=_PLUGIN,
version="0.1.0",
services=_PLUGIN_SERVICES,
instances=list(instances),
).set_topology(topology)
def install(self, installed: bool):
self.installed = installed
return self
def enable(self, enabled: bool):
self.enabled = enabled
return self
def set_topology(self, topology: dict[Instance, list[str]]):
self.topology = topology
return self
def add_instance(self, i):
self.instances.append(i)
return self
def set_data(self, data: dict[str, Optional[list[Any]]]):
self.data = data
return self
def assert_synced(self):
"""Assert that plugin reflection and plugin state in cluster are synchronized.
This means that system tables `_pico_plugin`, `_pico_service` and `_pico_service_route`
contain necessary plugin information."""
for i in self.instances:
plugins = i.eval(
"return box.space._pico_plugin:select({...})", self.name, self.version
)
if self.installed:
assert len(plugins) == 1
assert plugins[0][1] == self.enabled
else:
assert len(plugins) == 0
for service in self.services:
svcs = i.eval(
"return box.space._pico_service:select({...})",
[self.name, service, self.version],
)
if self.installed:
assert len(svcs) == 1
else:
assert len(svcs) == 0
for i in self.topology:
expected_services = []
for service in self.topology[i]:
expected_services.append(
[i.instance_id, self.name, self.version, service, False]
)
for neighboring_i in self.topology:
routes = neighboring_i.eval(
'return box.space._pico_service_route:pairs({...}, {iterator="EQ"}):totable()',
i.instance_id,
self.name,
self.version,
)
assert routes == expected_services
def assert_data_synced(self):
for table in self.data:
data = []
for i in self.instances:
if self.data[table] is None:
with pytest.raises(TarantoolError, match="attempt to index field"):
i.eval(f"return box.space.{table}:select()")
else:
data += i.eval(f"return box.space.{table}:select()")
if self.data[table] is not None:
assert data.sort() == self.data[table].sort()
@staticmethod
def assert_cb_called(service, callback, called_times, *instances):
for i in instances:
cb_calls_number = i.eval(
f"if _G['plugin_state'] == nil then _G['plugin_state'] = {{}} end "
f"if _G['plugin_state']['{service}'] == nil then _G['plugin_state']['{service}']"
f" = {{}} end "
f"if _G['plugin_state']['{service}']['{callback}'] == nil then _G['plugin_state']"
f"['{service}']['{callback}'] = 0 end "
f"return _G['plugin_state']['{service}']['{callback}']"
)
assert cb_calls_number == called_times
@staticmethod
def assert_persisted_data_exists(data, *instances):
for i in instances:
data_exists = i.eval(
f"return box.space.persisted_data:get({{'{data}'}}) ~= box.NULL"
)
assert data_exists
@staticmethod
def clear_persisted_data(data, *instances):
for i in instances:
i.eval("return box.space.persisted_data:drop()")
@staticmethod
def inject_error(service, error, value, instance):
instance.eval("if _G['err_inj'] == nil then _G['err_inj'] = {} end")
instance.eval(
f"if _G['err_inj']['{service}'] == nil then _G['err_inj']['{service}'] "
"= {{}} end"
)
instance.eval(f"_G['err_inj']['{service}']['{error}'] = ...", (value,))
@staticmethod
def remove_error(service, error, instance):
instance.eval("if _G['err_inj'] == nil then _G['err_inj'] = {} end")
instance.eval(
f"if _G['err_inj']['{service}'] == nil then _G['err_inj']['{service}'] "
"= {{}} end"
)
instance.eval(f"_G['err_inj']['{service}']['{error}'] = nil")
@staticmethod
def assert_last_seen_ctx(service, expected_ctx, *instances):
for i in instances:
ctx = i.eval(f"return _G['plugin_state']['{service}']['last_seen_ctx']")
assert ctx == expected_ctx
def get_config(self, service, instance):
config = dict()
records = instance.eval(
"return box.space._pico_plugin_config:select({...})",
[self.name, self.version, service],
)
for record in records:
config[record[3]] = record[4]
return config
@staticmethod
def get_seen_config(service, instance):
return instance.eval(
f"return _G['plugin_state']['{service}']['current_config']"
)
def assert_config(self, service, expected_cfg, *instances):
for i in instances:
cfg_space = self.get_config(service, i)
assert cfg_space == expected_cfg
cfg_seen = self.get_seen_config(service, i)
assert cfg_seen == expected_cfg
def assert_route_poisoned(self, poison_instance_id, service, poisoned=True):
for i in self.instances:
route_poisoned = i.eval(
"return box.space._pico_service_route:get({...}).poison",
poison_instance_id,
self.name,
self.version,
service,
)
assert route_poisoned == poisoned
@staticmethod
def assert_data_eq(instance, key, expected):
val = instance.eval(f"return _G['plugin_state']['data']['{key}']")
assert val == expected
@staticmethod
def assert_int_data_le(instance, key, expected):
val = instance.eval(f"return _G['plugin_state']['data']['{key}']")
assert int(val) <= expected
def install_and_enable_plugin(
instance,
plugin,
services,
version="0.1.0",
migrate=False,
timeout=3,
default_config=None,
if_not_exist=False,
):
instance.call(
"pico.install_plugin",
plugin,
version,
{"migrate": migrate, "if_not_exist": if_not_exist},
timeout=timeout,
)
for s in services:
if default_config is not None:
for key in default_config:
instance.eval(
f"box.space._pico_plugin_config:replace"
f"({{'{plugin}', '0.1.0', '{s}', '{key}', ...}})",
default_config[key],
)
instance.call("pico.service_append_tier", plugin, version, s, _DEFAULT_TIER)
instance.call("pico.enable_plugin", plugin, version, timeout=timeout)
@pytest.fixture @pytest.fixture
def postgres(cluster: Cluster): def postgres(cluster: Cluster):
return Postgres(cluster).install() return Postgres(cluster).install()
......
from conftest import ( from conftest import (
Cluster, Cluster,
Instance, Instance,
_PLUGIN,
_PLUGIN_SERVICES,
_PLUGIN_SMALL,
_PLUGIN_SMALL_SERVICES,
_PLUGIN_VERSION_1,
) )
from urllib.request import urlopen from urllib.request import urlopen
import pytest import pytest
...@@ -106,31 +101,37 @@ def test_webui_with_plugin(cluster: Cluster): ...@@ -106,31 +101,37 @@ def test_webui_with_plugin(cluster: Cluster):
""" """
cluster.set_config_file(yaml=cluster_cfg) cluster.set_config_file(yaml=cluster_cfg)
plugin_1 = "testplug"
plugin_1_services = ["testservice_1", "testservice_2"]
plugin_2 = "testplug_small"
plugin_2_service = "testservice_1"
version_1 = "0.1.0"
i1 = cluster.add_instance(wait_online=True, tier="red", enable_http=True) i1 = cluster.add_instance(wait_online=True, tier="red", enable_http=True)
i2 = cluster.add_instance(wait_online=True, tier="blue") i2 = cluster.add_instance(wait_online=True, tier="blue")
i3 = cluster.add_instance(wait_online=True, tier="green") i3 = cluster.add_instance(wait_online=True, tier="green")
i1.call("pico.install_plugin", _PLUGIN, _PLUGIN_VERSION_1) i1.call("pico.install_plugin", plugin_1, version_1)
i1.call("pico.install_plugin", _PLUGIN_SMALL, _PLUGIN_VERSION_1) i1.call("pico.install_plugin", plugin_2, version_1)
i1.call( i1.call(
"pico.service_append_tier", "pico.service_append_tier",
_PLUGIN, plugin_1,
_PLUGIN_VERSION_1, version_1,
_PLUGIN_SERVICES[0], plugin_1_services[0],
"red", "red",
) )
i1.call( i1.call(
"pico.service_append_tier", "pico.service_append_tier",
_PLUGIN, plugin_1,
_PLUGIN_VERSION_1, version_1,
_PLUGIN_SERVICES[1], plugin_1_services[1],
"blue", "blue",
) )
i1.call( i1.call(
"pico.service_append_tier", "pico.service_append_tier",
_PLUGIN_SMALL, plugin_2,
_PLUGIN_VERSION_1, version_1,
_PLUGIN_SMALL_SERVICES[0], plugin_2_service,
"blue", "blue",
) )
...@@ -207,13 +208,13 @@ def test_webui_with_plugin(cluster: Cluster): ...@@ -207,13 +208,13 @@ def test_webui_with_plugin(cluster: Cluster):
tier_red = { tier_red = {
**tier_template, **tier_template,
"name": "red", "name": "red",
"services": [_PLUGIN_SERVICES[0]], "services": [plugin_1_services[0]],
"replicasets": [r1], "replicasets": [r1],
} }
tier_blue = { tier_blue = {
**tier_template, **tier_template,
"name": "blue", "name": "blue",
"services": [_PLUGIN_SERVICES[1], _PLUGIN_SMALL_SERVICES[0]], "services": [plugin_1_services[1], plugin_2_service],
"replicasets": [r2], "replicasets": [r2],
} }
tier_green = {**tier_template, "name": "green", "services": [], "replicasets": [r3]} tier_green = {**tier_template, "name": "green", "services": [], "replicasets": [r3]}
...@@ -236,8 +237,8 @@ def test_webui_with_plugin(cluster: Cluster): ...@@ -236,8 +237,8 @@ def test_webui_with_plugin(cluster: Cluster):
"currentInstaceVersion": instance_version, "currentInstaceVersion": instance_version,
"memory": {"usable": 201326592, "used": 100663296}, "memory": {"usable": 201326592, "used": 100663296},
"plugins": [ "plugins": [
_PLUGIN + " " + _PLUGIN_VERSION_1, plugin_1 + " " + version_1,
_PLUGIN_SMALL + " " + _PLUGIN_VERSION_1, plugin_2 + " " + version_1,
], ],
} }
......
from dataclasses import dataclass, field
import time import time
from typing import Any from typing import Any, Dict, List, Optional
import pytest import pytest
import uuid import uuid
import msgpack # type: ignore import msgpack # type: ignore
...@@ -12,15 +13,6 @@ from conftest import ( ...@@ -12,15 +13,6 @@ from conftest import (
Instance, Instance,
TarantoolError, TarantoolError,
log_crawler, log_crawler,
PluginReflection,
_PLUGIN,
_PLUGIN_SERVICES,
_PLUGIN_SMALL,
_PLUGIN_SMALL_SERVICES,
_PLUGIN_VERSION_1,
_PLUGIN_VERSION_2,
_DEFAULT_TIER,
install_and_enable_plugin,
) )
from decimal import Decimal from decimal import Decimal
import requests # type: ignore import requests # type: ignore
...@@ -33,6 +25,14 @@ _DEFAULT_CFG = {"foo": True, "bar": 101, "baz": ["one", "two", "three"]} ...@@ -33,6 +25,14 @@ _DEFAULT_CFG = {"foo": True, "bar": 101, "baz": ["one", "two", "three"]}
_NEW_CFG = {"foo": True, "bar": 102, "baz": ["a", "b"]} _NEW_CFG = {"foo": True, "bar": 102, "baz": ["a", "b"]}
_NEW_CFG_2 = {"foo": False, "bar": 102, "baz": ["a", "b"]} _NEW_CFG_2 = {"foo": False, "bar": 102, "baz": ["a", "b"]}
_PLUGIN = "testplug"
_PLUGIN_SERVICES = ["testservice_1", "testservice_2"]
_PLUGIN_SMALL = "testplug_small"
_PLUGIN_SMALL_SERVICES = ["testservice_1"]
_PLUGIN_SMALL_SERVICES_SVC2 = ["testservice_2"]
_PLUGIN_VERSION_1 = "0.1.0"
_PLUGIN_VERSION_2 = "0.2.0"
_DEFAULT_TIER = "default"
_PLUGIN_WITH_MIGRATION = "testplug_w_migration" _PLUGIN_WITH_MIGRATION = "testplug_w_migration"
_PLUGIN_WITH_MIGRATION_SERVICES = ["testservice_2"] _PLUGIN_WITH_MIGRATION_SERVICES = ["testservice_2"]
_PLUGIN_W_SDK = "testplug_sdk" _PLUGIN_W_SDK = "testplug_sdk"
...@@ -44,6 +44,244 @@ PLUGIN_NAME = 2 ...@@ -44,6 +44,244 @@ PLUGIN_NAME = 2
SERVICE_NAME = 3 SERVICE_NAME = 3
PLUGIN_VERSION = 4 PLUGIN_VERSION = 4
# ---------------------------------- Test helper classes {-----------------------------------------
@dataclass
class PluginReflection:
"""PluginReflection used to describe the expected state of the plugin"""
# plugin name
name: str
# plugin version
version: str
# list of plugin services
services: List[str]
# instances in cluster
instances: List[Instance]
# plugin topology
topology: Dict[Instance, List[str]] = field(default_factory=dict)
# if True - assert_synced checks that plugin are installed
installed: bool = False
# if True - assert_synced checks that plugin are enabled
enabled: bool = False
# plugin data [table -> tuples] map
data: Dict[str, Optional[List[Any]]] = field(default_factory=dict)
def __post__init__(self):
for i in self.instances:
self.topology[i] = []
@staticmethod
def default(*instances):
"""Create reflection for default plugin with default topology"""
topology = {}
for i in instances:
topology[i] = _PLUGIN_SERVICES
return PluginReflection(
name=_PLUGIN,
version="0.1.0",
services=_PLUGIN_SERVICES,
instances=list(instances),
).set_topology(topology)
def install(self, installed: bool):
self.installed = installed
return self
def enable(self, enabled: bool):
self.enabled = enabled
return self
def set_topology(self, topology: dict[Instance, list[str]]):
self.topology = topology
return self
def add_instance(self, i):
self.instances.append(i)
return self
def set_data(self, data: dict[str, Optional[list[Any]]]):
self.data = data
return self
def assert_synced(self):
"""Assert that plugin reflection and plugin state in cluster are synchronized.
This means that system tables `_pico_plugin`, `_pico_service` and `_pico_service_route`
contain necessary plugin information."""
for i in self.instances:
plugins = i.eval(
"return box.space._pico_plugin:select({...})", self.name, self.version
)
if self.installed:
assert len(plugins) == 1
assert plugins[0][1] == self.enabled
else:
assert len(plugins) == 0
for service in self.services:
svcs = i.eval(
"return box.space._pico_service:select({...})",
[self.name, service, self.version],
)
if self.installed:
assert len(svcs) == 1
else:
assert len(svcs) == 0
for i in self.topology:
expected_services = []
for service in self.topology[i]:
expected_services.append(
[i.instance_id, self.name, self.version, service, False]
)
for neighboring_i in self.topology:
routes = neighboring_i.eval(
'return box.space._pico_service_route:pairs({...}, {iterator="EQ"}):totable()',
i.instance_id,
self.name,
self.version,
)
assert routes == expected_services
def assert_data_synced(self):
for table in self.data:
data = []
for i in self.instances:
if self.data[table] is None:
with pytest.raises(TarantoolError, match="attempt to index field"):
i.eval(f"return box.space.{table}:select()")
else:
data += i.eval(f"return box.space.{table}:select()")
if self.data[table] is not None:
assert data.sort() == self.data[table].sort()
@staticmethod
def assert_cb_called(service, callback, called_times, *instances):
for i in instances:
cb_calls_number = i.eval(
f"if _G['plugin_state'] == nil then _G['plugin_state'] = {{}} end "
f"if _G['plugin_state']['{service}'] == nil then _G['plugin_state']['{service}']"
f" = {{}} end "
f"if _G['plugin_state']['{service}']['{callback}'] == nil then _G['plugin_state']"
f"['{service}']['{callback}'] = 0 end "
f"return _G['plugin_state']['{service}']['{callback}']"
)
assert cb_calls_number == called_times
@staticmethod
def assert_persisted_data_exists(data, *instances):
for i in instances:
data_exists = i.eval(
f"return box.space.persisted_data:get({{'{data}'}}) ~= box.NULL"
)
assert data_exists
@staticmethod
def clear_persisted_data(data, *instances):
for i in instances:
i.eval("return box.space.persisted_data:drop()")
@staticmethod
def inject_error(service, error, value, instance):
instance.eval("if _G['err_inj'] == nil then _G['err_inj'] = {} end")
instance.eval(
f"if _G['err_inj']['{service}'] == nil then _G['err_inj']['{service}'] "
"= {{}} end"
)
instance.eval(f"_G['err_inj']['{service}']['{error}'] = ...", (value,))
@staticmethod
def remove_error(service, error, instance):
instance.eval("if _G['err_inj'] == nil then _G['err_inj'] = {} end")
instance.eval(
f"if _G['err_inj']['{service}'] == nil then _G['err_inj']['{service}'] "
"= {{}} end"
)
instance.eval(f"_G['err_inj']['{service}']['{error}'] = nil")
@staticmethod
def assert_last_seen_ctx(service, expected_ctx, *instances):
for i in instances:
ctx = i.eval(f"return _G['plugin_state']['{service}']['last_seen_ctx']")
assert ctx == expected_ctx
def get_config(self, service, instance):
config = dict()
records = instance.eval(
"return box.space._pico_plugin_config:select({...})",
[self.name, self.version, service],
)
for record in records:
config[record[3]] = record[4]
return config
@staticmethod
def get_seen_config(service, instance):
return instance.eval(
f"return _G['plugin_state']['{service}']['current_config']"
)
def assert_config(self, service, expected_cfg, *instances):
for i in instances:
cfg_space = self.get_config(service, i)
assert cfg_space == expected_cfg
cfg_seen = self.get_seen_config(service, i)
assert cfg_seen == expected_cfg
def assert_route_poisoned(self, poison_instance_id, service, poisoned=True):
for i in self.instances:
route_poisoned = i.eval(
"return box.space._pico_service_route:get({...}).poison",
poison_instance_id,
self.name,
self.version,
service,
)
assert route_poisoned == poisoned
@staticmethod
def assert_data_eq(instance, key, expected):
val = instance.eval(f"return _G['plugin_state']['data']['{key}']")
assert val == expected
@staticmethod
def assert_int_data_le(instance, key, expected):
val = instance.eval(f"return _G['plugin_state']['data']['{key}']")
assert int(val) <= expected
def install_and_enable_plugin(
instance,
plugin,
services,
version="0.1.0",
migrate=False,
timeout=3,
default_config=None,
if_not_exist=False,
):
instance.call(
"pico.install_plugin",
plugin,
version,
{"migrate": migrate, "if_not_exist": if_not_exist},
timeout=timeout,
)
for s in services:
if default_config is not None:
for key in default_config:
instance.eval(
f"box.space._pico_plugin_config:replace"
f"({{'{plugin}', '0.1.0', '{s}', '{key}', ...}})",
default_config[key],
)
instance.call("pico.service_append_tier", plugin, version, s, _DEFAULT_TIER)
instance.call("pico.enable_plugin", plugin, version, timeout=timeout)
def test_invalid_manifest_plugin(cluster: Cluster): def test_invalid_manifest_plugin(cluster: Cluster):
i1, i2 = cluster.deploy(instance_count=2) i1, i2 = cluster.deploy(instance_count=2)
......
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