Переглянути джерело

Merge branch 'windows-tools'

* windows-tools:
  doc: add info what properties are inherited from template
  Add 'gui-emulated' feature
  qvm-start-gui: fix handlign rpc-clipboard feature
Marek Marczykowski-Górecki 5 роки тому
батько
коміт
87122e54c9

+ 21 - 6
doc/manpages/qvm-features.rst

@@ -59,13 +59,28 @@ List of known features
 gui
 ^^^
 
-Qube provide any kind of GUI. Setting this feature to :py:obj:`False` disable
-GUI for given qubes - both gui-agent based and emulated VGA based one. Setting
-this feature to :py:obj:`True` enable gui-agent based GUI (i.e. with support of
-tools installed inside of qube). Not setting this feature at all, enable showing
-VGA emulated output.
+Qube has gui-agent installed. Setting this feature to :py:obj:`True` enables GUI
+based on a gui-agent installed inside the VM.
+See also `gui-emulated` feature.
 
-Default: show emulated VGA output only
+If neither `gui` nor `gui-emulated` is set, emulated VGA is used (if
+applicable for given VM virtualization mode).
+
+gui-emulated
+^^^^^^^^^^^^
+
+Qube provides GUI through emulated VGA. Setting this feature to
+:py:obj:`True` enables emulated VGA output. Note that when gui-agent connects to
+actual VM, emulated VGA output is closed (unless `debug` property is set to
+:py:obj:`True`). It's possible to open emulated VGA output for a running qube,
+regardless of this feature, using `qvm-start-gui --force-stubdomain QUBE_NAME`
+command.
+
+This feature is applicable only when qube's `virt_mode` is set to `hvm`.
+See also `gui` feature.
+
+If neither `gui` nor `gui-emulated` is set, emulated VGA is used (if
+applicable for given VM virtualization mode).
 
 qrexec
 ^^^^^^

+ 17 - 1
doc/manpages/qvm-prefs.rst

@@ -95,6 +95,8 @@ default_user
     Default user used by :manpage:`qvm-run(1)`. Note that it make sense only on
     non-standard template, as the standard one always have "user" account.
 
+    TemplateBasedVM use its template's value as a default.
+
 dispvm_allowed
     Property type: bool
 
@@ -117,7 +119,9 @@ kernel
     Accepted values: kernel version, empty
 
     Kernel version to use. Setting to empty value will use bootloader installed
-    in root volume (of VM's template) - available only for HVM
+    in root volume (of VM's template) - available only for HVM.
+
+    TemplateBasedVM use its template's value as a default.
 
 kernelopts
     Accepted values: string
@@ -128,6 +132,8 @@ kernelopts
     Some helpful options (for debugging purposes): ``earlyprintk=xen``,
     ``init=/bin/bash``
 
+    TemplateBasedVM use its template's value as a default.
+
 label
     Accepted values: ``red``, ``orange``, ``yellow``, ``green``, ``gray``,
     ``blue``, ``purple``, ``black``
@@ -151,6 +157,8 @@ maxmem
     qmemman disabled, this will be overridden by *memory* property (at VM
     startup).
 
+    TemplateBasedVM use its template's value as a default.
+
 memory
     Accepted values: memory size in MB
 
@@ -158,6 +166,8 @@ memory
     - before qmemman starts managing memory for this VM. For VM with qmemman
     disabled, this is static memory size.
 
+    TemplateBasedVM use its template's value as a default.
+
 name
     Accepted values: alphanumerical name
 
@@ -184,6 +194,8 @@ qrexec_timeout
     Ignored if qrexec not installed at all (`qrexec` feature not set, see
     :manpage:`qvm-features(1)`).
 
+    TemplateBasedVM use its template's value as a default.
+
 stubdom_mem
     Accepted values: memory in MB
 
@@ -202,12 +214,16 @@ vcpus
     Number of CPU (cores) available to VM. Some VM types (eg DispVM) will not
     work properly with more than one CPU.
 
+    TemplateBasedVM use its template's value as a default.
+
 virt_mode
     Accepted values: ``hvm``, ``pv``
 
     Virtualisation mode in VM should be started. ``hvm`` allow to install
     operating system without Xen-specific integration.
 
+    TemplateBasedVM use its template's value as a default.
+
 Authors
 -------
 

+ 81 - 10
qubesadmin/tests/tools/qvm_start_gui.py

@@ -87,6 +87,10 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
         self.app.expected_calls[
             ('dom0', 'admin.label.Index', 'red', None)] = \
             b'0\x001'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.feature.CheckWithTemplate',
+            'rpc-clipboard', None)] = \
+            b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
 
         with unittest.mock.patch.object(self.launcher, 'kde_guid_args') as \
                 kde_mock:
