Chapter 6 — Landscape Activation
Volume 1 chapter 9 carried the landscape boundary at the contract level:
TestJob's fivefire_*hooks are no-ops on single-host; subclasses give them bodies. This chapter walks the bodies. When the framework "activates" a landscape, what actually happens? Three layers, each with a job, each with failure modes the next layer can't recover.
Landscape.activate_configuration(),
Landscape.activate_integration(), and
Landscape.activate_operations() are the three method calls
InteropLibraryTestJob makes during a distributed test run.
Each delegates to a corresponding layer on the Landscape
class:
activate_configurationdelegates toLandscapeConfigurationLayer.activate_integrationdelegates toLandscapeIntegrationLayer.activate_operationsdelegates toLandscapeOperationalLayer.
The three layers are the framework's separation of concerns for landscape activation: load files → instantiate objects → bring online and verify. Each layer has a clear responsibility and a clear failure mode.
The three layers at a glance
| Layer | Responsibility | Output | Failure mode |
|---|---|---|---|
| Configuration | Load Landscape and Topology files; initialise credentials; let integration couplings inspect config | landscape_info, topology_info, CredentialManager populated |
Missing files; schema errors; unresolved credentials |
| Integration | Instantiate device, service, power, serial objects from the loaded config | _integrated_devices, _integrated_services, etc. populated |
Unknown deviceTypes; misconfigured Features; coordinator-init errors |
| Operational | Activate coordinators; attach extensions; verify connectivity | Devices online and reachable | Unreachable devices; failed authentication; service-side errors |
The order matters. Each layer requires the previous layer's
output. An attempted operational activation against a
landscape whose integration layer didn't produce devices
fails immediately. An integration activation against a
landscape whose configuration layer didn't load fails
immediately. The framework enforces the order through the
Landscape class's three methods, each of which checks its
predecessor's state.
Layer 1 — LandscapeConfigurationLayer
File: codecraftai-landscaping/source/packages/ccai/landscaping/layers/landscapeconfigurationlayer.py:51
The configuration layer is the "load and validate" phase. It owns the on-disk representation of the landscape and topology, plus the credentials and runtime references they contain.
Key methods
-
load_landscape(activation_params)— reads theLANDSCAPE_CONFIGURATION_MAP(populated earlier bycodecraftai-configuration's loader) and stores it asself._landscape_info. -
load_topology(activation_params)— same forTOPOLOGY_CONFIGURATION_MAP, stored asself._topology_info. Line 267. -
initialize_credentials()— instantiatesCredentialManagerfor the loaded credentials. Anything the test job will need to authenticate against must resolve here. -
attach_to_environment()(lines 201–219) — gives every installed integration coupling a chance to inspect the configuration before device creation. Quoted:def attach_to_environment(self): lscape = self.landscape # During the landscape initialization and before we start creating devices, give # all of the installed coordinator couplings and opportunity to peek at the configuration # and perform any special validation or internal initialization, this also provides the # integrations with a first glance at the test landscape after the configuration files # have been loaded. integ_coupling_table = lscape.installed_integration_couplings for integ_key in integ_coupling_table: integ_coupling = integ_coupling_table[integ_key] integ_coupling.attach_to_environment(lscape) return
-
validate_landscape()andvalidate_topology()— structural validation hooks. The shippedvalidate_topology()(line 536) is a stub returning empty errors / warnings; the user overrides it in aLandscapesubclass to enforce project-specific schema.
What can go wrong here
- Missing files. If
AM_CONFIG_LANDSCAPE_NAMESreferences a name thatConfigurationLoadercannot find, load fails before any device is created. This is the cleanest failure to diagnose — the path is explicit, the resolver explicitly reports what it tried. - Schema errors. YAML or JSON parse errors land here.
- Credential resolution failures. A credential name in the
Landscape that doesn't resolve through any configured backend
fails here. The test job sees a
ConfigurationErrorand refuses to proceed.
The configuration layer is fast. Loading is filesystem + parse + dict-walk. No network calls. No device probing. Failure here means the CI system handed the job configuration that doesn't even parse — a clear "your inputs are wrong" signal.
Layer 2 — LandscapeIntegrationLayer
File: codecraftai-landscaping/source/packages/ccai/landscaping/layers/landscapeintegrationlayer.py:43
The integration layer is the "instantiate objects" phase.
It walks the loaded landscape_info and constructs in-memory
representations of every device, service, power controller,
and serial coordinator the landscape declares.
Key method
initialize_landscape(activation_params) at lines 295–341 is
the layer's entry point. Its body:
def initialize_landscape(self, activation_params: LandscapeActivationParams):
lscape = self.landscape
devices = None
with lscape.begin_locked_landscape_scope() as locked:
layer_config = lscape.layer_configuration
if layer_config.landscape_info is not None:
if not activation_params.disable_device_activation:
# Initialize the devices so we know what they are...
devices = self._initialize_landscape_devices(layer_config)
if self._power_request_count > 0:
self._initialize_landscape_power(layer_config)
if self._serial_request_count > 0:
self._initialize_landscape_serial(layer_config)
else:
self.logger.info("LandscapeIntegrationLayer: 'Device Activation' was disabled.")
devices = {}
if not activation_params.disable_service_activation:
services = self._initialize_landscape_services(layer_config)
else:
self.logger.info("LandscapeIntegrationLayer: 'Service Activation' was disabled.")
services = {}
self._integrated_devices = devices.copy()
self._integrated_services = services.copy()
Three things to notice:
- The lock.
begin_locked_landscape_scope()is held during initialisation to prevent concurrent activation from multiple threads (a real concern with theLandscapeSingletonpattern). - The activation flags.
disable_device_activationanddisable_service_activationareLandscapeActivationParamsfields letting the test job skip device/service initialisation when only the configuration layer is needed (e.g., for inspection or audit). The default is to activate both. - The on-demand power and serial init. The integration
layer only initialises power and serial subsystems if at
least one device requested them (tracked via
_power_request_countand_serial_request_count). Pods without power-control devices skip the power init entirely.
What gets created
After initialize_landscape returns:
integrated_devices— a thread-safe dict ofdevice_name → LandscapeDeviceinstances. Each device knows its deviceType, host, credentials, Features, and the ProtocolExtensions it should expose.integrated_services— same for services.integrated_power— power-controller objects.integrated_serial— serial-coordinator objects.
The objects exist but are not yet online. No SSH session has been opened. No serial port has been probed. The integration layer is a graph-construction phase; the operational layer is the first-contact phase.
What can go wrong here
- Unknown deviceTypes. A Landscape declaring
deviceType: network/specialdeviceagainst a framework that has no Coordinator registered fornetwork/specialdevicefails here. The Volume 0 chapter 7 factory model is what determines whether a deviceType is known; a project adding a new deviceType registers a Coordinator subclass through the factory protocol. - Misconfigured Features. A Feature that references a
service-name that doesn't exist in the
servicessection fails here. The integration layer's_initialize_landscape_deviceswalks every device's Features and looks up service references; a missing service is aConfigurationError. - Coordinator-init errors. A Coordinator subclass's own
__init__failing surfaces here.
Layer 3 — LandscapeOperationalLayer
File: codecraftai-landscaping/source/packages/ccai/landscaping/layers/landscapeoperationallayer.py:41
The operational layer is where landscape activation contacts the real world. It activates each Coordinator (which brings its devices online), attaches ProtocolExtensions to devices, and verifies connectivity.
Key method
establish_connectivity(activation_params) at lines 391–480 is
the layer's verification entry point. Excerpted:
def establish_connectivity(self, activation_params: LandscapeActivationParams):
lscape = self.landscape
layer_integ = lscape.layer_integration
failures = []
coordinators_for_services = layer_integ.coordinators_for_services
for coord in coordinators_for_services.values():
results = coord.establish_connectivity(activation_params)
for item in results:
host, ipaddr, status, stdout, stderr, exc = item
if exc is not None:
failures.append(item)
elif status is not None:
if isinstance(status, bool):
if not status:
failures.append(item)
elif status != 0:
failures.append(item)
# ... similar loops for power, serial, devices ...
if len(failures) > 0:
errmsg_lines = [
"Errors encountered while establishing connectivity with the landscape.",
"FAILURES:"
]
for item in failures:
host, ipaddr, status, stdout, stderr, exc = item
if exc is not None:
errmsg_lines.append(f" Host: {host} IP: {ipaddr} Exception: {exc}")
else:
errmsg_lines.append(f" Host: {host} IP: {ipaddr} Status: {status} Stderr: {stderr}")
errmsg = os.linesep.join(errmsg_lines)
if not activation_params.allow_missing_devices:
raise RuntimeError(errmsg)
else:
self.logger.error(errmsg)
return
Three properties of the verification:
- The result tuple shape. Each coordinator's
establish_connectivityreturns a list of(host, ipaddr, status, stdout, stderr, exc)tuples. The tuple captures: which host, what IP it resolved to, what status code the probe returned, stdout / stderr from the probe, and any exception caught. This is the primary forensic record for "why is host X unreachable." - The
allow_missing_devicesflag. When False (the default), any failure raisesRuntimeErrorand the test job refuses to proceed. When True (typical for smoke runs), failures are logged and the run continues with missing devices. - The error message construction. The framework follows
the Volume 0 chapter 2 multi-line-error-message discipline:
build a list of lines, format with
os.linesep.join, raise. An SDET reading the error gets a structured failure report with the host, IP, status, and stderr — enough to triage without re-running.
What gets activated
activate_coordinators(activation_params) runs before
establish_connectivity. For each Coordinator:
- The Coordinator's
activate()method runs. This typically:- Walks each device the Coordinator manages.
- Probes the device for additional metadata (model number, OS version, hardware info).
- Attaches the appropriate ProtocolExtensions to the device
(
device.ssh,device.serial,device.power).
- Per-device features that need the Coordinator's bridging (e.g., a serial console reachable through a serial concentrator) get their ProtocolExtensions wired through the Coordinator's registry.
After activate_coordinators plus establish_connectivity
succeed, the Pod is operationally live. Devices respond to
calls; ProtocolExtensions are attached; service references
resolve; credentials work.
What can go wrong here
- Unreachable devices. Network down, host crashed, firewall blocking, wrong port — all surface as failures in the result tuples.
- Failed authentication. Credentials resolved (layer 1),
ProtocolExtensions attached (above), but the actual SSH
authentication failed. Surface as
SshAuthErrorin the exc field. - Timeout exceeded. The Coordinator's probe took too long
— either a long-running connection attempt hung, or the
device is up but the probe is slow. Surface as
TimeoutError.
The order: why each phase requires the previous
Volume 0 chapter 4's protocol discipline shows up here. The three layers are strictly ordered:
- Configuration must succeed before Integration (the
integration layer reads
landscape_infofrom the configuration layer). - Integration must succeed before Operational (the operational layer iterates the integrated coordinators from the integration layer).
- Operational must succeed before tests run (a test against a Pod whose devices aren't online and reachable is not deterministic — chapter 2's commitment).
The framework enforces the order by checking state at each
layer's entry. An out-of-order activation surfaces a
SemanticError (Volume 0 chapter 3) — the framework refuses
to run the operational layer if integration didn't produce
devices, refuses to run integration if configuration didn't
load, etc.
Activation parameters
LandscapeActivationParams
(codecraftai-landscaping/source/packages/ccai/landscaping/landscapeparameters.py:19)
is the dataclass that controls activation:
class LandscapeActivationParams(SimpleNamespace):
disable_device_activation: bool=False
disable_service_activation: bool=False
allow_missing_devices: bool=False
allow_unknown_devices: bool=False
allow_missing_services: bool=False
allow_unknown_services: bool=False
validate_features: bool=True
validate_topology: bool=True
Each flag answers a real question:
disable_device_activation— skip device activation entirely. Useful for test jobs that only inspect configuration.allow_missing_devices— proceed even when some devices fail to come online. Useful for smoke runs.validate_topology— run the topology validator before proceeding. DefaultTrue; chapter 7 walks the verification contract that hangs on this flag.
The default values are strict: activation requires every device and service to come up, every Feature to validate, every Topology assertion to verify. A project running a deliberate smoke / partial run flips the flags explicitly; the strict defaults ensure no production run accidentally runs against a half-broken Pod.
What this means for the verification contract
Chapter 7 walks the verification contract as it appears at the orchestration / job seam — what the CI system has promised the job, and how the job verifies the promise. The mechanism of verification is what this chapter just walked. Specifically:
- Landscape verification =
establish_connectivityreturning no failures. Every device declared in the Landscape can be reached at its declared address with its declared protocol using its declared credentials. - Topology verification = the user's
validate_topology()override returning no errors. The shipped stub returns empty; a project'sTopologysubclass overrides it to check the project-specific Topology shape.
If either fails (and the activation params are strict), the test job refuses to run. The Pod is not realised. Tests do not start.
What the next chapter does
Chapter 7 walks the verification contract at the orchestration-to-job seam. The CI system says "here are your resources" (the Landscape) and "here is the configured state" (the Topology); the test job verifies both before the first test runs. This chapter walked the layers that do the work; chapter 7 walks the contract the layers honour.