diff --git a/backend/commands/CronController.php b/backend/commands/CronController.php index dda45d3bf..3357dc359 100644 --- a/backend/commands/CronController.php +++ b/backend/commands/CronController.php @@ -184,9 +184,9 @@ public function actionInstancePf($before = 60) $this->actionInstancePfTables(true); } - public function actionInstancePfTables($dopf = false) + public function actionInstancePfTables($dopf = false, int $target_id = 0) { - $t = TargetInstance::find()->active(); + $t = TargetInstance::find()->server_assigned($target_id)->active()->orderBy('target_instance.created_at ASC, target_instance.updated_at ASC'); foreach ($t->all() as $val) { $IPs = []; @@ -222,9 +222,11 @@ public function actionInstancePfTables($dopf = false) * * @param bool $pfonly Perform PF operations and skip instance docker actions (default: false) * @param int $expired_ago Filter instances based that haven't been updated X seconds ago (default: 2400 seconds = 40 minutes) + * @param int $atOnce Process X instances at once + * @param int $server_id Filter operations to $server_id * @return int Exit code */ - public function actionInstances(bool $pfonly = false, int $expired_ago = 2400) + public function actionInstances(bool $pfonly = false, int $expired_ago = 2400, int $atOnce = 0, int $server_id = 0) { if (file_exists("/tmp/cron-instances.lock")) { echo date("Y-m-d H:i:s ") . "Instances: /tmp/cron-instances.lock exists, skipping execution\n"; @@ -232,7 +234,8 @@ public function actionInstances(bool $pfonly = false, int $expired_ago = 2400) } touch("/tmp/cron-instances.lock"); $action = SELF::ACTION_EXPIRED; - $t = TargetInstance::find()->pending_action($expired_ago); + $t = TargetInstance::find()->pending_action($expired_ago)->server_assigned($server_id)->orderBy('target_instance.created_at ASC, target_instance.updated_at ASC'); + if ($atOnce > 0) $t->limit($atOnce); foreach ($t->all() as $val) { try { $ips = []; @@ -345,7 +348,7 @@ public function actionInstances(bool $pfonly = false, int $expired_ago = 2400) if ($pfonly === false) { try { - $t = TargetInstance::find()->active()->withApprovedMemberHeartbeat()->last_updated(round($expired_ago / 2)); + $t = TargetInstance::find()->active()->withApprovedMemberHeartbeat()->last_updated(round($expired_ago / 2))->server_assigned($target_id); foreach ($t->all() as $instance) { printf("Updating heartbeat [%d: %s for %d: %s]\n", $instance->target_id, $instance->target->name, $instance->player_id, $instance->player->username); $instance->touch('updated_at'); diff --git a/backend/modules/infrastructure/models/TargetInstanceQuery.php b/backend/modules/infrastructure/models/TargetInstanceQuery.php index da1067901..cbc2b7eb5 100644 --- a/backend/modules/infrastructure/models/TargetInstanceQuery.php +++ b/backend/modules/infrastructure/models/TargetInstanceQuery.php @@ -9,9 +9,18 @@ */ class TargetInstanceQuery extends \yii\db\ActiveQuery { + /** - * Filters TargetInstances to those that are active - * and whose team has at least one approved member with vpn_local_address != 0 + * Filters target instances that are active and have at least one approved team member + * with a non-null VPN local address. + * + * Joins: + * - `team_player` (`tp`) to match the instance's player and check approval. + * - `team` (`t`) to get the player's team. + * - `team_player` (`am`) to include other approved members of the team. + * - `player_last` (`al`) to ensure at least one approved member has a VPN local address. + * + * @return TargetInstanceQuery */ public function withApprovedMemberHeartbeat() { @@ -28,16 +37,60 @@ public function withApprovedMemberHeartbeat() ->distinct(); } + /** + * Filters target instances that are active. + * + * Active instances: + * - Have a non-null IP address. + * - Are not marked for destruction (`reboot != 2`). + * + * @return TargetInstanceQuery + */ public function active() { return $this->andWhere('target_instance.[[ip]] IS NOT NULL')->andWhere('target_instance.[[reboot]]!=2'); } + /** + * Filters target instances last updated more than the given number of seconds ago. + * + * @param int $seconds_ago Minimum age in seconds since last update. + * @return TargetInstanceQuery + */ public function last_updated(int $seconds_ago = 1) { return $this->andWhere(['<', 'target_instance.[[updated_at]]', new \yii\db\Expression("NOW() - INTERVAL $seconds_ago SECOND")]); } + /** + * Filters target instances by assigned server. + * + * - If `$server_id` is 0, matches instances assigned to any server. + * - Otherwise, matches instances assigned to the specified server. + * + * @param int $server_id Server ID to filter by, or 0 for any assigned server. + * @return TargetInstanceQuery + */ + public function server_assigned(int $server_id = 0) + { + if ($server_id === 0) + return $this->andWhere(['IS NOT', 'target_instance.server_id', null]); + return $this->andWhere(['target_instance.server_id' => $server_id]); + } + + /** + * Selects target instances that require pending actions. + * + * Pending action criteria: + * - IP address is null. + * - `reboot` is greater than 0. + * - Last updated more than `$seconds_ago` seconds ago. + * + * Overrides `reboot` to 2 (destroy) if the instance hasn't been updated within `$seconds_ago` seconds. + * + * @param int $seconds_ago Threshold in seconds to consider an instance outdated. + * @return TargetInstanceQuery + */ public function pending_action(int $seconds_ago = 1) { return $this->addSelect([