@@ -117,6 +121,10 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
         self.app.expected_calls[
             ('dom0', 'admin.label.Index', 'red', None)] = \
             b'0\x001'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.feature.CheckWithTemplate',
+            'rpc-clipboard', None)] = \
+            b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
 
         with unittest.mock.patch.object(self.launcher, 'kde_guid_args') as \
                 kde_mock:
@@ -131,6 +139,40 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
 
         self.assertAllCalled()
 
+    def test_012_common_args_rpc_clipboard(self):
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00test-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.property.Get', 'label', None)] = \
+                b'0\x00default=False type=label red'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
+                b'0\x00default=False type=bool False'
+        self.app.expected_calls[
+            ('dom0', 'admin.label.Get', 'red', None)] = \
+            b'0\x000xff0000'
+        self.app.expected_calls[
+            ('dom0', 'admin.label.Index', 'red', None)] = \
+            b'0\x001'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.feature.CheckWithTemplate',
+            'rpc-clipboard', None)] = \
+            b'0\x001'
+
+        with unittest.mock.patch.object(self.launcher, 'kde_guid_args') as \
+                kde_mock:
+            kde_mock.return_value = []
+
+            args = self.launcher.common_guid_args(self.app.domains['test-vm'])
+            self.assertEqual(args, [
+                '/usr/bin/qubes-guid', '-N', 'test-vm',
+                '-c', '0xff0000',
+                '-i', '/usr/share/icons/hicolor/128x128/devices/appvm-red.png',
+                '-l', '1', '-q', '-Q'])
+
+        self.assertAllCalled()
+
     @unittest.mock.patch('asyncio.create_subprocess_exec')
     def test_020_start_gui_for_vm(self, proc_mock):
         loop = asyncio.new_event_loop()
@@ -186,10 +228,6 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
         self.app.expected_calls[
             ('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
                 b'0\x00default=False type=bool False'
-        self.app.expected_calls[
-            ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'rpc-clipboard',
-            None)] = \
-                b'0\x00True'
         self.app.expected_calls[
             ('test-vm', 'admin.vm.feature.CheckWithTemplate',
             'no-monitor-layout', None)] = \
@@ -199,7 +237,7 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
             loop.run_until_complete(self.launcher.start_gui_for_vm(
                 self.app.domains['test-vm']))
             # common arguments dropped for simplicity
-            proc_mock.assert_called_once_with('-d', '3000', '-n', '-Q')
+            proc_mock.assert_called_once_with('-d', '3000', '-n')
 
         self.assertAllCalled()
 
@@ -226,10 +264,6 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
         self.app.expected_calls[
             ('test-vm', 'admin.vm.property.Get', 'debug', None)] = \
                 b'0\x00default=False type=bool False'
-        self.app.expected_calls[
-            ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'rpc-clipboard',
-            None)] = \
-                b'0\x00True'
         self.app.expected_calls[
             ('test-vm', 'admin.vm.feature.CheckWithTemplate',
             'no-monitor-layout', None)] = \
@@ -252,7 +286,7 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
                 self.app.domains['test-vm']))
             # common arguments dropped for simplicity
             mock_proc.assert_called_once_with(
-                '-d', '3000', '-n', '-Q', '-K', '1234')
+                '-d', '3000', '-n', '-K', '1234')
         finally:
             unittest.mock.patch.stopall()
 
