mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-04 06:21:15 +02:00
Merge branch '3.1' into 3.1
This commit is contained in:
51 files changed
+3155
-183
No files matched your search
@@ -20,18 +20,33 @@ import copy
|
||||
import html
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
|
||||
from ..config import Config
|
||||
from ..utils import int_to_macaddress, macaddress_to_int
|
||||
from ..utils.application_id import is_iol_runner_environment
|
||||
from ..utils.images import images_directories
|
||||
from ..utils.vxlan import vxlan_device_name
|
||||
from .controller_error import ComputeConflictError, ComputeError, ControllerError, ControllerTimeoutError
|
||||
from .node_types import BUILTIN_NODE_TYPES
|
||||
from .ports.port_factory import DynamipsPortFactory, PortFactory, StandardPortFactory
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# The compute's error when it could not create a VXLAN tunnel device — the
|
||||
# wording is a same-repo contract with KernelDatapathMixin._kernel_error
|
||||
# (only computes running this codebase carry VXLAN links: the capability
|
||||
# gate keeps everything else on the relay), and the captured device name
|
||||
# identifies the link (a pure function of link id + node id).
|
||||
_VXLAN_CONFLICT_RE = re.compile(r"Could not create VXLAN device (?P<device>\S+)")
|
||||
|
||||
# Node-start self-heal rounds over a refused VXLAN VNI: the first re-wires
|
||||
# the link on a fresh VNI, the second degrades it to the relay (whose NIOs
|
||||
# cannot hit the conflict) — a start must not fail permanently because
|
||||
# another link already registered the VNI on the shared dstport socket.
|
||||
_VXLAN_START_HEALS = 2
|
||||
|
||||
|
||||
# Image properties of a node, mapped by node type. The value is the image
|
||||
# "type" used by the image manager (see utils.images). Docker images are not
|
||||
@@ -43,6 +58,7 @@ IMAGE_PROPERTIES_BY_NODE_TYPE = {
|
||||
"hdc_disk_image": "qemu",
|
||||
"hdd_disk_image": "qemu",
|
||||
"cdrom_image": "qemu",
|
||||
"cdrom2_image": "qemu",
|
||||
"initrd": "qemu",
|
||||
"kernel_image": "qemu",
|
||||
"bios_image": "qemu",
|
||||
@@ -885,6 +901,7 @@ class Node:
|
||||
"hdc_disk_image",
|
||||
"hdd_disk_image",
|
||||
"cdrom_image",
|
||||
"cdrom2_image",
|
||||
"bios_image",
|
||||
"initrd",
|
||||
"kernel_image",
|
||||
@@ -955,18 +972,32 @@ class Node:
|
||||
f"Cannot start node '{self._name}': {len(failed_links)} deferred link(s) "
|
||||
"could not be restored. Please try again."
|
||||
)
|
||||
try:
|
||||
# For IOU: we need to send the licence everytime we start a node
|
||||
if self.node_type == "iou":
|
||||
license_check = self._project.controller.iou_license.get("license_check", True)
|
||||
iourc_content = self._project.controller.iou_license.get("iourc_content", None)
|
||||
await self.post(
|
||||
"/start", timeout=240, data={"license_check": license_check, "iourc_content": iourc_content}
|
||||
for heal in range(_VXLAN_START_HEALS + 1):
|
||||
try:
|
||||
await self._start_on_compute(data)
|
||||
break
|
||||
except asyncio.TimeoutError:
|
||||
raise ControllerTimeoutError(f"Timeout when starting {self._name}")
|
||||
except ComputeError as e:
|
||||
conflicted = self._vxlan_conflicted_link(e)
|
||||
if conflicted is None or heal == _VXLAN_START_HEALS:
|
||||
raise
|
||||
degrade = heal > 0 # a second conflict takes the relay
|
||||
log.warning(
|
||||
"Node '%s': start refused over a VXLAN VNI conflict on link %s — %s and retrying",
|
||||
self._name,
|
||||
conflicted.id,
|
||||
"degrading the link to the uBridge relay" if degrade else "re-wiring the link on a fresh VNI",
|
||||
)
|
||||
else:
|
||||
await self.post("/start", data=data, timeout=240)
|
||||
except TimeoutError:
|
||||
raise ControllerTimeoutError(f"Timeout when starting {self._name}")
|
||||
# The refused start leaves the node half-started (the
|
||||
# emulator process runs, the wiring partial) — stop it
|
||||
# first: a start on a running container early-returns
|
||||
# without wiring anything.
|
||||
await self.stop()
|
||||
if await self._project._redispatch_vxlan_links([conflicted], downgrade=degrade) and not degrade:
|
||||
# The fresh VNI was already refused before the start
|
||||
# could even retry — degrade the pair to the relay now.
|
||||
await self._project._redispatch_vxlan_links([conflicted], downgrade=True)
|
||||
self._confirm_status("started")
|
||||
# A started node may have brought kernel anchors into existence that
|
||||
# a link needs to finish wiring (the Ethernet-switch fast path joins
|
||||
@@ -974,6 +1005,42 @@ class Node:
|
||||
for link in self._links:
|
||||
await link.node_started(self)
|
||||
|
||||
async def _start_on_compute(self, data=None):
|
||||
"""
|
||||
The compute-side start request (IOU carries its licence along).
|
||||
"""
|
||||
|
||||
# For IOU: we need to send the licence everytime we start a node
|
||||
if self.node_type == "iou":
|
||||
license_check = self._project.controller.iou_license.get("license_check", True)
|
||||
iourc_content = self._project.controller.iou_license.get("iourc_content", None)
|
||||
await self.post(
|
||||
"/start", timeout=240, data={"license_check": license_check, "iourc_content": iourc_content}
|
||||
)
|
||||
else:
|
||||
await self.post("/start", data=data, timeout=240)
|
||||
|
||||
def _vxlan_conflicted_link(self, error):
|
||||
"""
|
||||
The link whose VXLAN device a failed node start could not create,
|
||||
or None for every other failure.
|
||||
|
||||
The compute's error names the device it could not create and the
|
||||
name is a pure function of (link id, node id), so it identifies the
|
||||
link with no extra API surface. An unmatched message — another
|
||||
failure, a future wording change — simply leaves the error to
|
||||
surface unchanged, which is exactly the behaviour without the heal.
|
||||
"""
|
||||
|
||||
match = _VXLAN_CONFLICT_RE.search(str(error))
|
||||
if match is None:
|
||||
return None
|
||||
device = match.group("device")
|
||||
for link in self._links:
|
||||
if vxlan_device_name(link.id, self.id) == device and any(nio.get("vxlan") for nio in link._link_data):
|
||||
return link
|
||||
return None
|
||||
|
||||
async def stop(self, strict=False):
|
||||
"""
|
||||
Stop a node
|
||||
|
||||
Reference in new issue
Block a user