Class: Mt::Wall::Reconciler

Inherits:
Object
  • Object
show all
Defined in:
lib/mt/wall/reconciler.rb

Overview

Orchestrates the Terraform-style workflow for a single device:

1. Compiler turns the Configuration into the DESIRED DesiredState.
2. Transport#fetch reads the CURRENT DesiredState (DesiredState::MANAGED_PATHS).
3. Plan.diff produces the (identity-matched, order-aware) change set.
4. (apply only) Transport#apply executes the Plan under commit-confirm.

‘#plan` is read-only and safe to run anywhere (e.g. CI on a PR). `#apply` mutates the device and should run only on the trusted path (e.g. CI on merge to the main branch).

FAIL-SAFE APPLY: because mt-wall owns and replaces the whole filter/nat table over a network path that runs THROUGH that very firewall, apply uses a DEVICE-SIDE commit-confirm envelope (see Transport::Base) — client-side rollback is undeliverable if the link drops. The envelope is:

1. ARM: transport.arm_auto_revert(snapshot, timeout:) — back up the
   managed tables on the device and schedule a self-restore after timeout;
2. APPLY: transport.apply(plan.operations) in fail-safe order (open
   access + mgmt-protect BEFORE tightening; create-before-delete;
   default-drop LAST — encoded in the Plan, re-asserted here);
3. HEALTH-CHECK: the manager re-reaches the device to confirm the
   session survived;
4. CONFIRM: transport.confirm(handle) cancels the scheduled revert. If
   the health-check fails or the link is lost, CONFIRM never runs and the
   device-side job auto-reverts at timeout.

Constant Summary collapse

DEFAULT_REVERT_TIMEOUT =

Default seconds the device-side auto-revert waits before self-restoring if the manager never confirms. Overridable per device via ‘options`.

120
HEALTH_CHECK_MAX_ATTEMPTS =

Post-apply health-check resilience. A box that has just had its filter table replaced can briefly drop a probe (sessions re-establishing, connection-tracking settling) before it is genuinely reachable. The check therefore RETRIES a bounded number of times with a short backoff before declaring the apply unhealthy (and letting the device-side auto-revert fire). Kept small and time-boxed so it never blocks long: at most MAX_ATTEMPTS probes, never exceeding TOTAL_TIMEOUT seconds.

3
HEALTH_CHECK_BACKOFF =
0.5
HEALTH_CHECK_TOTAL_TIMEOUT =
5.0

Instance Method Summary collapse

Constructor Details

#initialize(configuration:, device:, transport:, sleeper: method(:sleep)) ⇒ Reconciler

Returns a new instance of Reconciler.

Parameters:

  • sleeper (#call) (defaults to: method(:sleep))

    seconds -> void; the backoff sleeper (test seam: inject a no-op for deterministic, instant health-check specs).



49
50
51
52
53
54
# File 'lib/mt/wall/reconciler.rb', line 49

def initialize(configuration:, device:, transport:, sleeper: method(:sleep))
  @configuration = configuration
  @device = device
  @transport = transport
  @sleeper = sleeper
end

Instance Method Details

#apply(plan = nil) ⇒ Plan

Apply the plan under the device-side commit-confirm envelope (arm_auto_revert -> apply -> health-check -> confirm; auto-revert fires on the device if confirm never runs).

Parameters:

  • plan (Plan) (defaults to: nil)

    defaults to a freshly computed plan

Returns:

  • (Plan)

    the plan that was applied



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/mt/wall/reconciler.rb', line 67

def apply(plan = nil)
  plan ||= self.plan
  return plan if plan.empty? # nothing to converge: skip the whole envelope

  # 1. ARM the device-side auto-revert. A nil handle => offline/no-op
  #    transport (e.g. Rsc): apply and return, no envelope to run.
  handle = @transport.arm_auto_revert(DesiredState::MANAGED_PATHS, timeout: revert_timeout)

  # 2. APPLY (operations are pre-sorted by Plan for fail-safe ordering).
  #    If the link drops here the exception propagates and CONFIRM never
  #    runs, so the device self-reverts at timeout.
  @transport.apply(plan.operations)

  # 3. HEALTH-CHECK + 4. CONFIRM (skipped for an offline/no-op transport).
  confirm_or_revert(handle) unless handle.nil?
  plan
end

#planPlan

Returns the changes needed to converge the device (no mutation).

Returns:

  • (Plan)

    the changes needed to converge the device (no mutation)



57
58
59
# File 'lib/mt/wall/reconciler.rb', line 57

def plan
  Plan.diff(desired: desired, current: fetch_current)
end