mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-12 21:45:41 +03:00
test(e2e): give the dynamips routers a detected idle-PC
Without one every c7200 busy-waits a full CPU core for the whole scenario -- the tests create raw nodes and no template supplies a value (measured: constant ~100 % per router, up to ~6 minutes per dynamips scenario). harness.dynamips_idlepc() detects a value through the controller's auto_idlepc endpoint (the GUI idle-PC finder path: a throwaway project boots the image, candidate values are validated by measuring actual CPU usage) and caches it in ~/.cache/gns3-e2e/idlepc.json keyed by the image checksum, so the cost (~100 s here) is paid once per image, not per run. GNS3_E2E_IDLEPC overrides detection; any failure returns None and the scenarios run without one rather than fail on a CPU optimisation. All four scenarios that boot c7200 routers (dynamips kernel/relay, ethernet switch kernel/relay) pass the value at creation. Live: detected 0x60189234 for the M11 image (238 s first run including detection); the routers drop from ~100 % during boot to 3-8 % steady (sampled per process), and a re-run reports the cached value with no detection.
This commit is contained in:
parent
f13da59f23
commit
e0329ceee6
@ -33,6 +33,15 @@ Either way each test creates its own project and deletes it again. Set
|
||||
`GNS3_E2E_KEEP=1` to keep the project (and a started instance) on failure
|
||||
and print where to look at it.
|
||||
|
||||
The dynamips scenarios give their routers an idle-PC: detected once per
|
||||
image through the controller's `auto_idlepc` (a throwaway project and a
|
||||
real CPU-usage measurement — the GUI's idle-PC finder path) and cached in
|
||||
`~/.cache/gns3-e2e/idlepc.json` keyed by the image checksum. Without one,
|
||||
each c7200 burns a **full CPU core** for the whole scenario — the e2e
|
||||
creates raw nodes, so no template supplies a value. Set `GNS3_E2E_IDLEPC`
|
||||
to skip the detection and use the given value (detection failing is never
|
||||
fatal: the routers simply run without one).
|
||||
|
||||
`harness.py` holds the shared pieces — REST client, server lifecycle, the
|
||||
IOS console driver, host-side kernel inspections (bridge membership, tap
|
||||
admin state, tc qdiscs, the L2-anchor spec §E assertions) — so a new node
|
||||
|
||||
@ -39,6 +39,9 @@ Environment knobs:
|
||||
* ``GNS3_E2E_USER`` / ``GNS3_E2E_PASSWORD`` — credentials for the target.
|
||||
* ``GNS3_E2E_IMAGES`` — images directory the isolated instance uses
|
||||
(default ``~/GNS3/images``); also where the dynamips test finds its image.
|
||||
* ``GNS3_E2E_IDLEPC`` — use this idle-PC value for the dynamips routers
|
||||
instead of detecting one (see ``dynamips_idlepc``; without an idle-PC each
|
||||
router burns a full CPU core for the whole scenario).
|
||||
* ``GNS3_E2E_KEEP=1`` — keep the project (and the isolated instance) when a
|
||||
test fails, and print their ids, for inspection.
|
||||
"""
|
||||
@ -121,8 +124,10 @@ class Compute:
|
||||
def create_project(self, name="e2e"):
|
||||
return self.call("POST", "/projects", {"name": f"{name}-{int(time.time())}"})
|
||||
|
||||
def create_dynamips_router(self, project_id, name, image, slots, platform="c7200", ram=512):
|
||||
def create_dynamips_router(self, project_id, name, image, slots, platform="c7200", ram=512, idlepc=None):
|
||||
properties = {"platform": platform, "image": image, "ram": ram}
|
||||
if idlepc:
|
||||
properties["idlepc"] = idlepc
|
||||
properties.update(slots)
|
||||
return self.call(
|
||||
"POST",
|
||||
@ -350,6 +355,79 @@ def release(server, project_id, failed=False):
|
||||
server.stop()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dynamips idle-PC (CPU)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Detected values, keyed by image identity; the detection needs a throwaway
|
||||
# boot, so it is worth caching across runs.
|
||||
IDLEPC_CACHE_PATH = os.path.join(os.path.expanduser("~/.cache/gns3-e2e"), "idlepc.json")
|
||||
|
||||
|
||||
def dynamips_idlepc(server, image, platform="c7200", ram=512):
|
||||
"""The idle-PC value the scenarios' dynamips routers should be created
|
||||
with — detected once per image and cached.
|
||||
|
||||
Without one, dynamips busy-waits and burns a *full CPU core per router*
|
||||
for the whole scenario: the e2e creates raw nodes, and no template
|
||||
supplies an idle-PC. Detection goes through the controller's
|
||||
``auto_idlepc`` endpoint — the GUI's idle-PC finder path: a throwaway
|
||||
project boots the image, candidate values are validated by measuring the
|
||||
actual process CPU (first candidate under 70 % wins) — and the result is
|
||||
cached in ``~/.cache/gns3-e2e/idlepc.json`` keyed by the image's
|
||||
checksum, so the cost is paid once per image, not once per run.
|
||||
|
||||
``GNS3_E2E_IDLEPC`` overrides detection and cache entirely. Any failure
|
||||
returns None: the scenarios then run without an idle-PC (and burn the
|
||||
cores) rather than fail on a CPU optimisation.
|
||||
"""
|
||||
override = os.environ.get("GNS3_E2E_IDLEPC")
|
||||
if override:
|
||||
return override
|
||||
|
||||
checksum = None
|
||||
for entry in server.compute.call("GET", "/computes/local/dynamips/images"):
|
||||
if entry["filename"] == image:
|
||||
checksum = entry.get("md5sum")
|
||||
break
|
||||
key = f"{image}:{checksum}" if checksum else image
|
||||
|
||||
cache = {}
|
||||
try:
|
||||
with open(IDLEPC_CACHE_PATH) as f:
|
||||
cache = json.load(f)
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
if cache.get(key):
|
||||
print(f".. idle-PC {cache[key]} for {image} (cached)")
|
||||
return cache[key]
|
||||
|
||||
print(f".. detecting an idle-PC for {image} (once per image — this takes a few minutes)")
|
||||
try:
|
||||
result = server.compute.call(
|
||||
"POST",
|
||||
"/computes/local/dynamips/auto_idlepc",
|
||||
{"platform": platform, "image": image, "ram": ram},
|
||||
timeout=600,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f".. idle-PC detection failed ({e}); the routers will burn a core each")
|
||||
return None
|
||||
idlepc = (result or {}).get("idlepc")
|
||||
if not idlepc:
|
||||
print(f".. no idle-PC value found for {image}; the routers will burn a core each")
|
||||
return None
|
||||
cache[key] = idlepc
|
||||
try:
|
||||
os.makedirs(os.path.dirname(IDLEPC_CACHE_PATH), exist_ok=True)
|
||||
with open(IDLEPC_CACHE_PATH, "w") as f:
|
||||
json.dump(cache, f, indent=2, sort_keys=True)
|
||||
except OSError as e:
|
||||
print(f".. could not write the idle-PC cache {IDLEPC_CACHE_PATH}: {e}")
|
||||
print(f".. idle-PC {idlepc} for {image} (cached for later runs)")
|
||||
return idlepc
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# IOS console (dynamips telnet console)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -84,13 +84,14 @@ def test_dynamips_kernel_datapath():
|
||||
if compute.capabilities().get("ubridge_tap") is not True:
|
||||
pytest.skip("this compute's uBridge cannot create persistent TAPs (ubridge_tap != True)")
|
||||
image = _pick_image(server)
|
||||
idlepc = harness.dynamips_idlepc(server, image)
|
||||
|
||||
project = compute.create_project("dyn-e2e")
|
||||
pid = project["project_id"]
|
||||
t1 = t2 = None
|
||||
try:
|
||||
r1 = compute.create_dynamips_router(pid, "E2E-R1", image, SLOTS)
|
||||
r2 = compute.create_dynamips_router(pid, "E2E-R2", image, SLOTS)
|
||||
r1 = compute.create_dynamips_router(pid, "E2E-R1", image, SLOTS, idlepc=idlepc)
|
||||
r2 = compute.create_dynamips_router(pid, "E2E-R2", image, SLOTS, idlepc=idlepc)
|
||||
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
||||
t1 = harness.anchor_name(r1_id, *ETH)
|
||||
t2 = harness.anchor_name(r2_id, *ETH)
|
||||
@ -244,13 +245,14 @@ def test_dynamips_relay_control():
|
||||
server = harness.live_server(kernel=False)
|
||||
compute = server.compute
|
||||
image = _pick_image(server)
|
||||
idlepc = harness.dynamips_idlepc(server, image)
|
||||
|
||||
project = compute.create_project("dyn-e2e-relay")
|
||||
pid = project["project_id"]
|
||||
t1 = None
|
||||
try:
|
||||
r1 = compute.create_dynamips_router(pid, "E2E-R1", image, SLOTS)
|
||||
r2 = compute.create_dynamips_router(pid, "E2E-R2", image, SLOTS)
|
||||
r1 = compute.create_dynamips_router(pid, "E2E-R1", image, SLOTS, idlepc=idlepc)
|
||||
r2 = compute.create_dynamips_router(pid, "E2E-R2", image, SLOTS, idlepc=idlepc)
|
||||
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
||||
t1 = harness.anchor_name(r1_id, *ETH)
|
||||
|
||||
|
||||
@ -67,7 +67,7 @@ def _switch_bridge_name(switch_id):
|
||||
return "gns3" + switch_id.replace("-", "")[:6]
|
||||
|
||||
|
||||
def _create_topology(compute, pid, image, name):
|
||||
def _create_topology(compute, pid, image, name, idlepc=None):
|
||||
switch = compute.call(
|
||||
"POST",
|
||||
f"/projects/{pid}/nodes",
|
||||
@ -83,8 +83,8 @@ def _create_topology(compute, pid, image, name):
|
||||
},
|
||||
},
|
||||
)
|
||||
r1 = compute.create_dynamips_router(pid, f"{name}-R1", image, SLOTS)
|
||||
r2 = compute.create_dynamips_router(pid, f"{name}-R2", image, SLOTS)
|
||||
r1 = compute.create_dynamips_router(pid, f"{name}-R1", image, SLOTS, idlepc=idlepc)
|
||||
r2 = compute.create_dynamips_router(pid, f"{name}-R2", image, SLOTS, idlepc=idlepc)
|
||||
return switch, r1, r2
|
||||
|
||||
|
||||
@ -100,12 +100,13 @@ def test_ethernet_switch_kernel_fast_path():
|
||||
if compute.capabilities().get("ubridge_tap") is not True:
|
||||
pytest.skip("this compute's uBridge cannot create persistent TAPs (ubridge_tap != True)")
|
||||
image = _pick_image(server)
|
||||
idlepc = harness.dynamips_idlepc(server, image)
|
||||
|
||||
project = compute.create_project("esw-e2e")
|
||||
pid = project["project_id"]
|
||||
sw_bridge = None
|
||||
try:
|
||||
switch, r1, r2 = _create_topology(compute, pid, image, "E2E")
|
||||
switch, r1, r2 = _create_topology(compute, pid, image, "E2E", idlepc)
|
||||
switch_id = switch["node_id"]
|
||||
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
||||
sw_bridge = _switch_bridge_name(switch_id)
|
||||
@ -262,12 +263,13 @@ def test_ethernet_switch_relay_control():
|
||||
server = harness.live_server(kernel=False)
|
||||
compute = server.compute
|
||||
image = _pick_image(server)
|
||||
idlepc = harness.dynamips_idlepc(server, image)
|
||||
|
||||
project = compute.create_project("esw-e2e-relay")
|
||||
pid = project["project_id"]
|
||||
sw_bridge = None
|
||||
try:
|
||||
switch, r1, r2 = _create_topology(compute, pid, image, "E2E")
|
||||
switch, r1, r2 = _create_topology(compute, pid, image, "E2E", idlepc)
|
||||
sw_bridge = _switch_bridge_name(switch["node_id"])
|
||||
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
||||
a1 = harness.anchor_name(r1_id, *ETH)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user