mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-21 02:12:21 +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
|
`GNS3_E2E_KEEP=1` to keep the project (and a started instance) on failure
|
||||||
and print where to look at it.
|
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
|
`harness.py` holds the shared pieces — REST client, server lifecycle, the
|
||||||
IOS console driver, host-side kernel inspections (bridge membership, tap
|
IOS console driver, host-side kernel inspections (bridge membership, tap
|
||||||
admin state, tc qdiscs, the L2-anchor spec §E assertions) — so a new node
|
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_USER`` / ``GNS3_E2E_PASSWORD`` — credentials for the target.
|
||||||
* ``GNS3_E2E_IMAGES`` — images directory the isolated instance uses
|
* ``GNS3_E2E_IMAGES`` — images directory the isolated instance uses
|
||||||
(default ``~/GNS3/images``); also where the dynamips test finds its image.
|
(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
|
* ``GNS3_E2E_KEEP=1`` — keep the project (and the isolated instance) when a
|
||||||
test fails, and print their ids, for inspection.
|
test fails, and print their ids, for inspection.
|
||||||
"""
|
"""
|
||||||
@ -121,8 +124,10 @@ class Compute:
|
|||||||
def create_project(self, name="e2e"):
|
def create_project(self, name="e2e"):
|
||||||
return self.call("POST", "/projects", {"name": f"{name}-{int(time.time())}"})
|
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}
|
properties = {"platform": platform, "image": image, "ram": ram}
|
||||||
|
if idlepc:
|
||||||
|
properties["idlepc"] = idlepc
|
||||||
properties.update(slots)
|
properties.update(slots)
|
||||||
return self.call(
|
return self.call(
|
||||||
"POST",
|
"POST",
|
||||||
@ -350,6 +355,79 @@ def release(server, project_id, failed=False):
|
|||||||
server.stop()
|
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)
|
# IOS console (dynamips telnet console)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@ -84,13 +84,14 @@ def test_dynamips_kernel_datapath():
|
|||||||
if compute.capabilities().get("ubridge_tap") is not True:
|
if compute.capabilities().get("ubridge_tap") is not True:
|
||||||
pytest.skip("this compute's uBridge cannot create persistent TAPs (ubridge_tap != True)")
|
pytest.skip("this compute's uBridge cannot create persistent TAPs (ubridge_tap != True)")
|
||||||
image = _pick_image(server)
|
image = _pick_image(server)
|
||||||
|
idlepc = harness.dynamips_idlepc(server, image)
|
||||||
|
|
||||||
project = compute.create_project("dyn-e2e")
|
project = compute.create_project("dyn-e2e")
|
||||||
pid = project["project_id"]
|
pid = project["project_id"]
|
||||||
t1 = t2 = None
|
t1 = t2 = None
|
||||||
try:
|
try:
|
||||||
r1 = compute.create_dynamips_router(pid, "E2E-R1", image, SLOTS)
|
r1 = compute.create_dynamips_router(pid, "E2E-R1", image, SLOTS, idlepc=idlepc)
|
||||||
r2 = compute.create_dynamips_router(pid, "E2E-R2", image, SLOTS)
|
r2 = compute.create_dynamips_router(pid, "E2E-R2", image, SLOTS, idlepc=idlepc)
|
||||||
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
||||||
t1 = harness.anchor_name(r1_id, *ETH)
|
t1 = harness.anchor_name(r1_id, *ETH)
|
||||||
t2 = harness.anchor_name(r2_id, *ETH)
|
t2 = harness.anchor_name(r2_id, *ETH)
|
||||||
@ -244,13 +245,14 @@ def test_dynamips_relay_control():
|
|||||||
server = harness.live_server(kernel=False)
|
server = harness.live_server(kernel=False)
|
||||||
compute = server.compute
|
compute = server.compute
|
||||||
image = _pick_image(server)
|
image = _pick_image(server)
|
||||||
|
idlepc = harness.dynamips_idlepc(server, image)
|
||||||
|
|
||||||
project = compute.create_project("dyn-e2e-relay")
|
project = compute.create_project("dyn-e2e-relay")
|
||||||
pid = project["project_id"]
|
pid = project["project_id"]
|
||||||
t1 = None
|
t1 = None
|
||||||
try:
|
try:
|
||||||
r1 = compute.create_dynamips_router(pid, "E2E-R1", image, SLOTS)
|
r1 = compute.create_dynamips_router(pid, "E2E-R1", image, SLOTS, idlepc=idlepc)
|
||||||
r2 = compute.create_dynamips_router(pid, "E2E-R2", image, SLOTS)
|
r2 = compute.create_dynamips_router(pid, "E2E-R2", image, SLOTS, idlepc=idlepc)
|
||||||
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
||||||
t1 = harness.anchor_name(r1_id, *ETH)
|
t1 = harness.anchor_name(r1_id, *ETH)
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,7 @@ def _switch_bridge_name(switch_id):
|
|||||||
return "gns3" + switch_id.replace("-", "")[:6]
|
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(
|
switch = compute.call(
|
||||||
"POST",
|
"POST",
|
||||||
f"/projects/{pid}/nodes",
|
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)
|
r1 = compute.create_dynamips_router(pid, f"{name}-R1", image, SLOTS, idlepc=idlepc)
|
||||||
r2 = compute.create_dynamips_router(pid, f"{name}-R2", image, SLOTS)
|
r2 = compute.create_dynamips_router(pid, f"{name}-R2", image, SLOTS, idlepc=idlepc)
|
||||||
return switch, r1, r2
|
return switch, r1, r2
|
||||||
|
|
||||||
|
|
||||||
@ -100,12 +100,13 @@ def test_ethernet_switch_kernel_fast_path():
|
|||||||
if compute.capabilities().get("ubridge_tap") is not True:
|
if compute.capabilities().get("ubridge_tap") is not True:
|
||||||
pytest.skip("this compute's uBridge cannot create persistent TAPs (ubridge_tap != True)")
|
pytest.skip("this compute's uBridge cannot create persistent TAPs (ubridge_tap != True)")
|
||||||
image = _pick_image(server)
|
image = _pick_image(server)
|
||||||
|
idlepc = harness.dynamips_idlepc(server, image)
|
||||||
|
|
||||||
project = compute.create_project("esw-e2e")
|
project = compute.create_project("esw-e2e")
|
||||||
pid = project["project_id"]
|
pid = project["project_id"]
|
||||||
sw_bridge = None
|
sw_bridge = None
|
||||||
try:
|
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"]
|
switch_id = switch["node_id"]
|
||||||
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
||||||
sw_bridge = _switch_bridge_name(switch_id)
|
sw_bridge = _switch_bridge_name(switch_id)
|
||||||
@ -262,12 +263,13 @@ def test_ethernet_switch_relay_control():
|
|||||||
server = harness.live_server(kernel=False)
|
server = harness.live_server(kernel=False)
|
||||||
compute = server.compute
|
compute = server.compute
|
||||||
image = _pick_image(server)
|
image = _pick_image(server)
|
||||||
|
idlepc = harness.dynamips_idlepc(server, image)
|
||||||
|
|
||||||
project = compute.create_project("esw-e2e-relay")
|
project = compute.create_project("esw-e2e-relay")
|
||||||
pid = project["project_id"]
|
pid = project["project_id"]
|
||||||
sw_bridge = None
|
sw_bridge = None
|
||||||
try:
|
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"])
|
sw_bridge = _switch_bridge_name(switch["node_id"])
|
||||||
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
r1_id, r2_id = r1["node_id"], r2["node_id"]
|
||||||
a1 = harness.anchor_name(r1_id, *ETH)
|
a1 = harness.anchor_name(r1_id, *ETH)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user