Browse Source

api/admin: implement *.property.GetAll methods

Allow getting all the VM properties with one call. This greatly improve
performance of an applications retrieving many/all of them (qvm-ls,
qubes manager etc)

QubesOS/qubes-issues#5415
Fixes QubesOS/qubes-issues#3293
Marek Marczykowski-Górecki 4 years ago
parent
commit
10f99e5c4a
3 changed files with 72 additions and 4 deletions
  1. 2 0
      Makefile
  2. 38 4
      qubes/api/admin.py
  3. 32 0
      qubes/tests/api_admin.py

+ 2 - 0
Makefile

@@ -35,6 +35,7 @@ ADMIN_API_METHODS_SIMPLE = \
 	admin.pool.volume.Set.rw \
 	admin.pool.volume.Snapshot \
 	admin.property.Get \
+	admin.property.GetAll \
 	admin.property.GetDefault \
 	admin.property.Help \
 	admin.property.HelpRst \
@@ -87,6 +88,7 @@ ADMIN_API_METHODS_SIMPLE = \
 	admin.vm.firewall.SetPolicy \
 	admin.vm.firewall.Reload \
 	admin.vm.property.Get \
+	admin.vm.property.GetAll \
 	admin.vm.property.GetDefault \
 	admin.vm.property.Help \
 	admin.vm.property.HelpRst \

+ 38 - 4
qubes/api/admin.py

@@ -169,7 +169,11 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
 
         self.fire_event_for_permission()
 
-        property_def = dest.property_get_def(self.arg)
+        return self._serialize_property(dest, self.arg)
+
+    @staticmethod
+    def _serialize_property(dest, prop):
+        property_def = dest.property_get_def(prop)
         # explicit list to be sure that it matches protocol spec
         if isinstance(property_def, qubes.vm.VMProperty):
             property_type = 'vm'
@@ -177,21 +181,51 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
             property_type = 'int'
         elif property_def.type is bool:
             property_type = 'bool'
-        elif self.arg == 'label':
+        elif prop == 'label':
             property_type = 'label'
         else:
             property_type = 'str'
 
         try:
-            value = getattr(dest, self.arg)
+            value = getattr(dest, str(prop))
         except AttributeError:
             return 'default=True type={} '.format(property_type)
         else:
             return 'default={} type={} {}'.format(
-                str(dest.property_is_default(self.arg)),
+                str(dest.property_is_default(prop)),
                 property_type,
                 str(value) if value is not None else '')
 
+    @qubes.api.method('admin.vm.property.GetAll', no_payload=True,
+        scope='local', read=True)
+    @asyncio.coroutine
+    def vm_property_get_all(self):
+        """Get values of all VM properties"""
+        return self._property_get_all(self.dest)
+
+    @qubes.api.method('admin.property.GetAll', no_payload=True,
+        scope='global', read=True)
+    @asyncio.coroutine
+    def property_get_all(self):
+        """Get value all global properties"""
+        self.enforce(self.dest.name == 'dom0')
+        return self._property_get_all(self.app)
+
+    def _property_get_all(self, dest):
+        self.enforce(not self.arg)
+
+        properties = dest.property_list()
+
+        properties = self.fire_event_for_filter(properties)
+
+        return ''.join(
+            '{} {}\n'.format(str(prop),
+                             self._serialize_property(dest, prop).
+                             replace('\\', '\\\\').replace('\n', '\\n'))
+            for prop in sorted(properties))
+
+
+
     @qubes.api.method('admin.vm.property.GetDefault', no_payload=True,
         scope='local', read=True)
     @asyncio.coroutine

+ 32 - 0
qubes/tests/api_admin.py

@@ -180,6 +180,38 @@ class TC_00_VMs(AdminAPITestCase):
             b'provides_network')
         self.assertEqual(value, 'type=bool False')
 
+    def test_027_vm_property_get_all(self):
+        # any string property, test \n encoding
+        self.vm.kernelopts = 'opt1\nopt2\nopt3\\opt4'
+        with unittest.mock.patch.object(self.vm, 'property_list') as list_mock:
+            list_mock.return_value = [
+                self.vm.property_get_def('name'),
+                self.vm.property_get_def('default_user'),
+                self.vm.property_get_def('netvm'),
+                self.vm.property_get_def('klass'),
+                self.vm.property_get_def('debug'),
+                self.vm.property_get_def('label'),
+                self.vm.property_get_def('kernelopts'),
+                self.vm.property_get_def('qrexec_timeout'),
+                self.vm.property_get_def('qid'),
+                self.vm.property_get_def('updateable'),
+            ]
+            value = self.call_mgmt_func(b'admin.vm.property.GetAll', b'test-vm1')
+        self.maxDiff = None
+        expected = '''debug default=True type=bool False
+default_user default=True type=str user
+klass default=True type=str AppVM
+label default=False type=label red
+name default=False type=str test-vm1
+qid default=False type=int 2
+qrexec_timeout default=True type=int 60
+updateable default=True type=bool False
+kernelopts default=False type=str opt1\\nopt2\\nopt3\\\\opt4
+netvm default=True type=vm 
+'''
+        self.assertEqual(value, expected)
+
+
     def test_030_vm_property_set_vm(self):
         netvm = self.app.add_new_vm('AppVM', label='red', name='test-net',
             template='test-template', provides_network=True)