Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/libblockdev-sections.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ bd_dm_name_from_node
bd_dm_node_from_name
bd_dm_map_exists
bd_dm_get_subsystem_from_name
bd_dm_get_thin_pool_stats
BDDMThinPoolStats
bd_dm_thin_pool_stats_copy
bd_dm_thin_pool_stats_free
BDDMTech
BDDMTechMode
bd_dm_is_tech_avail
Expand Down
90 changes: 90 additions & 0 deletions src/lib/plugin_apis/dm.api
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,83 @@ typedef enum {
BD_DM_TECH_MODE_QUERY = 1 << 2,
} BDDMTechMode;

#define BD_DM_TYPE_THIN_POOL_STATS (bd_dm_thin_pool_stats_get_type ())
GType bd_dm_thin_pool_stats_get_type ();

/**
* BDDMThinPoolStats:
* @used_metadata_blocks: number of used metadata blocks
* @total_metadata_blocks: total number of metadata blocks
* @used_data_blocks: number of used data blocks
* @total_data_blocks: total number of data blocks
* @read_only: whether the metadata may not be changed
* @fail: whether the pool fails all I/O
* @out_of_data_space: whether the pool ran out of data space
* @needs_check: whether the metadata needs a check
*
* The block sizes are not reported by the device mapper thin pool status, only
* the numbers of blocks. To get the space usage as a percentage divide the used
* blocks by the total blocks.
*/
typedef struct BDDMThinPoolStats {
guint64 used_metadata_blocks;
guint64 total_metadata_blocks;
guint64 used_data_blocks;
guint64 total_data_blocks;
gboolean read_only;
gboolean fail;
gboolean out_of_data_space;
gboolean needs_check;
} BDDMThinPoolStats;

/**
* bd_dm_thin_pool_stats_copy: (skip)
* @data: (nullable): %BDDMThinPoolStats to copy
*
* Creates a new copy of @data.
*/
BDDMThinPoolStats* bd_dm_thin_pool_stats_copy (BDDMThinPoolStats *data) {
if (data == NULL)
return NULL;

BDDMThinPoolStats *new = g_new0 (BDDMThinPoolStats, 1);

new->used_metadata_blocks = data->used_metadata_blocks;
new->total_metadata_blocks = data->total_metadata_blocks;
new->used_data_blocks = data->used_data_blocks;
new->total_data_blocks = data->total_data_blocks;
new->read_only = data->read_only;
new->fail = data->fail;
new->out_of_data_space = data->out_of_data_space;
new->needs_check = data->needs_check;

return new;
}

/**
* bd_dm_thin_pool_stats_free: (skip)
* @data: (nullable): %BDDMThinPoolStats to free
*
* Frees @data.
*/
void bd_dm_thin_pool_stats_free (BDDMThinPoolStats *data) {
if (data == NULL)
return;
g_free (data);
}

GType bd_dm_thin_pool_stats_get_type () {
static GType type = 0;

if (G_UNLIKELY(type == 0)) {
type = g_boxed_type_register_static("BDDMThinPoolStats",
(GBoxedCopyFunc) bd_dm_thin_pool_stats_copy,
(GBoxedFreeFunc) bd_dm_thin_pool_stats_free);
}

return type;
}

/**
* bd_dm_is_tech_avail:
* @tech: the queried tech
Expand Down Expand Up @@ -117,4 +194,17 @@ gchar* bd_dm_get_subsystem_from_name (const gchar *device_name, GError **error);
*/
gboolean bd_dm_map_exists (const gchar *map_name, gboolean live_only, gboolean active_only, GError **error);

/**
* bd_dm_get_thin_pool_stats:
* @map_name: name of the DM map (thin pool) to get stats for
* @error: (out) (optional): place to store error (if any)
*
* Returns: (transfer full): physical usage stats for the thin pool @map_name or
* %NULL in case of error (@error is set). This works for any device mapper thin
* pool, it doesn't have to be managed by LVM.
*
* Tech category: %BD_DM_TECH_MAP-%BD_DM_TECH_MODE_QUERY
*/
BDDMThinPoolStats* bd_dm_get_thin_pool_stats (const gchar *map_name, GError **error);

#endif /* BD_DM_API */
140 changes: 140 additions & 0 deletions src/plugins/dm.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,42 @@ GQuark bd_dm_error_quark (void)
return g_quark_from_static_string ("g-bd-dm-error-quark");
}

