Browse Source

Added admin.pool.UsageDetails API method

admin.pool.UsageDetails reports the usage data, unlike
admin.pool.Info, which should report the config/unchangeable data.
At the moment admin.Pool.Info still reports usage, to maintain
compatibility, but once all relevant tools are updated,
it should just return configuration data.
Marta Marczykowska-Górecka 4 years ago
parent
commit
2f6497e48d
5 changed files with 75 additions and 10 deletions
  1. 1 0
      Makefile
  2. 21 5
      qubes/api/admin.py
  3. 2 2
      qubes/storage/lvm.py
  4. 19 3
      qubes/tests/api_admin.py
  5. 32 0
      qubes/tests/storage_lvm.py

+ 1 - 0
Makefile

@@ -21,6 +21,7 @@ ADMIN_API_METHODS_SIMPLE = \
 	admin.label.Remove \
 	admin.pool.Add \
 	admin.pool.Info \
+	admin.pool.UsageDetails \
 	admin.pool.List \
 	admin.pool.ListDrivers \
 	admin.pool.Remove \

+ 21 - 5
qubes/api/admin.py

@@ -622,6 +622,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
         self.fire_event_for_permission(pool=pool)
 
         other_info = ''
+        # Deprecated: remove this when all tools using this call are updated
         pool_size = pool.size
         if pool_size is not None:
             other_info += 'size={}\n'.format(pool_size)
@@ -630,11 +631,6 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
         if pool_usage is not None:
             other_info += 'usage={}\n'.format(pool_usage)
 
-        pool_details = pool.usage_details
-        for name in pool_details:
-            if name not in ['data_size', 'data_usage']:
-                other_info += '{}={}\n'.format(name, pool_details[name])
-
         try:
             included_in = pool.included_in(self.app)
             if included_in:
@@ -646,6 +642,26 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
             for prop, val in sorted(pool.config.items())) + \
             other_info
 
+    @qubes.api.method('admin.pool.UsageDetails', no_payload=True,
+        scope='global', read=True)
+    @asyncio.coroutine
+    def pool_usage(self):
+        self.enforce(self.dest.name == 'dom0')
+        self.enforce(self.arg in self.app.pools.keys())
+
+        pool = self.app.pools[self.arg]
+
+        self.fire_event_for_permission(pool=pool)
+
+        usage = ''
+
+        pool_details = pool.usage_details
+
+        for name in sorted(pool_details):
+            usage += '{}={}\n'.format(name, pool_details[name])
+
+        return usage
+
     @qubes.api.method('admin.pool.Add',
         scope='global', write=True)
     @asyncio.coroutine

+ 2 - 2
qubes/storage/lvm.py

@@ -207,8 +207,8 @@ class ThinPool(qubes.storage.Pool):
             metadata_usage = qubes.storage.lvm.size_cache[
                 self.volume_group + '/' + self.thin_pool]['metadata_usage']
         except KeyError:
-            metadata_size = None
-            metadata_usage = None
+            metadata_size = 0
+            metadata_usage = 0
         result['metadata_size'] = metadata_size
         result['metadata_usage'] = metadata_usage
 

+ 19 - 3
qubes/tests/api_admin.py

@@ -582,14 +582,13 @@ class TC_00_VMs(AdminAPITestCase):
             'pool1': unittest.mock.Mock(config={
                 'param1': 'value1', 'param2': 'value2'},
                 usage=102400,
-                size=204800,
-                usage_details={'metadata_size': 500})
+                size=204800)
         }
         self.app.pools['pool1'].included_in.return_value = None
         value = self.call_mgmt_func(b'admin.pool.Info', b'dom0', b'pool1')
 
         self.assertEqual(value,
-            'param1=value1\nparam2=value2\nsize=204800\nusage=102400\nmetadata_size=500\n')
+            'param1=value1\nparam2=value2\nsize=204800\nusage=102400\n')
         self.assertFalse(self.app.save.called)
 
     def test_151_pool_info_unsupported_size(self):
@@ -623,6 +622,23 @@ class TC_00_VMs(AdminAPITestCase):
             '\nincluded_in=pool1\n')
         self.assertFalse(self.app.save.called)
 
+    def test_153_pool_usage(self):
+        self.app.pools = {
+            'pool1': unittest.mock.Mock(config={
+                'param1': 'value1', 'param2': 'value2'},
+                usage_details={
+                    'data_usage': 102400,
+                    'data_size': 204800,
+                    'metadata_size': 1024,
+                    'metadata_usage': 50})
+        }
+        self.app.pools['pool1'].included_in.return_value = None
+        value = self.call_mgmt_func(b'admin.pool.UsageDetails', b'dom0', b'pool1')
+
+        self.assertEqual(value,
+                         'data_size=204800\ndata_usage=102400\nmetadata_size=1024\nmetadata_usage=50\n')
+        self.assertFalse(self.app.save.called)
+
     @unittest.mock.patch('qubes.storage.pool_drivers')
     @unittest.mock.patch('qubes.storage.driver_parameters')
     def test_160_pool_add(self, mock_parameters, mock_drivers):

+ 32 - 0
qubes/tests/storage_lvm.py

@@ -997,6 +997,38 @@ class TC_00_ThinPool(ThinPoolBase):
         self.assertNotIn(volume1, list(self.pool.volumes))
         self.assertNotIn(volume1, list(self.pool.volumes))
 
+    def test_110_metadata_size(self):
+        with self.assertNotRaises(NotImplementedError):
+            usage = self.pool.usage_details
+
+        metadata_size = usage['metadata_size']
+        environ = os.environ.copy()
+        environ['LC_ALL'] = 'C.utf8'
+        pool_size = subprocess.check_output(['sudo', 'lvs', '--noheadings',
+                                             '-o', 'lv_metadata_size',
+                                             '--units', 'b',
+                                             self.pool.volume_group + '/' + self.pool.thin_pool],
+                                            env=environ)
+        self.assertEqual(metadata_size, int(pool_size.strip()[:-1]))
+
+    def test_111_metadata_usage(self):
+        with self.assertNotRaises(NotImplementedError):
+            usage = self.pool.usage_details
+
+        metadata_usage = usage['metadata_usage']
+        environ = os.environ.copy()
+        environ['LC_ALL'] = 'C.utf8'
+
+        pool_info = subprocess.check_output(['sudo', 'lvs', '--noheadings',
+            '-o', 'lv_metadata_size,metadata_percent',
+            '--units', 'b', self.pool.volume_group + '/' + self.pool.thin_pool],
+            env=environ)
+        pool_size, pool_usage = pool_info.strip().split()
+        pool_size = int(pool_size[:-1])
+        pool_usage = float(pool_usage)
+        self.assertEqual(metadata_usage, int(pool_size * pool_usage / 100))
+
+
 @skipUnlessLvmPoolExists
 class TC_01_ThinPool(ThinPoolBase, qubes.tests.SystemTestCase):
     ''' Sanity tests for :py:class:`qubes.storage.lvm.ThinPool` '''