235 lines
4.8 KiB
Python
Executable File
235 lines
4.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import ast
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
sys.path.insert(0, "daemon")
|
|
|
|
from registry import Plugin, Registry, RegistryError, Resource
|
|
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
raise SystemExit("FAIL: " + message)
|
|
|
|
|
|
def write_plugin(
|
|
root,
|
|
name,
|
|
manifest,
|
|
source,
|
|
):
|
|
plugin_root = Path(root) / name
|
|
plugin_root.mkdir()
|
|
|
|
(plugin_root / "manifest.json").write_text(
|
|
json.dumps(manifest)
|
|
)
|
|
(plugin_root / "plugin.py").write_text(source)
|
|
|
|
|
|
print("===== GENERIC PLUGIN RUNTIME STATE =====")
|
|
|
|
with tempfile.TemporaryDirectory() as temporary_root:
|
|
stateful_manifest = {
|
|
"name": "stateful",
|
|
"version": 1,
|
|
"module": "plugin.py",
|
|
"actions": ["monitor"],
|
|
"required_config": ["REQUIRED_VALUE"],
|
|
"optional_config": ["OPTIONAL_VALUE"],
|
|
}
|
|
|
|
write_plugin(
|
|
temporary_root,
|
|
"stateful",
|
|
stateful_manifest,
|
|
"""
|
|
def monitor(config, runtime_state):
|
|
counter = runtime_state.get("counter", 0) + 1
|
|
runtime_state["counter"] = counter
|
|
|
|
return {
|
|
"counter": counter,
|
|
"required": config["REQUIRED_VALUE"],
|
|
"optional": config["OPTIONAL_VALUE"],
|
|
}
|
|
""",
|
|
)
|
|
|
|
environment = {
|
|
"MANAGED_CONTAINERD_PLUGIN_ROOT": (
|
|
temporary_root
|
|
),
|
|
"RESOURCE_ID": "stateful-resource",
|
|
"PLUGIN": "stateful",
|
|
"REQUIRED_VALUE": "required",
|
|
"OPTIONAL_VALUE": "optional",
|
|
}
|
|
|
|
with patch.dict(
|
|
os.environ,
|
|
environment,
|
|
clear=False,
|
|
):
|
|
registry = Registry()
|
|
|
|
first = registry.execute(
|
|
"stateful-resource",
|
|
"monitor",
|
|
)
|
|
second = registry.execute(
|
|
"stateful-resource",
|
|
"monitor",
|
|
)
|
|
|
|
require(
|
|
first["result"]["counter"] == 1,
|
|
"first stateful invocation has wrong counter",
|
|
)
|
|
require(
|
|
second["result"]["counter"] == 2,
|
|
"runtime state did not persist",
|
|
)
|
|
require(
|
|
second["result"]["required"] == "required",
|
|
"required configuration was not injected",
|
|
)
|
|
require(
|
|
second["result"]["optional"] == "optional",
|
|
"optional configuration was not injected",
|
|
)
|
|
|
|
resource = registry.resources[
|
|
"stateful-resource"
|
|
]
|
|
|
|
require(
|
|
resource.runtime_state == {"counter": 2},
|
|
"resource runtime state has unexpected contents",
|
|
)
|
|
|
|
print("stateful_handler persisted")
|
|
print("optional_config injected")
|
|
|
|
legacy_manifest = {
|
|
"name": "legacy",
|
|
"version": 1,
|
|
"module": "plugin.py",
|
|
"actions": ["monitor"],
|
|
"required_config": ["REQUIRED_VALUE"],
|
|
}
|
|
|
|
write_plugin(
|
|
temporary_root,
|
|
"legacy",
|
|
legacy_manifest,
|
|
"""
|
|
def monitor(config):
|
|
return {
|
|
"legacy": True,
|
|
"required": config["REQUIRED_VALUE"],
|
|
}
|
|
""",
|
|
)
|
|
|
|
legacy_plugin = Plugin(
|
|
temporary_root,
|
|
"legacy",
|
|
)
|
|
|
|
legacy_result = legacy_plugin.execute(
|
|
"monitor",
|
|
{"REQUIRED_VALUE": "required"},
|
|
{},
|
|
)
|
|
|
|
require(
|
|
legacy_result == {
|
|
"legacy": True,
|
|
"required": "required",
|
|
},
|
|
"one-argument plugin compatibility failed",
|
|
)
|
|
|
|
print("legacy_handler compatible")
|
|
|
|
try:
|
|
Resource(
|
|
"invalid-resource",
|
|
legacy_plugin,
|
|
{"REQUIRED_VALUE": None},
|
|
)
|
|
except RegistryError:
|
|
print("required_config enforced")
|
|
else:
|
|
raise SystemExit(
|
|
"FAIL: missing required configuration accepted"
|
|
)
|
|
|
|
production_manifest = json.loads(
|
|
Path(
|
|
"plugins/postgresql/manifest.json"
|
|
).read_text()
|
|
)
|
|
|
|
require(
|
|
production_manifest.get("version") == 4,
|
|
"PostgreSQL manifest version is not 4",
|
|
)
|
|
require(
|
|
"PG_REPLICATION_PEERS"
|
|
in production_manifest.get(
|
|
"required_config",
|
|
[],
|
|
),
|
|
"replication peers are not required",
|
|
)
|
|
require(
|
|
"PG_RECONNECT_GRACE_SECONDS"
|
|
in production_manifest.get(
|
|
"optional_config",
|
|
[],
|
|
),
|
|
"reconnect grace is not optional",
|
|
)
|
|
|
|
plugin_tree = ast.parse(
|
|
Path(
|
|
"plugins/postgresql/plugin.py"
|
|
).read_text()
|
|
)
|
|
|
|
monitor_functions = [
|
|
node
|
|
for node in plugin_tree.body
|
|
if isinstance(node, ast.FunctionDef)
|
|
and node.name == "monitor"
|
|
]
|
|
|
|
require(
|
|
len(monitor_functions) == 1,
|
|
"PostgreSQL monitor entry point is ambiguous",
|
|
)
|
|
|
|
monitor_arguments = [
|
|
argument.arg
|
|
for argument
|
|
in monitor_functions[0].args.args
|
|
]
|
|
|
|
require(
|
|
monitor_arguments[:2]
|
|
== ["config", "runtime_state"],
|
|
"PostgreSQL monitor does not accept runtime state",
|
|
)
|
|
|
|
print("postgresql_contract validated")
|
|
print()
|
|
print("RUNTIME_STATE_TEST_PASS=1")
|