\Drupal\strata\Drill DrillRunner

Proves the store can reproduce what the site holds, on a schedule, without restoring anything.

A backup nobody has restored is a hypothesis. This is what turns it into a measurement: for a bounded sample of subjects, replay what the store says the subject was at the commit where it was last captured, read what the site holds now, and compare. Nothing is written; a drill is a read of both sides.

The comparison is only valid where the site has not moved on. A subject edited after the commit that was replayed will differ for a legitimate reason, and counting that as a failure would make every busy site look broken. So a subject whose last capture is older than its current state is SKIPPED, and the report keeps skipped separate from both pass and fail. A drill that could judge nothing reports inconclusive rather than pass.

What a drill does not prove. It reads the sample it took, so a fault confined to a subject outside the sample survives it; it compares values rather than replaying a whole restore, so it says nothing about whether writing those values back would succeed; and it cannot see a subject the capture never recorded, which is what the reconciler exists for. What it does prove is that the bytes in the bucket decode, resolve through their delta chains and dictionaries, and reconstruct the value they claim to hold.

Summary

Methods
Properties
Constants
__construct
run
examine
No public properties found
CODE
DEFAULT_SAMPLE
No protected methods found
No protected properties found
No protected constants found
sealedAt
compare
live
liveEntity
liveState
normalize
choose
record
refuse
replayer
subjects
refs
commits
entityTypeManager
configFactory
state
ledger
time
logger
index
notifier
No private constants found

Constant

CODE

CODE = 'drill.drift'

The finding code a drill raises when the store does not reproduce the site.

DEFAULT_SAMPLE

DEFAULT_SAMPLE = 50

Subjects one drill examines when no size is given.

A drill runs on cron beside everything else, so it is bounded by default. Every subject can be checked with an explicit sample size, at the cost of one replay each.

Properties

$replayer

$replayer : \Drupal\strata\Restore\Replayer

Type

Replayer

$subjects

$subjects : \Drupal\strata\Tree\SubjectIndex

Type

SubjectIndex

$refs

$refs : \Drupal\strata\Tree\RefStore

Type

RefStore

$commits

$commits : \Drupal\strata\Tree\CommitLog

Type

CommitLog

$entityTypeManager

$entityTypeManager : \Drupal\Core\Entity\EntityTypeManagerInterface

Type

EntityTypeManagerInterface

$configFactory

$configFactory : \Drupal\Core\Config\ConfigFactoryInterface

Type

ConfigFactoryInterface

$state

$state : \Drupal\Core\State\StateInterface

Type

StateInterface

$time

$time : \Drupal\Component\Datetime\TimeInterface

Type

TimeInterface

$logger

$logger : \Psr\Log\LoggerInterface

Type

LoggerInterface

$index

$index : ?\Drupal\strata\Drill\DrillIndex

Type

DrillIndex|null

$notifier

$notifier : ?\Drupal\strata\Event\Notifier

Type

Notifier|null

Methods

__construct()

__construct(\Drupal\strata\Restore\Replayer  $replayer, \Drupal\strata\Tree\SubjectIndex  $subjects, \Drupal\strata\Tree\RefStore  $refs, \Drupal\strata\Tree\CommitLog  $commits, \Drupal\Core\Entity\EntityTypeManagerInterface  $entityTypeManager, \Drupal\Core\Config\ConfigFactoryInterface  $configFactory, \Drupal\Core\State\StateInterface  $state, \Drupal\strata\Health\HealthLedgerInterface  $ledger, \Drupal\Component\Datetime\TimeInterface  $time, \Psr\Log\LoggerInterface  $logger, \Drupal\strata\Drill\DrillIndex|null  $index = null, \Drupal\strata\Event\Notifier|null  $notifier = null) : mixed

Constructs a runner.

Parameters

\Drupal\strata\Restore\Replayer $replayer

Reconstructs what the store says a subject was.

\Drupal\strata\Tree\SubjectIndex $subjects

Names the subjects and when each was last captured.

\Drupal\strata\Tree\RefStore $refs

Supplies the commit to replay against when none is given.

\Drupal\strata\Tree\CommitLog $commits

Reads when the replayed commit was sealed, which is what the moved-on check compares against.

\Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager

Loads the live entities.

\Drupal\Core\Config\ConfigFactoryInterface $configFactory

Loads the live configuration.

