Fix relay batch gateway binding resolution

This commit is contained in:
Jeppe Bundgaard
2026-06-30 12:52:53 +02:00
parent d902202fe9
commit c6cf953ede
2 changed files with 65 additions and 4 deletions
@@ -982,19 +982,58 @@ class edge_gateway_manager
*/
public function resolveRelayBinding(int $departmentId, string $logicalRelayId): array
{
$gateway = $this->getPrimaryGatewayForDepartment($departmentId, false);
$rows = (new edge_gateway_relay_bindings_o())->getFieldsWhere([
'department_id' => $departmentId,
'gateway_id' => (int)$gateway->id,
'relay_id' => $logicalRelayId,
'deleted_at' => null,
], ['id']);
], ['id', 'gateway_id']);
if ($rows === []) {
throw new Exception('No edge gateway relay binding found for relay ' . $logicalRelayId);
}
return (new edge_gateway_relay_bindings_o())->select((int)$rows[0]['id'])->asArray();
$candidates = [];
foreach ($rows as $row) {
$binding = (new edge_gateway_relay_bindings_o())->select((int)$row['id']);
if (!$binding->exists()) {
continue;
}
$gateway = (new edge_gateways_o())->select((int)$row['gateway_id']);
if (!$gateway->exists() || $gateway->deleted_at->value() !== null) {
continue;
}
$effectiveStatus = self::resolveGatewayStatus(
$gateway->status->value() === null ? null : (string)$gateway->status->value(),
$gateway->last_heartbeat_at->value() === null ? null : (string)$gateway->last_heartbeat_at->value()
);
$candidates[] = [
'binding' => $binding->asArray(),
'gateway' => $gateway,
'status' => $effectiveStatus,
];
}
if ($candidates === []) {
throw new Exception('No active edge gateway found for relay ' . $logicalRelayId);
}
usort($candidates, static function (array $a, array $b) use ($departmentId): int {
/** @var edge_gateways_o $aGateway */
$aGateway = $a['gateway'];
/** @var edge_gateways_o $bGateway */
$bGateway = $b['gateway'];
return (((int)$bGateway->department_id->value() === $departmentId) <=> ((int)$aGateway->department_id->value() === $departmentId))
?: ((int)$bGateway->is_primary->value() <=> (int)$aGateway->is_primary->value())
?: (self::statusPriority((string)$b['status']) <=> self::statusPriority((string)$a['status']))
?: (self::heartbeatTimestamp($bGateway->last_heartbeat_at->value() === null ? null : (string)$bGateway->last_heartbeat_at->value())
<=> self::heartbeatTimestamp($aGateway->last_heartbeat_at->value() === null ? null : (string)$aGateway->last_heartbeat_at->value()));
});
return $candidates[0]['binding'];
}
/**
@@ -82,6 +82,28 @@ it('keeps relay dispatch and discovery queueing on the edge gateway manager', fu
expect($managerSource)->toContain("command_type");
});
it('resolves relay bindings without requiring a primary department gateway first', function (): void {
$managerSource = file_get_contents(app_path('classes/edge_gateway_manager.php'));
expect($managerSource)->not->toBeFalse();
preg_match(
'/public function resolveRelayBinding\(int \$departmentId, string \$logicalRelayId\): array\s*\{(?P<body>.*?)\n \}\n\n \/\*\*/s',
(string)$managerSource,
$matches
);
expect($matches)->toHaveKey('body');
$body = (string)$matches['body'];
expect($body)->toContain("'department_id' => \$departmentId")
->and($body)->toContain("'relay_id' => \$logicalRelayId")
->and($body)->not->toContain('getPrimaryGatewayForDepartment')
->and($body)->toContain('(new edge_gateways_o())->select((int)$row[\'gateway_id\'])')
->and($body)->toContain('No active edge gateway found for relay')
->and($body)->toContain('statusPriority')
->and($body)->toContain('heartbeatTimestamp');
});
it('loads relay command helpers on the manager and gateway operations on the dedicated service', function (): void {
$reflection = new ReflectionClass(edge_gateway_manager::class);
$operationServiceReflection = new ReflectionClass(\classes\edge_gateway_operation_service::class);