/**
* bd_dm_thin_pool_stats_copy: (skip)
* @data: (nullable): %BDDMThinPoolStats to copy
*
* Creates a new copy of @data.
*/
BDDMThinPoolStats* bd_dm_thin_pool_stats_copy (BDDMThinPoolStats *data) {
if (data == NULL)
return NULL;

BDDMThinPoolStats *new = g_new0 (BDDMThinPoolStats, 1);

new->used_metadata_blocks = data->used_metadata_blocks;
new->total_metadata_blocks = data->total_metadata_blocks;
new->used_data_blocks = data->used_data_blocks;
new->total_data_blocks = data->total_data_blocks;
new->read_only = data->read_only;
new->fail = data->fail;
new->out_of_data_space = data->out_of_data_space;
new->needs_check = data->needs_check;

return new;
}

/**
* bd_dm_thin_pool_stats_free: (skip)
* @data: (nullable): %BDDMThinPoolStats to free
*
* Frees @data.
*/
void bd_dm_thin_pool_stats_free (BDDMThinPoolStats *data) {
if (data == NULL)
return;
g_free (data);
}

static volatile guint avail_deps = 0;
static GMutex deps_check_lock;

Expand Down Expand Up @@ -393,3 +429,107 @@ gboolean bd_dm_map_exists (const gchar *map_name, gboolean live_only, gboolean a

return ret;
}

/**
* bd_dm_get_thin_pool_stats:
* @map_name: name of the DM map (thin pool) to get stats for
* @error: (out) (optional): place to store error (if any)
*
* Returns: (transfer full): physical usage stats for the thin pool @map_name or
* %NULL in case of error (@error is set). This works for any device mapper thin
* pool, it doesn't have to be managed by LVM.
*
* Tech category: %BD_DM_TECH_MAP-%BD_DM_TECH_MODE_QUERY
*/
BDDMThinPoolStats* bd_dm_get_thin_pool_stats (const gchar *map_name, GError **error) {
struct dm_pool *pool = NULL;
struct dm_task *task = NULL;
struct dm_info info;
struct dm_status_thin_pool *status = NULL;
guint64 start = 0;
guint64 length = 0;
gchar *type = NULL;
gchar *params = NULL;
BDDMThinPoolStats *ret = NULL;

if (geteuid () != 0) {
g_set_error_literal (error, BD_DM_ERROR, BD_DM_ERROR_NOT_ROOT,
"Not running as root, cannot query DM maps");
return NULL;
}

task = dm_task_create (DM_DEVICE_STATUS);
if (!task) {
g_set_error (error, BD_DM_ERROR, BD_DM_ERROR_TASK,
"Failed to create DM task for the thin pool map '%s'", map_name);
return NULL;
}

if (dm_task_set_name (task, map_name) == 0) {
g_set_error (error, BD_DM_ERROR, BD_DM_ERROR_TASK,
"Failed to set name for the DM task for the thin pool map '%s'", map_name);
dm_task_destroy (task);
return NULL;
}

if (dm_task_run (task) == 0) {
g_set_error (error, BD_DM_ERROR, BD_DM_ERROR_TASK,
"Failed to run the DM task for the thin pool map '%s'", map_name);
dm_task_destroy (task);
return NULL;
}

if (dm_task_get_info (task, &info) == 0) {
g_set_error (error, BD_DM_ERROR, BD_DM_ERROR_TASK,
"Failed to get task info for the thin pool map '%s'", map_name);
dm_task_destroy (task);
return NULL;
}

if (!info.exists) {
g_set_error (error, BD_DM_ERROR, BD_DM_ERROR_DEVICE_NOEXIST,
"The thin pool map '%s' doesn't exist", map_name);
dm_task_destroy (task);
return NULL;
}

dm_get_next_target (task, NULL, &start, &length, &type, &params);

if (g_strcmp0 (type, "thin-pool") != 0) {
g_set_error (error, BD_DM_ERROR, BD_DM_ERROR_TASK,
"The map '%s' is not a thin pool", map_name);
dm_task_destroy (task);
return NULL;
}

pool = dm_pool_create ("bd-pool", 20);
if (!pool) {
g_set_error (error, BD_DM_ERROR, BD_DM_ERROR_TASK,
"Failed to create memory pool for parsing the status of '%s'", map_name);
dm_task_destroy (task);
return NULL;
}

if (dm_get_status_thin_pool (pool, params, &status) == 0) {
g_set_error (error, BD_DM_ERROR, BD_DM_ERROR_TASK,
"Failed to get status of the thin pool map '%s'", map_name);
dm_task_destroy (task);
dm_pool_destroy (pool);
return NULL;
}

ret = g_new0 (BDDMThinPoolStats, 1);
ret->used_metadata_blocks = status->used_metadata_blocks;
ret->total_metadata_blocks = status->total_metadata_blocks;
ret->used_data_blocks = status->used_data_blocks;
ret->total_data_blocks = status->total_data_blocks;
ret->read_only = status->read_only != 0;
ret->fail = status->fail != 0;
ret->out_of_data_space = status->out_of_data_space != 0;
ret->needs_check = status->needs_check != 0;

dm_task_destroy (task);
dm_pool_destroy (pool);

return ret;
}
15 changes: 15 additions & 0 deletions src/plugins/dm.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ typedef enum {
BD_DM_TECH_MODE_QUERY = 1 << 2,
} BDDMTechMode;

