mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-13 14:05:36 +03:00
feat(compute): probe uBridge's swappable bridge TAP leg
The bridge module gained delete_nio_tap (uBridge's gns3server-integration contract, section 'bridge — generic NIO relay with a swappable TAP leg'): a per-port bridge can swap its topology leg between UDP and a persistent TAP through stop -> delete -> add -> start, which is what lets an IOL runner container's port bridge carry a kernel-link anchor. The probe needs no scratch objects and no capabilities: one command against a bridge that cannot exist — 202 (unknown command) is an old build, 214 (bridge doesn't exist) is a new one. Cached by binary identity like every other probe; any failure reports unknown. GET /v3/compute/capabilities gains ubridge_bridge_tap; the controller's default capability dict carries it too (the single schema definition, per the 1e67fbf12 lesson).
This commit is contained in:
parent
d6851695cc
commit
c20125c180
@ -25,7 +25,12 @@ from fastapi import APIRouter, Request
|
|||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
from gns3server.compute import MODULES
|
from gns3server.compute import MODULES
|
||||||
from gns3server.compute.ubridge.tc_probe import probe_iol_tap_support, probe_tc_capabilities, probe_tap_support
|
from gns3server.compute.ubridge.tc_probe import (
|
||||||
|
probe_bridge_tap_support,
|
||||||
|
probe_iol_tap_support,
|
||||||
|
probe_tc_capabilities,
|
||||||
|
probe_tap_support,
|
||||||
|
)
|
||||||
from gns3server.utils.path import get_default_project_directory
|
from gns3server.utils.path import get_default_project_directory
|
||||||
from gns3server.utils.tc_capabilities import usable_ebpf_modes
|
from gns3server.utils.tc_capabilities import usable_ebpf_modes
|
||||||
from gns3server.version import __version__
|
from gns3server.version import __version__
|
||||||
@ -60,6 +65,11 @@ async def get_capabilities(request: Request) -> dict:
|
|||||||
# Ethernet ports need for the kernel datapath. None = unknown.
|
# Ethernet ports need for the kernel datapath. None = unknown.
|
||||||
ubridge_iol_tap = await probe_iol_tap_support()
|
ubridge_iol_tap = await probe_iol_tap_support()
|
||||||
|
|
||||||
|
# Swappable TAP leg on the generic bridge module (bridge delete_nio_tap):
|
||||||
|
# what lets an IOL runner container's per-port bridge carry a kernel-link
|
||||||
|
# anchor. None = unknown (probe failed or uBridge too old).
|
||||||
|
ubridge_bridge_tap = await probe_bridge_tap_support()
|
||||||
|
|
||||||
# record the controller hostname or IP address
|
# record the controller hostname or IP address
|
||||||
if request.client:
|
if request.client:
|
||||||
request.app.state.controller_host = request.client.host
|
request.app.state.controller_host = request.client.host
|
||||||
@ -74,4 +84,5 @@ async def get_capabilities(request: Request) -> dict:
|
|||||||
"ubridge_tc": ubridge_tc,
|
"ubridge_tc": ubridge_tc,
|
||||||
"ubridge_tap": ubridge_tap,
|
"ubridge_tap": ubridge_tap,
|
||||||
"ubridge_iol_tap": ubridge_iol_tap,
|
"ubridge_iol_tap": ubridge_iol_tap,
|
||||||
|
"ubridge_bridge_tap": ubridge_bridge_tap,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,8 @@ _cache = {}
|
|||||||
_tap_cache = {}
|
_tap_cache = {}
|
||||||
# binary identity -> whether iol_bridge can bind a port to a TAP (None = unknown)
|
# binary identity -> whether iol_bridge can bind a port to a TAP (None = unknown)
|
||||||
_iol_tap_cache = {}
|
_iol_tap_cache = {}
|
||||||
|
# binary identity -> whether the bridge module can release a named TAP NIO (None = unknown)
|
||||||
|
_bridge_tap_cache = {}
|
||||||
_lock = asyncio.Lock()
|
_lock = asyncio.Lock()
|
||||||
|
|
||||||
|
|
||||||
@ -274,3 +276,80 @@ async def _probe_iol_tap(path, config, timeout):
|
|||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
await hypervisor.stop()
|
await hypervisor.stop()
|
||||||
shutil.rmtree(working_dir, ignore_errors=True)
|
shutil.rmtree(working_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def probe_bridge_tap_support(timeout: float = 15.0):
|
||||||
|
"""
|
||||||
|
Whether this host's uBridge has ``bridge delete_nio_tap`` — the command
|
||||||
|
that makes the bridge module's TAP leg swappable (stop -> delete -> add ->
|
||||||
|
start), which is what lets an IOL runner container's per-port bridge swap
|
||||||
|
its topology leg between UDP (relay) and a persistent TAP anchor (kernel
|
||||||
|
datapath). Probed with no scratch objects and no capabilities needed:
|
||||||
|
deleting from a bridge that doesn't exist answers 214 on a build with the
|
||||||
|
command and 202 (unknown command) on one without. Cached by binary
|
||||||
|
identity like the other probes; any failure means unknown, reported as
|
||||||
|
None and cached as such.
|
||||||
|
"""
|
||||||
|
|
||||||
|
config = Config.instance()
|
||||||
|
path = shutil.which(config.settings.Server.ubridge_path)
|
||||||
|
if not path:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
stat = os.stat(path)
|
||||||
|
key = (path, stat.st_mtime_ns, stat.st_size)
|
||||||
|
except OSError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async with _lock:
|
||||||
|
if key in _bridge_tap_cache:
|
||||||
|
return _bridge_tap_cache[key]
|
||||||
|
supported = await _probe_bridge_tap(path, config, timeout)
|
||||||
|
_bridge_tap_cache[key] = supported
|
||||||
|
return supported
|
||||||
|
|
||||||
|
|
||||||
|
async def _probe_bridge_tap(path, config, timeout):
|
||||||
|
"""
|
||||||
|
Spawn a throwaway uBridge and issue the probe command against a bridge
|
||||||
|
name that cannot exist (uuid). The reply splits the builds: "bridge ...
|
||||||
|
doesn't exist" (214) means the command ran — this is the new build;
|
||||||
|
"Unknown command" (202) is the old one. Both arrive as UbridgeError (the
|
||||||
|
wrapper raises on any non-100 reply), so the message is the discriminator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
working_dir = tempfile.mkdtemp(prefix="gns3-brtap-probe-")
|
||||||
|
hypervisor = Hypervisor(
|
||||||
|
None,
|
||||||
|
path,
|
||||||
|
working_dir,
|
||||||
|
config.settings.Server.ubridge_control_transport,
|
||||||
|
config.settings.Server.host,
|
||||||
|
str(uuid.uuid4()),
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
|
||||||
|
async def ask():
|
||||||
|
await hypervisor.start()
|
||||||
|
await hypervisor.connect()
|
||||||
|
try:
|
||||||
|
await hypervisor.send(f"bridge delete_nio_tap gns3brtapprobe{uuid.uuid4().hex[:4]} any")
|
||||||
|
except UbridgeError as e:
|
||||||
|
if "doesn't exist" in str(e):
|
||||||
|
return True
|
||||||
|
# "Unknown command" (and anything else): old or broken build.
|
||||||
|
log.debug("uBridge bridge-tap probe answered: %s", e)
|
||||||
|
return None
|
||||||
|
# A 100 here would mean the bridge existed — impossible on this
|
||||||
|
# uuid name; treat it as unknown rather than supported.
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return await asyncio.wait_for(ask(), timeout=timeout)
|
||||||
|
except (OSError, asyncio.TimeoutError, ValueError) as e:
|
||||||
|
log.debug("uBridge bridge-tap probe failed: %s", e)
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
await hypervisor.stop()
|
||||||
|
shutil.rmtree(working_dir, ignore_errors=True)
|
||||||
|
|||||||
@ -102,6 +102,7 @@ class Compute:
|
|||||||
"ubridge_tc": None,
|
"ubridge_tc": None,
|
||||||
"ubridge_tap": None,
|
"ubridge_tap": None,
|
||||||
"ubridge_iol_tap": None,
|
"ubridge_iol_tap": None,
|
||||||
|
"ubridge_bridge_tap": None,
|
||||||
}
|
}
|
||||||
self.name = name
|
self.name = name
|
||||||
# Cache of interfaces on remote host
|
# Cache of interfaces on remote host
|
||||||
|
|||||||
@ -61,3 +61,9 @@ class Capabilities(BaseModel):
|
|||||||
"the anchor IOU Ethernet ports need to wire IOU links on the kernel datapath); absent when the "
|
"the anchor IOU Ethernet ports need to wire IOU links on the kernel datapath); absent when the "
|
||||||
"probe failed",
|
"probe failed",
|
||||||
)
|
)
|
||||||
|
ubridge_bridge_tap: Optional[bool] = Field(
|
||||||
|
None,
|
||||||
|
description="Whether uBridge's generic bridge module can release a named TAP NIO "
|
||||||
|
"(bridge delete_nio_tap, the swappable TAP leg an IOL runner container's per-port bridge "
|
||||||
|
"needs to wire its links on the kernel datapath); absent when the probe failed",
|
||||||
|
)
|
||||||
|
|||||||
@ -43,6 +43,7 @@ class TestCapabilitiesRoutes:
|
|||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tc_capabilities", no_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tc_capabilities", no_probe)
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", _no_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", _no_tap_probe)
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
||||||
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_bridge_tap_support", _no_tap_probe)
|
||||||
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
@ -69,6 +70,7 @@ class TestCapabilitiesRoutes:
|
|||||||
"ubridge_tc": None,
|
"ubridge_tc": None,
|
||||||
"ubridge_tap": None,
|
"ubridge_tap": None,
|
||||||
"ubridge_iol_tap": None,
|
"ubridge_iol_tap": None,
|
||||||
|
"ubridge_bridge_tap": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def test_get_on_gns3vm(self, app: FastAPI, compute_client: AsyncClient, on_gns3vm, monkeypatch) -> None:
|
async def test_get_on_gns3vm(self, app: FastAPI, compute_client: AsyncClient, on_gns3vm, monkeypatch) -> None:
|
||||||
@ -79,6 +81,7 @@ class TestCapabilitiesRoutes:
|
|||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tc_capabilities", no_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tc_capabilities", no_probe)
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", _no_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", _no_tap_probe)
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
||||||
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_bridge_tap_support", _no_tap_probe)
|
||||||
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
@ -105,6 +108,7 @@ class TestCapabilitiesRoutes:
|
|||||||
"ubridge_tc": None,
|
"ubridge_tc": None,
|
||||||
"ubridge_tap": None,
|
"ubridge_tap": None,
|
||||||
"ubridge_iol_tap": None,
|
"ubridge_iol_tap": None,
|
||||||
|
"ubridge_bridge_tap": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
async def test_get_reports_ubridge_tc(self, app: FastAPI, compute_client: AsyncClient, monkeypatch) -> None:
|
async def test_get_reports_ubridge_tc(self, app: FastAPI, compute_client: AsyncClient, monkeypatch) -> None:
|
||||||
@ -115,6 +119,7 @@ class TestCapabilitiesRoutes:
|
|||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tc_capabilities", fake_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tc_capabilities", fake_probe)
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", _no_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", _no_tap_probe)
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
||||||
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_bridge_tap_support", _no_tap_probe)
|
||||||
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert response.json()["ubridge_tc"] == {
|
assert response.json()["ubridge_tc"] == {
|
||||||
@ -135,6 +140,7 @@ class TestCapabilitiesRoutes:
|
|||||||
|
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", fake_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", fake_tap_probe)
|
||||||
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
||||||
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_bridge_tap_support", _no_tap_probe)
|
||||||
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert response.json()["ubridge_tap"] is True
|
assert response.json()["ubridge_tap"] is True
|
||||||
@ -153,3 +159,20 @@ class TestCapabilitiesRoutes:
|
|||||||
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert response.json()["ubridge_iol_tap"] is True
|
assert response.json()["ubridge_iol_tap"] is True
|
||||||
|
|
||||||
|
async def test_get_reports_ubridge_bridge_tap(self, app: FastAPI, compute_client: AsyncClient, monkeypatch) -> None:
|
||||||
|
"""
|
||||||
|
The swappable-bridge-TAP-leg capability is what makes an IOL runner
|
||||||
|
container kernel-eligible, so it travels with the other compute
|
||||||
|
capabilities.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def fake_bridge_tap_probe():
|
||||||
|
return True
|
||||||
|
|
||||||
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_bridge_tap_support", fake_bridge_tap_probe)
|
||||||
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_tap_support", _no_tap_probe)
|
||||||
|
monkeypatch.setattr("gns3server.api.routes.compute.capabilities.probe_iol_tap_support", _no_tap_probe)
|
||||||
|
response = await compute_client.get(app.url_path_for("compute:get_capabilities"))
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
assert response.json()["ubridge_bridge_tap"] is True
|
||||||
|
|||||||
@ -22,7 +22,12 @@ payload: spawn a throwaway uBridge, ask once, cache by binary identity.
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from gns3server.compute.ubridge import tc_probe
|
from gns3server.compute.ubridge import tc_probe
|
||||||
from gns3server.compute.ubridge.tc_probe import probe_iol_tap_support, probe_tc_capabilities, probe_tap_support
|
from gns3server.compute.ubridge.tc_probe import (
|
||||||
|
probe_bridge_tap_support,
|
||||||
|
probe_iol_tap_support,
|
||||||
|
probe_tc_capabilities,
|
||||||
|
probe_tap_support,
|
||||||
|
)
|
||||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||||
|
|
||||||
pytestmark = pytest.mark.asyncio
|
pytestmark = pytest.mark.asyncio
|
||||||
@ -36,6 +41,10 @@ class FakeHypervisor:
|
|||||||
reply = ["netem=delay,rate;ebpf=1;cbpf=1;ebpf_modes=nth,quota,window,flow"]
|
reply = ["netem=delay,rate;ebpf=1;cbpf=1;ebpf_modes=nth,quota,window,flow"]
|
||||||
error = None
|
error = None
|
||||||
error_prefix = None # fail only commands starting with this (a build missing one command)
|
error_prefix = None # fail only commands starting with this (a build missing one command)
|
||||||
|
# what `bridge delete_nio_tap` answers: a new build's 214 (the bridge
|
||||||
|
# doesn't exist), None = answer 100 (an impossible reply on the probe's
|
||||||
|
# uuid name — the probe must treat it as unknown)
|
||||||
|
bridge_tap_error = "214-bridge 'nosuch' doesn't exist"
|
||||||
spawned = 0
|
spawned = 0
|
||||||
|
|
||||||
def __init__(self, project, path, working_dir, transport, host, node_id):
|
def __init__(self, project, path, working_dir, transport, host, node_id):
|
||||||
@ -57,6 +66,10 @@ class FakeHypervisor:
|
|||||||
raise UbridgeError(self.error)
|
raise UbridgeError(self.error)
|
||||||
if self.error_prefix and command.startswith(self.error_prefix):
|
if self.error_prefix and command.startswith(self.error_prefix):
|
||||||
raise UbridgeError(f"202-Unknown command")
|
raise UbridgeError(f"202-Unknown command")
|
||||||
|
if command.startswith("bridge delete_nio_tap "):
|
||||||
|
if FakeHypervisor.bridge_tap_error:
|
||||||
|
raise UbridgeError(FakeHypervisor.bridge_tap_error)
|
||||||
|
return ["OK"]
|
||||||
if command == "tc capabilities":
|
if command == "tc capabilities":
|
||||||
return FakeHypervisor.reply
|
return FakeHypervisor.reply
|
||||||
return ["OK"]
|
return ["OK"]
|
||||||
@ -71,10 +84,12 @@ def probe_env(monkeypatch, tmp_path):
|
|||||||
FakeHypervisor.spawned = 0
|
FakeHypervisor.spawned = 0
|
||||||
FakeHypervisor.error = None
|
FakeHypervisor.error = None
|
||||||
FakeHypervisor.error_prefix = None
|
FakeHypervisor.error_prefix = None
|
||||||
|
FakeHypervisor.bridge_tap_error = "214-bridge 'nosuch' doesn't exist"
|
||||||
FakeHypervisor.reply = ["netem=delay,rate;ebpf=1;cbpf=1;ebpf_modes=nth,quota,window,flow"]
|
FakeHypervisor.reply = ["netem=delay,rate;ebpf=1;cbpf=1;ebpf_modes=nth,quota,window,flow"]
|
||||||
tc_probe._cache.clear()
|
tc_probe._cache.clear()
|
||||||
tc_probe._tap_cache.clear()
|
tc_probe._tap_cache.clear()
|
||||||
tc_probe._iol_tap_cache.clear()
|
tc_probe._iol_tap_cache.clear()
|
||||||
|
tc_probe._bridge_tap_cache.clear()
|
||||||
FakeHypervisor.last = None
|
FakeHypervisor.last = None
|
||||||
# a real file on disk: the cache is keyed on its (path, mtime, size)
|
# a real file on disk: the cache is keyed on its (path, mtime, size)
|
||||||
binary = tmp_path / "ubridge"
|
binary = tmp_path / "ubridge"
|
||||||
@ -193,3 +208,44 @@ async def test_probe_iol_tap_support_missing_binary(monkeypatch):
|
|||||||
monkeypatch.setattr(tc_probe.shutil, "which", lambda name: None)
|
monkeypatch.setattr(tc_probe.shutil, "which", lambda name: None)
|
||||||
assert await probe_iol_tap_support() is None
|
assert await probe_iol_tap_support() is None
|
||||||
assert FakeHypervisor.spawned == 0
|
assert FakeHypervisor.spawned == 0
|
||||||
|
|
||||||
|
|
||||||
|
async def test_probe_bridge_tap_support_asks_one_unused_bridge():
|
||||||
|
"""
|
||||||
|
The probe needs no scratch objects and no capabilities: one command
|
||||||
|
against a bridge that cannot exist, whose 214 answer means the command
|
||||||
|
ran (new build). Nothing is created, nothing needs cleaning up.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert await probe_bridge_tap_support() is True
|
||||||
|
assert FakeHypervisor.spawned == 1
|
||||||
|
commands = FakeHypervisor.last.commands
|
||||||
|
assert len(commands) == 1
|
||||||
|
assert commands[0].startswith("bridge delete_nio_tap gns3brtapprobe")
|
||||||
|
# cached like the other probes: no respawn on every /capabilities hit
|
||||||
|
assert await probe_bridge_tap_support() is True
|
||||||
|
assert FakeHypervisor.spawned == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_probe_bridge_tap_support_unknown_on_an_old_build():
|
||||||
|
"""
|
||||||
|
A uBridge without the command answers 202 "Unknown command": unknown, so
|
||||||
|
IOL runner containers stay on the relay datapath. A 100 answer is
|
||||||
|
unknown too — the bridge "existed" on the probe's uuid name, which
|
||||||
|
cannot happen on a sane build.
|
||||||
|
"""
|
||||||
|
|
||||||
|
FakeHypervisor.error_prefix = "bridge delete_nio_tap"
|
||||||
|
assert await probe_bridge_tap_support() is None
|
||||||
|
|
||||||
|
FakeHypervisor.error_prefix = None
|
||||||
|
FakeHypervisor.bridge_tap_error = None
|
||||||
|
tc_probe._bridge_tap_cache.clear()
|
||||||
|
assert await probe_bridge_tap_support() is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_probe_bridge_tap_support_missing_binary(monkeypatch):
|
||||||
|
|
||||||
|
monkeypatch.setattr(tc_probe.shutil, "which", lambda name: None)
|
||||||
|
assert await probe_bridge_tap_support() is None
|
||||||
|
assert FakeHypervisor.spawned == 0
|
||||||
|
|||||||
@ -381,7 +381,7 @@ async def test_json(compute):
|
|||||||
"disk_usage_percent": 0,
|
"disk_usage_percent": 0,
|
||||||
"connected": True,
|
"connected": True,
|
||||||
"last_error": None,
|
"last_error": None,
|
||||||
"capabilities": {"version": "", "platform": "", "cpus": 0, "memory": 0, "disk_size": 0, "node_types": [], "ubridge_tc": None, "ubridge_tap": None, "ubridge_iol_tap": None},
|
"capabilities": {"version": "", "platform": "", "cpus": 0, "memory": 0, "disk_size": 0, "node_types": [], "ubridge_tc": None, "ubridge_tap": None, "ubridge_iol_tap": None, "ubridge_bridge_tap": None},
|
||||||
}
|
}
|
||||||
assert compute.asdict(topology_dump=True) == {
|
assert compute.asdict(topology_dump=True) == {
|
||||||
"compute_id": "my_compute_id",
|
"compute_id": "my_compute_id",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user