\Drupal\Core\State\StateInterface $state

Loads the live state values.

\Drupal\strata\Health\HealthLedgerInterface $ledger

Where drift is recorded.

\Drupal\Component\Datetime\TimeInterface $time

Stamps the report.

\Psr\Log\LoggerInterface $logger

Records the verdict.

\Drupal\strata\Drill\DrillIndex|null $index

Where the run is recorded, or NULL not to record it.

\Drupal\strata\Event\Notifier|null $notifier

Announces the result, or NULL to stay silent.

Returns

mixed —

run()

run(string|null  $target = null, int  $sample = 0) : \Drupal\strata\Drill\DrillReport

Runs a drill.

Parameters

string|null $target

Commit to replay against, or NULL for the current head.

int $sample

Subjects to examine, or 0 for the default. A negative value examines every subject.

Returns

\Drupal\strata\Drill\DrillReport —

What the drill found.

examine()

examine(string  $subject, string  $commit, int  $at = 0) : array{string, string}

Compares one subject against what the store says it was.

Parameters

string $subject

Subject path, in the form <realm>/<name>.

string $commit

Commit to replay against.

int $at

Unix microseconds the commit was sealed at, or 0 to read it from the commit. Passed in by a whole-drill run, which resolves it once rather than per subject.

Returns

array{string, string} —

The verdict constant and a note.

sealedAt()

sealedAt(string  $commit) : int

When a commit was sealed.

Read from the commit itself rather than from the local index, so a drill still knows the moment on a store whose index has been dropped and not yet rebuilt.

Parameters

string $commit

The commit id.

Returns

int —

Unix microseconds, or 0 when the commit cannot be read.

compare()

compare(\Drupal\strata\Restore\ReplayResult  $result, array  $live) : array{string, string}

Compares a replayed value against the live one.

Only the fields the store claims to know are compared. A field the site has and the capture never recorded is not evidence the store is wrong; it is evidence the capture scope excludes it, which is a different question and the reconciler's.

Parameters

\Drupal\strata\Restore\ReplayResult $result

What the store reconstructed.

array $live

What the site holds.

Returns

array{string, string} —

The verdict constant and a note.

live()

live(string  $subject) : array<string,mixed>|false|null

The live value of a subject, as a field map.

Parameters

string $subject

Subject path.

Returns

array|false|null —

The field map, FALSE when the realm holds values but this one is gone, or NULL when the realm is one a drill cannot read back.

liveEntity()

liveEntity(string  $name) : array<string,mixed>|false|null

The live field map of an entity named by a subject path.

Parameters

string $name

The part after the realm, in the form <entity type>:<id> - a colon, because that is what EntityCapture writes and the slash only separates the realm from the rest.

Returns

array|false|null —

The field map, FALSE when the entity is gone, or NULL when the path is not readable.

liveState()

liveState(string  $key) : array<string,mixed>|false

The live value of a state key, wrapped as a field map.

Parameters

string $key

The state key.

Returns

array|false —

A single-entry map, or FALSE when the key is gone.

normalize()

normalize(mixed  $value) : string

Reduces a value to something two sides can be compared on.

A stored value has been through JSON, so an integer may come back as a string and an object as an array. Comparing the serialized forms rather than the values themselves makes the drill insensitive to that round trip and sensitive to what actually changed.

Parameters

mixed $value

The value.

Returns

string —

A comparable form.

choose()

choose(list  $subjects, int  $sample) : list<string>

The subjects one drill examines.

Chosen by even stride rather than at random, so two drills of the same store cover different subjects only when the store has changed, and a repeated drill of a failing store reproduces the failure rather than hiding it behind a new sample.

Parameters

list $subjects

Every subject the index holds.

int $sample

How many to examine, 0 for the default, negative for all.

Returns

list

The chosen subjects.

record()

record(\Drupal\strata\Drill\DrillReport  $report) : void

Records a report, raising a finding when the store did not reproduce the site.

Parameters

\Drupal\strata\Drill\DrillReport $report

The report.

Returns

void —

refuse()

refuse(string  $reason, float  $started, string  $target = '') : \Drupal\strata\Drill\DrillReport

A report for a drill that could not start.

Parameters

string $reason

Why it did not run.

float $started

When it was attempted.

string $target

The commit it would have used.

Returns

\Drupal\strata\Drill\DrillReport —

The refusal.