typedef struct BDDMThinPoolStats {
guint64 used_metadata_blocks;
guint64 total_metadata_blocks;
guint64 used_data_blocks;
guint64 total_data_blocks;
gboolean read_only;
gboolean fail;
gboolean out_of_data_space;
gboolean needs_check;
} BDDMThinPoolStats;

void bd_dm_thin_pool_stats_free (BDDMThinPoolStats *data);
BDDMThinPoolStats* bd_dm_thin_pool_stats_copy (BDDMThinPoolStats *data);

/*
* If using the plugin as a standalone library, the following functions should
* be called to:
Expand All @@ -45,5 +59,6 @@ gboolean bd_dm_map_exists (const gchar *map_name, gboolean live_only, gboolean a
gchar* bd_dm_name_from_node (const gchar *dm_node, GError **error);
gchar* bd_dm_node_from_name (const gchar *map_name, GError **error);
gchar* bd_dm_get_subsystem_from_name (const gchar *device_name, GError **error);
BDDMThinPoolStats* bd_dm_get_thin_pool_stats (const gchar *map_name, GError **error);

#endif /* BD_DM */
58 changes: 57 additions & 1 deletion tests/dm_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
import overrides_hack

from utils import run, create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, TestTags, tag_test, required_plugins
from utils import run, run_command, create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, TestTags, tag_test, required_plugins

import gi
gi.require_version('GLib', '2.0')
Expand Down Expand Up @@ -160,6 +160,62 @@ def test_name_node_bijection(self):
BlockDev.dm_name_from_node("")


class DevMapperThinPoolStats(DevMapperTestCase):
def _clean_up(self):
for map_name in ("testPool", "testData", "testMeta"):
run("dmsetup remove %s >/dev/null 2>&1" % map_name)
super()._clean_up()

def _create_thin_pool(self):
# loop_dev is 1 GiB = 2097152 sectors; carve out a metadata and a data
# device using linear maps and build a thin pool on top of them
ret, _out, err = run_command("dmsetup create testMeta --table '0 16384 linear %s 0'" % self.loop_dev)
self.assertEqual(ret, 0, msg="Failed to create metadata device: %s" % err)

ret, _out, err = run_command("dmsetup create testData --table '0 2080768 linear %s 16384'" % self.loop_dev)
self.assertEqual(ret, 0, msg="Failed to create data device: %s" % err)

# a fresh thin pool needs its metadata superblock zeroed
ret, _out, err = run_command("dd if=/dev/zero of=/dev/mapper/testMeta bs=4096 count=1")
self.assertEqual(ret, 0, msg="Failed to zero the metadata device: %s" % err)

# data block size 128 sectors (64 KiB), low water mark 0
ret, _out, err = run_command("dmsetup create testPool "
"--table '0 2080768 thin-pool /dev/mapper/testMeta /dev/mapper/testData 128 0'")
self.assertEqual(ret, 0, msg="Failed to create the thin pool: %s" % err)

def test_thin_pool_stats(self):
"""Verify that it is possible to get stats for a device mapper thin pool"""

self._create_thin_pool()

stats = BlockDev.dm_get_thin_pool_stats("testPool")
self.assertIsNotNone(stats)

self.assertGreater(stats.total_data_blocks, 0)
self.assertGreater(stats.total_metadata_blocks, 0)
self.assertLessEqual(stats.used_data_blocks, stats.total_data_blocks)
self.assertLessEqual(stats.used_metadata_blocks, stats.total_metadata_blocks)

self.assertFalse(stats.fail)
self.assertFalse(stats.out_of_data_space)
self.assertFalse(stats.needs_check)

def test_thin_pool_stats_errors(self):
"""Verify that getting stats for an invalid thin pool fails as expected"""

# non-existing map
with self.assertRaisesRegex(GLib.GError, "doesn't exist"):
BlockDev.dm_get_thin_pool_stats("testPool")

# existing map that is not a thin pool
succ = BlockDev.dm_create_linear("testMap", self.loop_dev, 100, None)
self.assertTrue(succ)

with self.assertRaisesRegex(GLib.GError, "not a thin pool"):
BlockDev.dm_get_thin_pool_stats("testMap")


class DMNoStorageTest(DevMapperTest):
@tag_test(TestTags.NOSTORAGE)
def test_plugin_version(self):
Expand Down
Loading