Skip to content

Add method to solely create disposable#401

Merged
marmarek merged 2 commits intoQubesOS:mainfrom
ben-grande:preload-optimize
Dec 22, 2025
Merged

Add method to solely create disposable#401
marmarek merged 2 commits intoQubesOS:mainfrom
ben-grande:preload-optimize

Conversation

@ben-grande
Copy link
Contributor

In line with the qubes module, disposable creation happens on from_appvm(). This change is intended to measure how much time it takes to get a fresh disposable object, improving the performance tests distinction of the from_appvm() phase from the execution/run*() phase.

For: QubesOS/qubes-issues#10230
For: QubesOS/qubes-issues#1512


wrapper = DispVMWrapper(app, method_dest)
method_dest = "dom0"
dispvm = app.qubesd_call(method_dest, "admin.vm.CreateDisposable")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bad idea. Normal qubes intentionally have no permission to call admin.vm.CreateDisposable (nor any other admin.* calls), but they may be granted calls to the @dispvm target (possibly choosing different disposable template depending on the requested service).

Requiring extended admin API access just to measure time a bit better is no-go...

Copy link
Contributor Author

@ben-grande ben-grande Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Background:

commit 9bb59cdd20fa551563a04346559be2f09500708a
Author: Marek Marczykowski-Górecki <[email protected]>
Date:   Sun Aug 6 12:22:47 2017 +0200

    vm: add DispVMWrapper for calling a single service in new DispVM

    This is a wrapper to use `$dispvm` target of qrexec call, just like any
    other service call in qubesadmin module - using vm.run_service().
    When running in dom0, qrexec-client-vm is not available, so DispVM needs
    to be created "manually", using appropriate Admin API call
    (admin.vm.CreateDisposable).

    QubesOS/qubes-issues#2974

I did not comprehend why using $dispvm was necessary, thought it served the necessity at that time only.

This is a bad idea. Normal qubes intentionally have no permission to call admin.vm.CreateDisposable (nor any other admin.* calls), but they may be granted calls to the @dispvm target (possibly choosing different disposable template depending on the requested service).

I'm not sure I completely understand this. What you are saying is that this is problematic when core-admin-client is running on a domU?

Requiring extended admin API access just to measure time a bit better is no-go...

What about a using the old code, maintaining the use of $dispvm*, and adding method just to create the disposable if self._method_dest.startswith("$dispvm")?

class DispVMWrapper(QubesVM):
    """Wrapper class for new DispVM, supporting only service call

    Note that when running in dom0, one need to manually kill the DispVM after
    service call ends.
    """

    def create_disposable(self):
        """Create dispvm at service call or run service directly."""
        if self._method_dest.startswith("$dispvm"):
            if self._method_dest.startswith("$dispvm:"):
                method_dest = self._method_dest[len("$dispvm:") :]
            else:
                method_dest = "dom0"
            dispvm = self.app.qubesd_call(
                method_dest, "admin.vm.CreateDisposable"
            )
            dispvm = dispvm.decode("ascii")
            self._method_dest = dispvm
        return self

    def run_service(self, service, **kwargs):
        """Create disposable if absent and run service."""
        if (
            self.app.qubesd_connection_type == "socket"
            and self._method_dest.startswith("$dispvm")
        ):
            self.create_disposable()
            # Service call may wait for session start, give it more time
            # than default 5s
            kwargs["connect_timeout"] = self.qrexec_timeout
        return super().run_service(service, **kwargs)

    def cleanup(self):
        """Cleanup after disposable usage."""
        # in 'remote' case nothing is needed, as DispVM is cleaned up
        # automatically
        if (
            self.app.qubesd_connection_type == "socket"
            and not self._method_dest.startswith("$dispvm")
        ):
            try:
                self.kill()
            except qubesadmin.exc.QubesVMNotRunningError:
                pass

    def start(self):
        """Create disposable if absent and start it."""
        if self._method_dest.startswith("$dispvm"):
            self.create_disposable()
        super().start()


class DispVM(QubesVM):
    """Disposable VM"""

    @classmethod
    def from_appvm(cls, app, appvm):
        """Returns a wrapper for calling service in a new DispVM based on given
        AppVM. If *appvm* is none, use default DispVM template"""

        if appvm:
            method_dest = "$dispvm:" + str(appvm)
        else:
            method_dest = "$dispvm"

        wrapper = DispVMWrapper(app, method_dest)
        return wrapper

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The solution above allows:

