The JN0-224 exam leads to JNCIA-DevOps (HPE Juniper Networks Certified Associate, Automation and DevOps). It's the entry exam for engineers who want to manage Junos devices with code instead of the CLI. It isn't a programming exam in the computer-science sense: you won't write algorithms. What it checks is whether you can read a short PyEZ script, an XPath expression, a YAML file or a curl call and say what it will do. The quickest way to get there is to write those pieces yourself, so this guide follows the blueprint with small labs you can run against a vJunos or vSRX instance.
JN0-224 exam at a glance
| Item | Detail |
|---|---|
| Exam code | JN0-224 |
| Certification | JNCIA-DevOps (Automation and DevOps, Associate) |
| Questions | 65 multiple choice |
| Duration | 90 minutes |
| Passing score | Pass/fail, roughly 60–70% |
| Price | USD 200 |
| Delivery | Pearson VUE |
| Software versions | Junos 24.2, Python 3.8.10, PyEZ 2.6.3 |
| Recommended course | Introduction to Junos Platform Automation and DevOps (IJAUT) |
That works out to about 83 seconds per question, enough time to read a 15-line code sample carefully. Check the official JNCIA-DevOps certification page before booking, because Juniper updates the software versions the exam is based on.
The five blueprint domains
- Junos automation stack and DevOps concepts: tools, frameworks, APIs and DevOps culture.
- NETCONF/XML API: XML syntax, XPath, NETCONF and the Junos XML API.
- Data serialization: YAML and JSON.
- Python/PyEZ: syntax, JSNAPy, Jinja2, RPCs, exception handling, device status and configuration.
- REST API: the Junos REST API, the REST API Explorer and curl.
Python/PyEZ carries the most objectives and draws on the other four, so the labs below spend most of their time there.
Lab setup
One virtual Junos device is enough. Enable the services the exam covers:
set system services ssh
set system services netconf ssh
set system services rest http port 3000
set system services rest enable-explorer
set system login user automation class super-user authentication plain-text-password
commit
On your workstation:
python3 -m venv jncia && source jncia/bin/activate
pip install junos-eznc jsnapy
Domain 1: The Junos automation stack
Expect questions that ask you to place a tool in the stack:
| Layer | Examples | Runs where |
|---|---|---|
| On-box scripting | Commit, op and event scripts (SLAX or Python) | On the Junos device |
| APIs | NETCONF/XML API, Junos REST API, gRPC/gNMI telemetry | Device exposes them |
| Libraries | Junos PyEZ (Python) | Off-box, your workstation |
| Frameworks | Ansible (juniper.device collection), Salt |
Off-box |
| Testing | JSNAPy | Off-box |
The DevOps questions are conceptual: version control for configuration, small and frequent changes, automated testing before and after a change, and shared ownership between network and development teams. If an answer choice describes a manual, one-off process, it is usually the wrong one.
Domain 2: NETCONF, XML and XPath
NETCONF (RFC 6241) runs over SSH, by default on port 830, and its operations work on configuration datastores. Junos uses the candidate configuration model you already know from the CLI. The core operations are <get-config>, <edit-config>, <lock>, <unlock>, <commit> and <close-session>. Junos also accepts its own XML RPCs for every operational command.
The trick that answers many exam questions is that any CLI command shows its XML RPC and its XML output:
user@vjunos> show interfaces terse ge-0/0/0 | display xml rpc
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/24.2R1/junos">
<rpc>
<get-interface-information>
<terse/>
<interface-name>ge-0/0/0</interface-name>
</get-interface-information>
</rpc>
</rpc-reply>
From that output you can derive the PyEZ method name: replace hyphens with underscores, so get-interface-information becomes get_interface_information. Empty tags such as <terse/> become terse=True.
XPath selects nodes from the XML reply. These are the patterns worth memorising:
| XPath | Selects |
|---|---|
//physical-interface |
Every physical-interface element at any depth |
physical-interface/name |
The name child of each physical-interface |
//physical-interface[oper-status='down']/name |
Names of interfaces that are down |
//logical-interface[1] |
The first logical-interface under each parent |
@junos:style |
An attribute, not an element |
Remember that Junos XML text often carries newline whitespace, so compare with normalize-space() or call .strip() in Python.
Domain 3: YAML and JSON
Questions show a document and ask whether it is valid, or what data type a value is. Here is the same data both ways:
---
devices:
- name: core1
host: 192.0.2.10
interfaces: [ge-0/0/0, ge-0/0/1]
ospf: true
{
"devices": [
{"name": "core1", "host": "192.0.2.10",
"interfaces": ["ge-0/0/0", "ge-0/0/1"], "ospf": true}
]
}
Things that trip people up: YAML indentation must use spaces, not tabs. A dash starts a list item. In YAML, yes, no, on and off may load as booleans unless quoted. JSON needs double quotes and allows no trailing commas. Junos can produce JSON directly with | display json.
Domain 4: Python and PyEZ
Connect, read facts and call an RPC
from jnpr.junos import Device
with Device(host="192.0.2.10", user="automation", passwd="lab123") as dev:
print(dev.facts["hostname"], dev.facts["model"], dev.facts["version"])
reply = dev.rpc.get_interface_information(terse=True, interface_name="ge-0/0/*")
for ifd in reply.findall("physical-interface"):
name = ifd.findtext("name").strip()
status = ifd.findtext("oper-status").strip()
print(f"{name:<12} {status}")
Points the exam likes: Device uses NETCONF over SSH (port 830 by default), the context manager opens and closes the session for you, dev.facts is collected when the session opens, and RPC replies are lxml elements, so findall, findtext and xpath all work on them. To get output in another format, pass {"format": "json"} or {"format": "text"} as the first positional argument to the RPC call.
Change configuration safely with Config and Jinja2
{# interfaces.j2 #}
{% for ifc in interfaces %}
set interfaces {{ ifc.name }} description "{{ ifc.desc }}"
set interfaces {{ ifc.name }} unit 0 family inet address {{ ifc.ip }}
{% endfor %}
from jnpr.junos import Device
from jnpr.junos.utils.config import Config
from jnpr.junos.exception import (ConnectError, LockError,
ConfigLoadError, CommitError)
data = {"interfaces": [
{"name": "ge-0/0/1", "desc": "to-core2", "ip": "10.0.12.1/30"},
]}
try:
with Device(host="192.0.2.10", user="automation", passwd="lab123") as dev:
with Config(dev, mode="exclusive") as cu:
cu.load(template_path="interfaces.j2", template_vars=data, format="set")
cu.pdiff()
if cu.commit_check():
cu.commit(comment="JNCIA-DevOps lab: interface update")
except ConnectError as err:
print(f"Cannot connect: {err}")
except LockError:
print("Configuration is locked by another user")
except (ConfigLoadError, CommitError) as err:
print(f"Change rejected: {err}")
Know what each step does. mode="exclusive" locks the candidate configuration (the same as configure exclusive). load() accepts set, text (curly-brace) or xml formats. pdiff() prints the same diff as show | compare. commit_check() validates without activating. rollback() discards uncommitted changes.
The exception hierarchy is a common question. ConnectAuthError, ConnectTimeoutError and ConnectRefusedError are all subclasses of ConnectError, so catching ConnectError covers them all. Configuration problems raise ConfigLoadError, LockError or CommitError, and RPC failures raise RpcError or RpcTimeoutError.
JSNAPy: test before and after a change
JSNAPy takes snapshots of operational state and compares them. A test file looks like this:
# test_interfaces.yml
tests_include:
- check_interfaces_up
check_interfaces_up:
- command: show interfaces terse ge-0/0/*
- iterate:
xpath: physical-interface
id: name
tests:
- is-equal: oper-status, up
err: "Interface <{{id_0}}> is {{post['oper-status']}}"
info: "Interface <{{id_0}}> is up"
Use jsnapy --snap pre, make the change, run jsnapy --snap post, then jsnapy --check pre post to compare. --snapcheck takes one snapshot and runs the tests against it straight away. The difference between --check (compares two snapshots) and --snapcheck (tests one snapshot) is a favourite exam question.
Domain 5: The Junos REST API
With the configuration from the lab setup, every RPC is available over HTTP:
# Single RPC, XML reply (the default)
curl -u automation:lab123 http://192.0.2.10:3000/rpc/get-software-information
# Same RPC, JSON reply
curl -u automation:lab123 -H "Accept: application/json" \
http://192.0.2.10:3000/rpc/get-software-information
# RPC with parameters
curl -u automation:lab123 \
"http://192.0.2.10:3000/rpc/get-interface-information?terse=&interface-name=ge-0/0/0"
The REST API Explorer is a browser GUI on the same port that lets you pick an RPC, fill in its parameters and see both the generated curl command and the reply. It is useful for learning, but don't leave it enabled on production devices. Know the defaults: port 3000 for HTTP and 3443 for HTTPS, HTTP basic authentication, GET for a single RPC, and POST to /rpc to send several RPCs in one request.
A four-week JN0-224 study plan
| Week | Focus | Lab goal |
|---|---|---|
| 1 | Domains 1–3 | Use display xml rpc and display json on 20 commands, write XPath for each |
| 2 | PyEZ read operations | Facts, RPC calls, parsing replies with findtext and xpath
|
| 3 | PyEZ changes and testing | Config utility with Jinja2, exception handling, JSNAPy pre/post checks |
| 4 | REST API and review | curl and REST Explorer, then timed practice questions |
In week 4, switch to timed practice. NWExam has free JN0-224 sample questions in the same read-the-snippet style, and the Junos PyEZ Developer Guide is the reference to go back to when a question about the Config or Device classes catches you out.
Exam-day checklist
- CLI command to RPC:
| display xml rpc, then swap hyphens for underscores to get the PyEZ name. - NETCONF runs over SSH on port 830. The REST API defaults to port 3000 (HTTP) and 3443 (HTTPS).
-
ConnectErroris the parent of the connection exceptions. Config problems raiseConfigLoadError,LockErrororCommitError. -
commit_check()validates,pdiff()shows the diff,rollback()discards changes. - JSNAPy
--checkcompares two snapshots and--snapchecktests one. - YAML uses spaces, not tabs, and JSON needs double quotes with no trailing commas.
If you can read each code block in this guide and predict its output before running it, you are ready for the JN0-224 JNCIA-DevOps exam.