Bind daemon to Pacemaker resource identity

This commit is contained in:
Lyncoln
2026-09-17 12:31:15 -04:00
parent e0494aaeed
commit 773e046d42
3 changed files with 233 additions and 0 deletions
+61
View File
@@ -16,6 +16,7 @@ SOCKET_PATH = os.environ.get(
server_socket = None server_socket = None
registry = None registry = None
pacemaker_resource_id = None
PROTOCOL_VERSION = 1 PROTOCOL_VERSION = 1
@@ -32,6 +33,38 @@ def protocol_response(payload):
return response return response
def load_pacemaker_resource_binding(
current_registry,
environ=None,
):
"""
Load and validate the Pacemaker resource identity accepted by this
daemon instance.
RESOURCE_ID identifies the registry entry. PACEMAKER_RESOURCE_ID is a
separate control-plane binding and must resolve to that registry.
"""
if environ is None:
environ = os.environ
resource_id = environ.get(
"PACEMAKER_RESOURCE_ID"
)
if not resource_id:
raise RegistryError(
"PACEMAKER_RESOURCE_ID is not configured"
)
if resource_id not in current_registry.resources:
raise RegistryError(
"PACEMAKER_RESOURCE_ID does not identify "
f"a registered resource: {resource_id}"
)
return resource_id
def handle_request(req): def handle_request(req):
if not isinstance(req, dict): if not isinstance(req, dict):
return protocol_response({ return protocol_response({
@@ -65,11 +98,17 @@ def handle_request(req):
return protocol_response({ return protocol_response({
"ok": True, "ok": True,
"daemon": "managed-containerd", "daemon": "managed-containerd",
"pacemaker_resource": (
pacemaker_resource_id
),
}) })
if action == "registry.list": if action == "registry.list":
return protocol_response({ return protocol_response({
"ok": True, "ok": True,
"pacemaker_resource": (
pacemaker_resource_id
),
"resources": registry.list_resources(), "resources": registry.list_resources(),
}) })
@@ -82,6 +121,18 @@ def handle_request(req):
"error": "resource is required", "error": "resource is required",
}) })
if resource != pacemaker_resource_id:
return protocol_response({
"ok": False,
"error_code": "resource_binding_mismatch",
"error": (
"requested resource does not match "
"the configured Pacemaker binding"
),
"resource": resource,
"action": action,
})
return protocol_response( return protocol_response(
registry.execute( registry.execute(
resource, resource,
@@ -118,8 +169,12 @@ def shutdown(signum, frame):
def serve(): def serve():
global server_socket global server_socket
global registry global registry
global pacemaker_resource_id
registry = Registry() registry = Registry()
pacemaker_resource_id = (
load_pacemaker_resource_binding(registry)
)
os.makedirs( os.makedirs(
os.path.dirname(SOCKET_PATH), os.path.dirname(SOCKET_PATH),
@@ -151,6 +206,12 @@ def serve():
flush=True, flush=True,
) )
print(
"Pacemaker resource binding: "
+ pacemaker_resource_id,
flush=True,
)
try: try:
while True: while True:
conn, _ = server_socket.accept() conn, _ = server_socket.accept()
+1
View File
@@ -1,4 +1,5 @@
RESOURCE_ID=postgresql-ha RESOURCE_ID=postgresql-ha
PACEMAKER_RESOURCE_ID=postgresql-ha
PLUGIN=postgresql PLUGIN=postgresql
MANAGED_CONTAINERD_SOCKET=/run/managed-containerd/managed-containerd.sock MANAGED_CONTAINERD_SOCKET=/run/managed-containerd/managed-containerd.sock
+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env python3
import sys
sys.path.insert(0, "daemon")
import server
from registry import RegistryError
class FakeRegistry:
def __init__(self):
self.resources = {
"postgresql-ha": object(),
}
self.executions = []
def list_resources(self):
return {
"postgresql-ha": {
"resource": "postgresql-ha",
}
}
def execute(self, resource, action):
self.executions.append(
(resource, action)
)
return {
"ok": True,
"resource": resource,
"plugin": "postgresql",
"action": action,
"result": {
"accepted": True,
},
}
def require(condition, message):
if not condition:
raise SystemExit("FAIL: " + message)
def require_registry_error(callback, message):
try:
callback()
except RegistryError:
return
raise SystemExit("FAIL: " + message)
registry = FakeRegistry()
print("===== PACEMAKER RESOURCE BINDING =====")
require_registry_error(
lambda: server.load_pacemaker_resource_binding(
registry,
{},
),
"missing binding was accepted",
)
print("missing_binding rejected")
require_registry_error(
lambda: server.load_pacemaker_resource_binding(
registry,
{
"PACEMAKER_RESOURCE_ID": "other-resource",
},
),
"unknown binding was accepted",
)
print("unknown_binding rejected")
binding = server.load_pacemaker_resource_binding(
registry,
{
"PACEMAKER_RESOURCE_ID": "postgresql-ha",
},
)
require(
binding == "postgresql-ha",
"valid binding was not loaded",
)
print("valid_binding accepted")
server.registry = registry
server.pacemaker_resource_id = binding
ping = server.handle_request({
"protocol_version": 1,
"action": "ping",
})
require(ping["ok"] is True, "ping failed")
require(
ping["pacemaker_resource"] == "postgresql-ha",
"ping did not report the binding",
)
require(
ping["protocol_version"] == 1,
"ping lost the protocol version",
)
print("ping_binding reported")
response = server.handle_request({
"protocol_version": 1,
"resource": "postgresql-ha",
"action": "monitor",
})
require(
response["ok"] is True,
"matching resource was rejected",
)
require(
registry.executions
== [("postgresql-ha", "monitor")],
"matching request was not executed exactly once",
)
print("matching_resource executed")
response = server.handle_request({
"protocol_version": 1,
"resource": "different-resource",
"action": "monitor",
})
require(
response["ok"] is False,
"mismatched resource was accepted",
)
require(
response["error_code"]
== "resource_binding_mismatch",
"wrong mismatch classification",
)
require(
response["resource"]
== "different-resource",
"rejected resource was not echoed",
)
require(
response["action"] == "monitor",
"rejected action was not echoed",
)
require(
registry.executions
== [("postgresql-ha", "monitor")],
"mismatched request reached the registry",
)
require(
response["protocol_version"] == 1,
"mismatch response lost protocol metadata",
)
print("mismatched_resource rejected")
print()
print("RESOURCE_BINDING_TEST_PASS=1")