@@ -275,6 +309,43 @@ class TC_00_qvm_start_gui(qubesadmin.tests.QubesTestCase):
         self.app.expected_calls[
             ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
             b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui-emulated',
+            None)] = \
+            b'2\x00QubesFeatureNotFoundError\x00\x00Feature not set\x00'
+        proc_mock = unittest.mock.Mock()
+        with unittest.mock.patch('asyncio.create_subprocess_exec',
+                lambda *args: self.mock_coroutine(proc_mock, *args)):
+            with unittest.mock.patch.object(self.launcher,
+                    'common_guid_args', lambda vm: []):
+                loop.run_until_complete(self.launcher.start_gui_for_stubdomain(
+                    self.app.domains['test-vm']))
+                # common arguments dropped for simplicity
+                proc_mock.assert_called_once_with('-d', '3001', '-t', '3000')
+
+        self.assertAllCalled()
+
+    def test_031_start_gui_for_stubdomain_forced(self):
+        loop = asyncio.new_event_loop()
+        asyncio.set_event_loop(loop)
+        self.addCleanup(loop.close)
+
+        self.app.expected_calls[
+            ('dom0', 'admin.vm.List', None, None)] = \
+            b'0\x00test-vm class=AppVM state=Running\n'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.property.Get', 'xid', None)] = \
+                b'0\x00default=False type=int 3000'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.property.Get', 'stubdom_xid', None)] = \
+                b'0\x00default=False type=int 3001'
+        # self.app.expected_calls[
+        #     ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui', None)] = \
+        #     b'0\x00'
+        self.app.expected_calls[
+            ('test-vm', 'admin.vm.feature.CheckWithTemplate', 'gui-emulated',
+            None)] = \
+            b'0\x001'
         proc_mock = unittest.mock.Mock()
         with unittest.mock.patch('asyncio.create_subprocess_exec',
                 lambda *args: self.mock_coroutine(proc_mock, *args)):

+ 12 - 8
qubesadmin/tools/qvm_start_gui.py

@@ -167,6 +167,9 @@ class GUILauncher(object):
         else:
             guid_cmd += ['-q']
 
+        if vm.features.check_with_template('rpc-clipboard', False):
+            guid_cmd.extend(['-Q'])
+
         guid_cmd += self.kde_guid_args(vm)
         return guid_cmd
 
@@ -191,9 +194,6 @@ class GUILauncher(object):
         if vm.virt_mode == 'hvm':
             guid_cmd.extend(['-n'])
 
-            if vm.features.check_with_template('rpc-clipboard', False):
-                guid_cmd.extend(['-Q'])
-
             stubdom_guid_pidfile = self.guid_pidfile(vm.stubdom_xid)
             if not vm.debug and os.path.exists(stubdom_guid_pidfile):
                 # Terminate stubdom guid once "real" gui agent connects
@@ -215,9 +215,13 @@ class GUILauncher(object):
         This function is a coroutine.
         '''
         want_stubdom = force
-        # if no 'gui' feature set at all, assume no gui agent installed
         if not want_stubdom and \
-                vm.features.check_with_template('gui', None) is None:
+                vm.features.check_with_template('gui-emulated', False):
+            want_stubdom = True
+        # if no 'gui' or 'gui-emulated' feature set at all, use emulated GUI
+        if not want_stubdom and \
+                vm.features.check_with_template('gui', None) is None and \
+                vm.features.check_with_template('gui-emulated', None) is None:
             want_stubdom = True
         if not want_stubdom and vm.debug:
             want_stubdom = True
@@ -241,13 +245,13 @@ class GUILauncher(object):
         :param force_stubdom: Force GUI daemon for stubdomain, even if the
         one for target AppVM is running.
         '''
-        if not vm.features.check_with_template('gui', True):
-            return
-
         if vm.virt_mode == 'hvm':
             yield from self.start_gui_for_stubdomain(vm,
                 force=force_stubdom)
 
+        if not vm.features.check_with_template('gui', True):
+            return
+
         if not os.path.exists(self.guid_pidfile(vm.xid)):
             yield from self.start_gui_for_vm(vm, monitor_layout=monitor_layout)