dispvm = qubesadmin.vm.DispVM.from_appvm(app, appvm).create_disposable()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you are saying is that this is problematic when core-admin-client is running on a domU?

Yes.

What about a using the old code, maintaining the use of $dispvm*, and adding method just to create the disposable if self._method_dest.startswith("$dispvm")?

This approach is okay, it will preserve using special dispvm target, but will allow explicitly created disposable if somebody really wants.

BTW, while at it, it would be good to change deprecated $dispvm to @dispvm.

Generation cannot help on "from_appvm()" as normal qubes don't have
permission to call "admin.vm.CreateDisposable" or other "admin.*" calls.

With this new method, retrieval of the qube object can be done directly:

  dispvm = qubesadmin.vm.DispVM.from_appvm(app, dvm).create_disposable()

For: QubesOS/qubes-issues#10230
For: QubesOS/qubes-issues#1512
Itches that it is different.
@ben-grande ben-grande marked this pull request as ready for review December 10, 2025 09:13
@ben-grande ben-grande changed the title Create disposable right on from_appvm() Add method to solely create disposable Dec 10, 2025
@ben-grande
Copy link
Contributor Author

PipelineRetryFailed

@codecov
Copy link

codecov bot commented Dec 10, 2025

Codecov Report

❌ Patch coverage is 76.92308% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.15%. Comparing base (a5ea121) to head (96e818a).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
qubesadmin/vm/__init__.py 76.92% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #401      +/-   ##
==========================================
+ Coverage   76.10%   76.15%   +0.04%     
==========================================
  Files          53       53              
  Lines        9287     9285       -2     
==========================================
+ Hits         7068     7071       +3     
+ Misses       2219     2214       -5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ben-grande
Copy link
Contributor Author

Openqa me please.

@qubesos-bot
Copy link

qubesos-bot commented Dec 12, 2025

OpenQA test summary

Complete test suite and dependencies: https://openqa.qubes-os.org/tests/overview?distri=qubesos&version=4.3&build=2025121808-4.3&flavor=pull-requests

Test run included the following:

New failures, excluding unstable

Compared to: https://openqa.qubes-os.org/tests/overview?distri=qubesos&version=4.3&build=2025111104-4.3&flavor=update

Failed tests

No failures!

Fixed failures

Compared to: https://openqa.qubes-os.org/tests/158999#dependencies

10 fixed
  • system_tests_dispvm

    • system_tests: Fail (unknown)
      Tests qubes.tests.integ.dispvm failed (exit code 1), details report...

    • system_tests: Failed (test died)
      # Test died: Some tests failed at qubesos/tests/system_tests.pm lin...

    • TC_20_DispVM_whonix-workstation-18: test_030_edit_file (failure)
      AssertionError: Timeout waiting for editor window

    • TC_20_DispVM_whonix-workstation-18: test_100_open_in_dispvm (failure)
      AssertionError: './open-file test.txt' failed with ./open-file test...

  • system_tests_dispvm_perf@hw7

    • system_tests: Fail (unknown)
      Tests qubes.tests.integ.dispvm_perf failed (exit code 1), details r...

    • system_tests: Failed (test died)
      # Test died: Some tests failed at qubesos/tests/system_tests.pm lin...

    • TC_00_DispVMPerf_debian-13-xfce: test_411_dom0_dispvm_preload_gui_concurrent_api (failure)
      AssertionError: '/usr/lib/qubes/tests/dispvm_perf.py --dvm=test-ins...

    • TC_00_DispVMPerf_debian-13-xfce: test_900_reader (failure)
      AssertionError: '/usr/lib/qubes/tests/dispvm_perf_reader.py --templ...

    • TC_00_DispVMPerf_whonix-workstation-18: test_409_dom0_dispvm_preload_gui_api (failure)
      AssertionError: '/usr/lib/qubes/tests/dispvm_perf.py --dvm=test-ins...

    • TC_00_DispVMPerf_whonix-workstation-18: test_900_reader (failure)
      AssertionError: '/usr/lib/qubes/tests/dispvm_perf_reader.py --templ...

Unstable tests

Details

Performance Tests

Performance degradation:

No issues

Remaining performance tests:

No remaining performance tests

@marmarek marmarek merged commit dcb1ec8 into QubesOS:main Dec 22, 2025
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants