Golden port β LAB-1.1.1 (the fidelity proof)ΒΆ
This is the 1:1 port of the real published lablet
deployment/lds/content/LAB-1.1.1/ (legacy RCUv1 XML) into the
Content-Driven Lifecycle DSL. Where
LAB-0.1 is a minimal reference and LAB-0.2
exercises multi-part, LAB-1.1.1 is the fidelity bar: every legacy task β tScp, tVerify
gating, control-node operations, candidate-solution exec, pauses, conditionals, and the connector
model β must round-trip into a primitive with no loss. If a construct cannot be expressed here,
the primitive set (ADR-057 Β§2.2) is
incomplete.
Like the other samples this is a documentation reference, not a deployed seed. It mirrors the artifacts under
deployment/lds/content/LAB-1.1.1/RCUv1/.
Legacy β primitive coverageΒΆ
| Legacy task (RCUv1) | New uses: |
Where |
|---|---|---|
tPause |
pause@v1 |
jobs/post_init.yaml |
tExecute (chunk, out, set, error, if) |
exec@v1 |
post_init, grade |
tExecuteBatch (CDATA) |
exec@v1 (script:) |
grade (candidate solution) |
tScp (from, to, port) |
copy@v1 |
post_init |
tVerify (string, regexp, set) |
evaluate.regex@v1 + when: |
post_init |
verify subject='commandOutput' |
collect@v1 |
grade (collect stage) |
verify subject='parse' (regexp, mode, issue_replace) |
evaluate.regex@v1 |
grade (evaluate stage) |
bounce_interface |
cml.bounce_interface@v1 |
post_init, grade |
cmlctl β¦ --action wipe |
cml.wipe@v1 |
grade (setup) |
cmlctl β¦ --action stop |
cml.power@v1 |
post_init |
dev::tExecute targeting |
target: <connector> |
everywhere |
${config.core.paths.lab_root} / port=5052 / trackNMC50 |
${ content.* } / ${ runtime_env.* } |
all (see ADR-058) |
Phase map (legacy β new)ΒΆ
| Legacy phase | New phase | JobDefinition | process_type |
|---|---|---|---|
init (implicit) |
instantiate |
native + cml.lab_resolve/cml.lab_start |
Initialization |
post_init (sb_post_init.xml) |
post_init |
jobs/post_init.yaml |
Initialization |
pre_collect (sb_pre_collect.xml) |
grade (setup stage) |
jobs/grade.yaml (stage: setup) |
Grading |
grade.xml collect + grade |
grade (collect/evaluate/report) |
jobs/grade.yaml |
Grading |
flowchart LR
A["instantiate\n(native + lab_start)"] --> B["post_init\nseed desktop, bounce, isolate"]
B --> C["grade\nsetup β collect β evaluate β report"]
C --> D["teardown\narchive + wipe"]
style B fill:#0d9488,color:#fff
style C fill:#0d9488,color:#fff
Folder layoutΒΆ
LAB-1.1.1/
βββ PAv1/
βββ manifest.yaml
βββ lifecycle.yaml
βββ connectors.yaml # from RCUv1/pod.xml
βββ topology/
β βββ devices.json # from RCUv1/devices.json
β βββ ports.json # from RCUv1/cml.yaml smart_annotations
βββ jobs/
β βββ post_init.yaml # from RCUv1/sb_post_init.xml
β βββ grade.yaml # from RCUv1/sb_pre_collect.xml + grade.xml
βββ grading/
β βββ rubric.yaml # from RCUv1/grade.xml
βββ reports/
β βββ score_report.yaml
βββ files/
βββ desktop_package.tgz # from RCUv1/desktop_package.tgz
File contentsΒΆ
manifest.yamlΒΆ
# Derived from RCUv1/mosaic_meta.json.
apiVersion: pav1
kind: LabletDefinition
metadata:
name: LAB-1.1.1
title: "350-901 AUTOCOR β Lablet 1.1.1"
track: "350-901 AUTOCOR" # TrackLongName
track_short: AUTOCOR # TrackShortName
exam: "350-901"
language: ENU
content_version: "32" # mosaic_meta Version
spec:
pod_type: CML_ON_AWS
summary: >
Two routers + two switches + a workstation. Ports VLAN/ACL/OSPF/NTP grading and the full
post_init desktop-seed + control-node isolation flow from the legacy RCUv1 content.
lifecycle.yamlΒΆ
# Orchestration only (CPA owns phase ORDER; SE owns each job BODY in jobs/).
apiVersion: pav1
kind: Lifecycle
metadata:
lablet: LAB-1.1.1
spec:
phases:
- name: instantiate
native_steps_by_pod_type:
cml_on_aws: [worker_lab_resolve, pod_locator, ports_alloc, lds_register]
jobs:
- definition: cml.lab_start@v1 # resolve+start handled as native cml.* steps
- name: post_init
jobs:
- definition: post_init@v1 # -> jobs/post_init.yaml
process_type: Initialization
- name: grade
jobs:
- definition: grade@v1 # -> jobs/grade.yaml (setupβcollectβevaluateβreport)
process_type: Grading
rubric: rubric # -> grading/rubric.yaml
report: score_report # -> reports/score_report.yaml
- name: teardown
native_steps_by_pod_type:
cml_on_aws: [archive]
jobs:
- definition: cml.wipe@v1
process_type: Archive
connectors.yamlΒΆ
# 1:1 from RCUv1/pod.xml unit-template/connector. Facts (ports/prompts/creds) come from
# runtime_env.* at submit time (ADR-058) β never literal here.
apiVersion: pav1
kind: ConnectorModel
metadata:
name: LAB-1.1.1
spec:
connectors:
- name: rtr01
class: cisco_common
transport: telnet
timeout: 3
prompt: "${ runtime_env.devices.rtr01.prompt }" # rtr01#
enable_password: "${ runtime_env.devices.rtr01.enable_password }" # cisco
port: "${ runtime_env.devices.rtr01.serial_port }"
- name: rtr02
class: cisco_common
transport: telnet
timeout: 3
prompt: "${ runtime_env.devices.rtr02.prompt }" # rtr02#
enable_password: "${ runtime_env.devices.rtr02.enable_password }"
port: "${ runtime_env.devices.rtr02.serial_port }"
- name: sw01
class: cisco_common
transport: telnet
timeout: 3
prompt: "${ runtime_env.devices.sw01.prompt }" # sw01#
enable_password: "${ runtime_env.devices.sw01.enable_password }"
port: "${ runtime_env.devices.sw01.serial_port }" # 5048
- name: sw02
class: cisco_common
transport: telnet
timeout: 3
prompt: "${ runtime_env.devices.sw02.prompt }" # sw02#
enable_password: "${ runtime_env.devices.sw02.enable_password }"
port: "${ runtime_env.devices.sw02.serial_port }" # 5049
- name: workstation_22
class: unix
transport: ssh # UnixSSH via PAT
timeout: 5
via_port: "${ runtime_env.devices.workstation.pat_port }" # 5052 -> 22
username: "${ runtime_env.devices.workstation.username }" # cisco
password: "${ runtime_env.devices.workstation.password }" # cisco
prompt: "#goodmorningstartshine#"
- name: workstation_serial
class: unix
transport: telnet # serial console
timeout: 5
port: "${ runtime_env.devices.workstation.serial_port }"
username: "${ runtime_env.devices.workstation.username }"
password: "${ runtime_env.devices.workstation.password }"
prompt: "#goodmorningstartshine#"
- name: control_node
class: control # cmlctl-0; used only by cml.*
transport: telnet
timeout: 5
port: "${ runtime_env.control_node.serial_port }"
topology/devices.jsonΒΆ
{
"default_instance_type": "m5zn.metal",
"device_type": "cml",
"ami_name": "cisco-cml2.9.1-20nodes-v0.2.2c",
"disk_type": "io1",
"disk_size": 256,
"public_ip": 1,
"lab_type": "lablet"
}
topology/ports.jsonΒΆ
{
"comment": "Per-device serial/vnc/pat ports (from RCUv1/cml.yaml smart_annotations).",
"ports": [
{ "device": "rtr01", "serial": 5045 },
{ "device": "rtr02", "serial": 5046 },
{ "device": "sw01", "serial": 5048 },
{ "device": "sw02", "serial": 5049 },
{ "device": "workstation", "serial": 5050, "vnc": 5051, "pat": { "5052": 22 } },
{ "device": "control_node", "serial": 5047 }
]
}
jobs/post_init.yamlΒΆ
A direct port of sb_post_init.xml. The legacy set='CMD1.OK' / if='CMD1.OK' flags become
capture: + when: gates; tVerify becomes evaluate.regex; tScp becomes copy; the
control-node ops become cml.*.
apiVersion: pav1
kind: JobDefinition
metadata:
name: post_init
version: v1
spec:
process_type: Initialization
steps:
# tPause (id=1)
- id: settle
uses: pause@v1
with: { seconds: 30 }
# tExecute id=3 β mkdir tmp dir on the workstation (SSH via PAT)
- id: mkdir_tmp
uses: exec@v1
target: workstation_22
with: { command: "mkdir -p /home/cisco/Desktop/tmp/" }
capture: { ok: cmd0_ok, error: cmd0_err }
# tScp id=5 β push the desktop package over the PAT port
- id: push_package
uses: copy@v1
target: workstation_22
with:
source: "${ content.files.desktop_package }" # was ${config.core.paths.lab_root}/desktop_package.tgz
dest: "/home/cisco/Desktop/tmp/desktop_package.tgz"
via_port: "${ runtime_env.devices.workstation.pat_port }" # was port=5052
capture: { ok: scp_ok }
# tExecute id=10 β list tmp dir, capture as vars.files
- id: list_tmp
uses: exec@v1
target: workstation_22
with: { command: "ls -la /home/cisco/Desktop/tmp/" }
capture: { stdout: files, ok: cmd1_ok }
# tVerify id=30 β gate: is the package present? (if=CMD1.OK)
- id: verify_package
uses: evaluate.regex@v1
when: "${ vars.cmd1_ok }"
with:
source: "${ vars.files }" # was string="{files}"
regex: "desktop_package\\.tgz"
mode: positive
capture: { passed: file_ok }
# tExecute id=40 β unpack (if=file.OK)
- id: unpack
uses: exec@v1
target: workstation_22
when: "${ vars.file_ok }"
with: { command: "tar -v -C /home/cisco/Desktop/tasks/ -xzf /home/cisco/Desktop/tmp/desktop_package.tgz" }
timeout: 10
capture: { stdout: tgz_out, ok: cmd2_ok }
# tExecute id=45 β remove distractions
- id: cleanup_tmp
uses: exec@v1
target: workstation_22
with: { command: "rm -rf /home/cisco/Desktop/tmp/" }
timeout: 10
capture: { ok: cmd45_ok }
# tExecute id=50/51 β bounce Vl99 on both switches (control node)
- id: bounce_sw01
uses: cml.bounce_interface@v1
with:
device: sw01
interface: Vl99
serial_port: "${ runtime_env.devices.sw01.serial_port }" # was 5048
timeout: 15
capture: { ok: sw01_bounce }
- id: bounce_sw02
uses: cml.bounce_interface@v1
with:
device: sw02
interface: Vl99
serial_port: "${ runtime_env.devices.sw02.serial_port }" # was 5049
timeout: 15
capture: { ok: sw02_bounce }
# tExecute id=70 β shut down Internet access (cmlctl --action stop on ext-conn-0)
- id: isolate_internet
uses: cml.power@v1
with:
node: ext-conn-0
action: stop # cml_password resolved from runtime_env
timeout: 15
capture: { ok: cmd4_ok }
jobs/grade.yamlΒΆ
sb_pre_collect.xml becomes the setup stage; grade.xml's verify commandOutput becomes the
collect stage; verify parse becomes the evaluate stage; the report is the terminal
report.score. The 9 subsections / 10 points live in grading/rubric.yaml (referenced by the
evaluate stage), keeping the job body compact.
apiVersion: pav1
kind: JobDefinition
metadata:
name: grade
version: v1
spec:
process_type: Grading
steps:
# ---- setup stage (was sb_pre_collect.xml) ----
# id=10 β wipe candidate devices
- id: wipe_devices
uses: cml.wipe@v1
stage: setup
with: { devices: [rtr01, rtr02, sw01, sw02] }
timeout: 120
capture: { ok: wipe_ok }
# id=20/21 β bounce Vl99 on both switches
- id: bounce_sw01
uses: cml.bounce_interface@v1
stage: setup
with: { device: sw01, interface: Vl99, serial_port: "${ runtime_env.devices.sw01.serial_port }" }
timeout: 30
- id: bounce_sw02
uses: cml.bounce_interface@v1
stage: setup
with: { device: sw02, interface: Vl99, serial_port: "${ runtime_env.devices.sw02.serial_port }" }
timeout: 30
# id=30 β run the candidate's python deployment (serial console, venv activate/deactivate)
- id: candidate_py_deploy
uses: exec@v1
stage: setup
target: workstation_serial
with:
script: |
cd /home/cisco/Desktop/tasks/
source venv/bin/activate
python ./scripts/py_deploy.py -i ./scripts/py_inventory.yaml
deactivate
timeout: 15
on_error: { action: continue } # legacy only logged error=30.error.msg
# id=31 β run the candidate's ansible playbook
- id: candidate_ansible
uses: exec@v1
stage: setup
target: workstation_serial
with:
script: |
cd /home/cisco/Desktop/tasks/
source venv/bin/activate
./ansible/run-playbook.sh
deactivate
timeout: 15
on_error: { action: continue }
# ---- collect stage (was grade.xml verify subject='commandOutput') ----
- { id: c_sw01_vlan, uses: collect@v1, stage: collect, target: sw01, with: { command: "show vlan brief" }, capture: { output: sw01.show_vlan_brief } }
- { id: c_sw02_vlan, uses: collect@v1, stage: collect, target: sw02, with: { command: "show vlan brief" }, capture: { output: sw02.show_vlan_brief } }
- { id: c_rtr01_acl, uses: collect@v1, stage: collect, target: rtr01, with: { command: "show access-list" }, capture: { output: rtr01.show_access_list } }
- { id: c_rtr02_acl, uses: collect@v1, stage: collect, target: rtr02, with: { command: "show access-list" }, capture: { output: rtr02.show_access_list } }
- { id: c_rtr01_gi, uses: collect@v1, stage: collect, target: rtr01, with: { command: "show interface gi0/1" }, capture: { output: rtr01.show_int_gi01 } }
- { id: c_rtr02_gi, uses: collect@v1, stage: collect, target: rtr02, with: { command: "show interface gi0/1" }, capture: { output: rtr02.show_int_gi01 } }
- { id: c_rtr01_lo, uses: collect@v1, stage: collect, target: rtr01, with: { command: "show interface Loopback0" }, capture: { output: rtr01.show_int_loop0 } }
- { id: c_rtr02_lo, uses: collect@v1, stage: collect, target: rtr02, with: { command: "show interface Loopback0" }, capture: { output: rtr02.show_int_loop0 } }
- { id: c_rtr01_ospf, uses: collect@v1, stage: collect, target: rtr01, with: { command: "show ip ospf neighbor" }, capture: { output: rtr01.show_ip_ospf_nei } }
- { id: c_rtr01_ntp, uses: collect@v1, stage: collect, target: rtr01, with: { command: "show ntp associations" }, capture: { output: rtr01.show_ntp_assoc } }
- { id: c_rtr02_ntp, uses: collect@v1, stage: collect, target: rtr02, with: { command: "show ntp associations" }, capture: { output: rtr02.show_ntp_assoc } }
# ---- evaluate + report stage ----
# The evaluate stage applies grading/rubric.yaml (each rubric check -> evaluate.regex@v1)
# against the captured vars.* above, then report.score assembles the ScoreReport.
- id: evaluate
uses: evaluate.regex@v1 # ruleset-driven: one evaluate.regex per rubric check
stage: evaluate
with: { ruleset: rubric } # -> grading/rubric.yaml
capture: { items: graded_items }
- id: report
uses: report.score@v1
stage: report
with:
items: "${ vars.graded_items }"
report_class: ScoreReport # legacy reportClass='Reports::LabletReport'
capture: { report_ref: report_ref }
Note on the evaluate stage. Rather than spelling out 14
evaluate.regexsteps inline, the single ruleset-drivenevaluatestep expandsgrading/rubric.yamlinto oneevaluate.regex@v1invocation per check (same primitive, samesource/regex/mode/issuefields). This keeps the job body compact while remaining 1:1 withgrade.xml'sverify subject='parse'rows.
grading/rubric.yamlΒΆ
Direct translation of grade.xml section 1 "Content" β 10 points, 9 subsections. Each check
maps to one legacy verify subject='parse' (regexp β regex, mode β mode, issue_replace
β on_fail). match='/^(.*)$/msig' on the legacy commandOutput verify is captured by the
collect step, so only the parse checks live here.
apiVersion: pav1
kind: EvaluationRuleset
metadata:
name: rubric
spec:
domain: "1.0 Network Automation"
max_points: 10
items:
- id: 1
points: 1
description: "sw01 must have VLAN 80"
checks:
- { source: sw01.show_vlan_brief, regex: '^80\s+LAB80', mode: positive, flags: [multiline] }
- id: 2
points: 1
description: "sw02 must have VLAN 80"
checks:
- { source: sw02.show_vlan_brief, regex: '^80\s+LAB80', mode: positive, flags: [multiline] }
- id: 3
points: 1
description: "rtr01 must have an access-list MGMTOPS configured"
checks:
- { source: rtr01.show_access_list, regex: 'MGMTOPS', mode: positive, flags: [multiline], on_fail: "MGMTOPS ACL not found" }
- id: 4
points: 1
description: "rtr02 must have an access-list MGMTOPS configured"
checks:
- { source: rtr02.show_access_list, regex: 'MGMTOPS', mode: positive, flags: [multiline], on_fail: "MGMTOPS ACL not found" }
- id: 5
points: 1
description: "Interface descriptions present (rtr01<->rtr02)"
checks:
- { source: rtr01.show_int_gi01, regex: 'Description: to-rtr02', mode: positive, flags: [multiline], on_fail: "Description: to-rtr02 NOT found in rtr01.show_int_gi0.1" }
- { source: rtr02.show_int_gi01, regex: 'Description: to-rtr01', mode: positive, flags: [multiline], on_fail: "Description: to-rtr01 NOT found in rtr02.sh_int_gi0.1" }
- id: 6
points: 1
description: "rtr01 Loopback0 ip address"
checks:
- { source: rtr01.show_int_loop0, regex: 'Loopback0 is up, line protocol is up', mode: positive, flags: [multiline], on_fail: "Lo0 is not up/up" }
- { source: rtr01.show_int_loop0, regex: 'Internet address is 10\.255\.81\.1/32', mode: positive, flags: [multiline], on_fail: "Internet address is 10.255.81.1/32 was not found." }
- id: 7
points: 1
description: "rtr02 Loopback0 ip address"
checks:
- { source: rtr02.show_int_loop0, regex: 'Loopback0 is up, line protocol is up', mode: positive, flags: [multiline], on_fail: "Lo0 is not up/up" }
- { source: rtr02.show_int_loop0, regex: 'Internet address is 10\.255\.81\.2/32', mode: positive, flags: [multiline], on_fail: "Internet address is 10.255.81.2/32 was not found." }
- id: 8
points: 2
description: "rtr01 has one OSPF neighbor in FULL state"
checks:
- { source: rtr01.show_ip_ospf_nei, regex: 'FULL.*GigabitEthernet0/1', mode: positive, flags: [multiline, ignorecase], on_fail: "OSPF neighbor in state FULL not found" }
- id: 9
points: 1
description: "rtr01 and rtr02 must have NTP server 192.168.10.254"
checks:
- { source: rtr01.show_ntp_assoc, regex: '192\.168\.10\.254', mode: positive, flags: [multiline] }
- { source: rtr02.show_ntp_assoc, regex: '192\.168\.10\.254', mode: positive, flags: [multiline] }
reports/score_report.yamlΒΆ
apiVersion: pav1
kind: ProcessReportSpec
metadata:
name: score_report
spec:
process_type: Grading
report_class: ScoreReport # legacy: Reports::LabletReport
include:
- per_item: [id, description, points_awarded, points_max, passed, issues]
- totals: [points_awarded, points_max, percentage]
Fidelity checklistΒΆ
- β
tPauseβpause@v1(settle) - β
tExecute(capture + gate) βexec@v1withcapture/when - β
tScp(PAT port) βcopy@v1withvia_portfromruntime_env.* - β
tVerifygate βevaluate.regex@v1+ downstreamwhen: - β
control-node
bounce_interface/cmlctl --action wipe/--action stopβcml.bounce_interface/cml.wipe/cml.power - β
candidate solutions (
py_deploy.py,run-playbook.sh) βexec@v1(script) onworkstation_serial - β
verify commandOutputβcollect@v1;verify parseβevaluate.regex@v1 - β
10 points / 9 subsections preserved in
grading/rubric.yaml - β
connector model (Telnet / UnixSSH / serial / control node) β
connectors.yaml - β
zero hard-coded ports or passwords β all resolved from
runtime_env.*(ADR-058)