Merge pull request #2086 from GNS3/iou-user-loader-libraries

Support user defined loader/libraries to run IOU
This commit is contained in:
Jeremy Grossmann 2022-07-20 17:03:18 +02:00 committed by GitHub
commit d08a052f94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 10 deletions

View File

@ -88,6 +88,8 @@ class IOUVM(BaseNode):
self._started = False self._started = False
self._nvram_watcher = None self._nvram_watcher = None
self._path = self.manager.get_abs_image_path(path, project.path) self._path = self.manager.get_abs_image_path(path, project.path)
self._lib_base = self.manager.get_images_directory()
self._loader = None
self._license_check = True self._license_check = True
# IOU settings # IOU settings
@ -147,6 +149,7 @@ class IOUVM(BaseNode):
""" """
self._path = self.manager.get_abs_image_path(path, self.project.path) self._path = self.manager.get_abs_image_path(path, self.project.path)
self._loader = None
log.info(f'IOU "{self._name}" [{self._id}]: IOU image updated to "{self._path}"') log.info(f'IOU "{self._name}" [{self._id}]: IOU image updated to "{self._path}"')
@property @property
@ -178,9 +181,10 @@ class IOUVM(BaseNode):
Finds the default RAM and NVRAM values for the IOU image. Finds the default RAM and NVRAM values for the IOU image.
""" """
await self._check_requirements()
try: try:
output = await gns3server.utils.asyncio.subprocess_check_output( output = await gns3server.utils.asyncio.subprocess_check_output(
self._path, "-h", cwd=self.working_dir, stderr=True *self._loader, self._path, "-h", cwd=self.working_dir, stderr=True
) )
match = re.search(r"-n <n>\s+Size of nvram in Kb \(default ([0-9]+)KB\)", output) match = re.search(r"-n <n>\s+Size of nvram in Kb \(default ([0-9]+)KB\)", output)
if match: if match:
@ -195,11 +199,13 @@ class IOUVM(BaseNode):
await self.update_default_iou_values() await self.update_default_iou_values()
def _check_requirements(self): async def _check_requirements(self):
""" """
Checks the IOU image. Checks the IOU image.
""" """
if self._loader is not None:
return # image already checked
if not self._path: if not self._path:
raise IOUError("IOU image is not configured") raise IOUError("IOU image is not configured")
if not os.path.isfile(self._path) or not os.path.exists(self._path): if not os.path.isfile(self._path) or not os.path.exists(self._path):
@ -223,6 +229,28 @@ class IOUVM(BaseNode):
if not os.access(self._path, os.X_OK): if not os.access(self._path, os.X_OK):
raise IOUError(f"IOU image '{self._path}' is not executable") raise IOUError(f"IOU image '{self._path}' is not executable")
# set loader command
if elf_header_start[4] == 1:
# 32-bit loader
loader = os.path.join(self._lib_base, "lib", "ld-linux.so.2")
lib_path = (os.path.join(self._lib_base, "lib"),
os.path.join(self._lib_base, "lib", "i386-linux-gnu"))
else:
# 64-bit loader
loader = os.path.join(self._lib_base, "lib64", "ld-linux-x86-64.so.2")
lib_path = (os.path.join(self._lib_base, "lib64"),
os.path.join(self._lib_base, "lib", "x86_64-linux-gnu"))
self._loader = []
if os.path.isfile(loader):
try:
proc = await asyncio.create_subprocess_exec(loader, "--verify", self._path)
if await proc.wait() == 0:
self._loader = [loader, "--library-path", ":".join(lib_path)]
else:
log.warning(f"Loader {loader} incompatible with '{self._path}'")
except (OSError, subprocess.SubprocessError) as e:
log.warning(f"Could not use loader {loader}: {e}")
def asdict(self): def asdict(self):
iou_vm_info = { iou_vm_info = {
@ -391,8 +419,10 @@ class IOUVM(BaseNode):
Checks for missing shared library dependencies in the IOU image. Checks for missing shared library dependencies in the IOU image.
""" """
env = os.environ.copy()
env["LD_TRACE_LOADED_OBJECTS"] = "1"
try: try:
output = await gns3server.utils.asyncio.subprocess_check_output("ldd", self._path) output = await gns3server.utils.asyncio.subprocess_check_output(*self._loader, self._path, env=env)
except (OSError, subprocess.SubprocessError) as e: except (OSError, subprocess.SubprocessError) as e:
log.warning(f"Could not determine the shared library dependencies for {self._path}: {e}") log.warning(f"Could not determine the shared library dependencies for {self._path}: {e}")
return return
@ -519,7 +549,7 @@ class IOUVM(BaseNode):
Starts the IOU process. Starts the IOU process.
""" """
self._check_requirements() await self._check_requirements()
if not self.is_running(): if not self.is_running():
await self._library_check() await self._library_check()
@ -566,10 +596,13 @@ class IOUVM(BaseNode):
command = await self._build_command() command = await self._build_command()
try: try:
if self._loader:
log.info(f"Starting IOU: {command} with loader {self._loader}")
else:
log.info(f"Starting IOU: {command}") log.info(f"Starting IOU: {command}")
self.command_line = " ".join(command) self.command_line = " ".join(command)
self._iou_process = await asyncio.create_subprocess_exec( self._iou_process = await asyncio.create_subprocess_exec(
*command, *self._loader, *command,
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
@ -1131,7 +1164,7 @@ class IOUVM(BaseNode):
env["IOURC"] = self.iourc_path env["IOURC"] = self.iourc_path
try: try:
output = await gns3server.utils.asyncio.subprocess_check_output( output = await gns3server.utils.asyncio.subprocess_check_output(
self._path, "-h", cwd=self.working_dir, env=env, stderr=True *self._loader, self._path, "-h", cwd=self.working_dir, env=env, stderr=True
) )
if re.search(r"-l\s+Enable Layer 1 keepalive messages", output): if re.search(r"-l\s+Enable Layer 1 keepalive messages", output):
command.extend(["-l"]) command.extend(["-l"])

View File

@ -91,7 +91,8 @@ async def get_computes(app: FastAPI) -> List[dict]:
def image_filter(change: Change, path: str) -> bool: def image_filter(change: Change, path: str) -> bool:
if change == Change.added and os.path.isfile(path): if change == Change.added and os.path.isfile(path):
if path.endswith(".tmp") or path.endswith(".md5sum") or path.startswith("."): if path.endswith(".tmp") or path.endswith(".md5sum") or path.startswith(".") or \
"/lib/" in path or "/lib64/" in path:
return False return False
header_magic_len = 7 header_magic_len = 7
with open(path, "rb") as f: with open(path, "rb") as f:

View File

@ -48,6 +48,7 @@ async def vm(compute_project, manager, config, tmpdir, fake_iou_bin, iourc_file)
vm = IOUVM("test", str(uuid.uuid4()), compute_project, manager, application_id=1) vm = IOUVM("test", str(uuid.uuid4()), compute_project, manager, application_id=1)
config.settings.IOU.iourc_path = iourc_file config.settings.IOU.iourc_path = iourc_file
vm.path = "iou.bin" vm.path = "iou.bin"
vm._loader = []
return vm return vm
@ -228,7 +229,8 @@ def test_path_relative(vm, fake_iou_bin):
assert vm.path == fake_iou_bin assert vm.path == fake_iou_bin
def test_path_invalid_bin(vm, tmpdir, config): @pytest.mark.asyncio
async def test_path_invalid_bin(vm, tmpdir, config):
config.settings.Server.images_path = str(tmpdir) config.settings.Server.images_path = str(tmpdir)
path = str(tmpdir / "test.bin") path = str(tmpdir / "test.bin")
@ -238,7 +240,7 @@ def test_path_invalid_bin(vm, tmpdir, config):
with pytest.raises(IOUError): with pytest.raises(IOUError):
vm.path = path vm.path = path
vm._check_requirements() await vm._check_requirements()
def test_create_netmap_config(vm): def test_create_netmap_config(vm):