diff --git a/assets/45d/45drives-disks/assets/index.bbceb330.js b/assets/45d/45drives-disks/assets/index.bbceb330.js index 8e0bff9..261f605 100644 --- a/assets/45d/45drives-disks/assets/index.bbceb330.js +++ b/assets/45d/45drives-disks/assets/index.bbceb330.js @@ -23288,6 +23288,21 @@ function zfsAnimation(p5) { }); return true; }; + p5.showStorageGroupPeers = (cd, animationInfo, diskLocations2, y_offset = 0) => { + const currentDisk = animationInfo?.animation_disks?.[cd]; + const group = animationInfo?.animation_groups?.[currentDisk?.group] || []; + if (!currentDisk || group.length < 2) { + return false; + } + group.forEach((dsk) => { + const dsk_idx = diskLocations2.findIndex((loc) => loc.BAY === dsk.name); + if (dsk_idx < 0 || !diskLocations2[dsk_idx]?.image) + return; + const loc = diskLocations2[dsk_idx]; + p5.animateZpools(loc.x, loc.y, loc.image.width, loc.image.height + y_offset, p5.zfsAnimationSteps, p5.zfsAnimationIndex, p5.zfsAnimationColors.storage_group.start, p5.zfsAnimationColors.storage_group.end, p5.zfsAnimationDir); + }); + return true; + }; p5.showAnimations = (cd, zfsInfo, diskLocations2, y_offset = 0) => { if (zfsInfo.zfs_installed) { if (Array.isArray(zfsInfo.zpools) && zfsInfo.zfs_disks && zfsInfo.zfs_disks[cd]) { @@ -23338,6 +23353,7 @@ function zfsAnimation(p5) { } }); }); + p5.showStorageGroupPeers(cd, zfsInfo, diskLocations2, y_offset); return true; } } diff --git a/php/zfs_info.php b/php/zfs_info.php index 2b572f1..8860bac 100644 --- a/php/zfs_info.php +++ b/php/zfs_info.php @@ -73,6 +73,17 @@ function zfs_drivemap_lookup() $lookup[$value] = $bay_id; $lookup[basename($value)] = $bay_id; } + + $storage_label = $slot['storage-label'] ?? ''; + if (is_string($storage_label) && preg_match('/^disk([0-9]+)$/', $storage_label, $match)) { + $lookup[$storage_label] = $bay_id; + $lookup['md' . $match[1]] = $bay_id; + $lookup['md' . $match[1] . 'p1'] = $bay_id; + $lookup['/dev/md' . $match[1]] = $bay_id; + $lookup['/dev/md' . $match[1] . 'p1'] = $bay_id; + $lookup['/dev/mapper/md' . $match[1]] = $bay_id; + $lookup['/dev/mapper/md' . $match[1] . 'p1'] = $bay_id; + } } } @@ -401,54 +412,10 @@ function zpool_status_parse($status_obj, $key, $pool_name, $disk_lookup = []) function verify_zfs_device_format($status_obj, $pool_name, $disk_lookup = []) { - // Frontend drive-to-bay mapping relies on "card-drive" aliases (e.g. 1-1). - // Emit warnings when pool members do not follow that convention. + // Non-bay devices such as boot, flash, and standalone NVMe pools are valid + // ZFS members, but there is no slot to annotate in the drivemap UI. + // Keep them in the pool list and skip user-facing warnings. $alert = []; - if (!isset($status_obj[$pool_name])) { - return $alert; - } - - $default_pattern = '/^\t (\d+-\d+)(?:-part[0-9])?\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*/m'; - $unsupported_pattern = '/^\t (\S+)(?:-part[0-9])?\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*/m'; - - preg_match_all($default_pattern, $status_obj[$pool_name], $default_matches, PREG_SET_ORDER); - preg_match_all($unsupported_pattern, $status_obj[$pool_name], $unsupported_matches, PREG_SET_ORDER); - - $default_disks = []; - foreach ($default_matches as $match) { - $default_disks[] = $match[1]; - } - - $unsupported_disks = []; - foreach ($unsupported_matches as $match) { - $unsupported_disks[] = $match[1]; - } - - if (count($unsupported_disks) > count($default_disks)) { - $filtered = array_values(array_diff($unsupported_disks, $default_disks)); - $filtered = array_values(array_filter($filtered, function ($name) { - return !preg_match('/^(\d+-\d+)(?:-part[0-9])/', $name); - })); - $filtered = array_values(array_filter($filtered, function ($name) use ($disk_lookup) { - return !preg_match('/^\d+-\d+$/', canonical_zfs_disk_name($name, $disk_lookup)); - })); - - if (!$filtered) { - return $alert; - } - - $alert[] = "ZFS status displayed by this module for zpool '$pool_name' may be incomplete.\n\n"; - $alert[] = "This module can only display zfs status information for devices that are created using a device alias.\n\n"; - $alert[] = "This can be done using the 45Drives cockpit-zfs-manager package:\nhttps://github.com/45Drives/cockpit-zfs-manager/releases/\n\n"; - if ($filtered) { - $alert[] = "The following zfs devices do not conform:\n"; - foreach ($filtered as $disk) { - $alert[] = "\t $disk\n"; - } - } - $alert[] = "\n"; - } - return $alert; } @@ -624,6 +591,9 @@ function generate_zfs_info() } foreach ($pool['vdevs'] as $vdev) { foreach ($vdev['disks'] as $disk) { + if (!isset($disk['name']) || !preg_match('/^\d+-\d+$/', $disk['name'])) { + continue; + } $disk_entries[$disk['name']] = [ 'zpool_name' => $pool['name'], 'zpool_used' => $pool['used'] ?? '-', diff --git a/tests/fixtures/zfs_array/zfs_list.txt b/tests/fixtures/zfs_array/zfs_list.txt new file mode 100644 index 0000000..56aa900 --- /dev/null +++ b/tests/fixtures/zfs_array/zfs_list.txt @@ -0,0 +1,2 @@ +disk1 10G 90G 10G /mnt/disk1 +zmirror 20G 180G 20G /mnt/zmirror diff --git a/tests/fixtures/zfs_array/zpool_iostat_disk1.txt b/tests/fixtures/zfs_array/zpool_iostat_disk1.txt new file mode 100644 index 0000000..3212ed9 --- /dev/null +++ b/tests/fixtures/zfs_array/zpool_iostat_disk1.txt @@ -0,0 +1,5 @@ + capacity operations bandwidth +pool alloc free read write read write +------------------------------------- ----- ----- ----- ----- ----- ----- +disk1 10G 90G 1 2 10M 20M + /dev/mapper/md1p1 10G 90G 1 2 10M 20M diff --git a/tests/fixtures/zfs_array/zpool_iostat_path_disk1.txt b/tests/fixtures/zfs_array/zpool_iostat_path_disk1.txt new file mode 100644 index 0000000..3212ed9 --- /dev/null +++ b/tests/fixtures/zfs_array/zpool_iostat_path_disk1.txt @@ -0,0 +1,5 @@ + capacity operations bandwidth +pool alloc free read write read write +------------------------------------- ----- ----- ----- ----- ----- ----- +disk1 10G 90G 1 2 10M 20M + /dev/mapper/md1p1 10G 90G 1 2 10M 20M diff --git a/tests/fixtures/zfs_array/zpool_iostat_path_zmirror.txt b/tests/fixtures/zfs_array/zpool_iostat_path_zmirror.txt new file mode 100644 index 0000000..6fada8a --- /dev/null +++ b/tests/fixtures/zfs_array/zpool_iostat_path_zmirror.txt @@ -0,0 +1,7 @@ + capacity operations bandwidth +pool alloc free read write read write +------------------------------------- ----- ----- ----- ----- ----- ----- +zmirror 20G 180G 1 2 10M 20M + mirror-0 20G 180G 1 2 10M 20M + /dev/nvme1n1p1 10G 90G 1 2 5M 10M + /dev/nvme2n1p1 10G 90G 1 2 5M 10M diff --git a/tests/fixtures/zfs_array/zpool_iostat_zmirror.txt b/tests/fixtures/zfs_array/zpool_iostat_zmirror.txt new file mode 100644 index 0000000..ed0a34b --- /dev/null +++ b/tests/fixtures/zfs_array/zpool_iostat_zmirror.txt @@ -0,0 +1,7 @@ + capacity operations bandwidth +pool alloc free read write read write +------------------------------------- ----- ----- ----- ----- ----- ----- +zmirror 20G 180G 1 2 10M 20M + mirror-0 20G 180G 1 2 10M 20M + nvme1n1p1 10G 90G 1 2 5M 10M + nvme2n1p1 10G 90G 1 2 5M 10M diff --git a/tests/fixtures/zfs_array/zpool_list.txt b/tests/fixtures/zfs_array/zpool_list.txt new file mode 100644 index 0000000..c08a468 --- /dev/null +++ b/tests/fixtures/zfs_array/zpool_list.txt @@ -0,0 +1,2 @@ +disk1 100G 10G 90G - - 10% 10% 1.00x ONLINE - +zmirror 200G 20G 180G - - 10% 10% 1.00x ONLINE - diff --git a/tests/fixtures/zfs_array/zpool_status_disk1.txt b/tests/fixtures/zfs_array/zpool_status_disk1.txt new file mode 100644 index 0000000..bff7beb --- /dev/null +++ b/tests/fixtures/zfs_array/zpool_status_disk1.txt @@ -0,0 +1,9 @@ + pool: disk1 + state: ONLINE +config: + + NAME STATE READ WRITE CKSUM + disk1 ONLINE 0 0 0 + /dev/mapper/md1p1 ONLINE 0 0 0 + +errors: No known data errors diff --git a/tests/fixtures/zfs_array/zpool_status_path_disk1.txt b/tests/fixtures/zfs_array/zpool_status_path_disk1.txt new file mode 100644 index 0000000..bff7beb --- /dev/null +++ b/tests/fixtures/zfs_array/zpool_status_path_disk1.txt @@ -0,0 +1,9 @@ + pool: disk1 + state: ONLINE +config: + + NAME STATE READ WRITE CKSUM + disk1 ONLINE 0 0 0 + /dev/mapper/md1p1 ONLINE 0 0 0 + +errors: No known data errors diff --git a/tests/fixtures/zfs_array/zpool_status_path_zmirror.txt b/tests/fixtures/zfs_array/zpool_status_path_zmirror.txt new file mode 100644 index 0000000..1e7220c --- /dev/null +++ b/tests/fixtures/zfs_array/zpool_status_path_zmirror.txt @@ -0,0 +1,11 @@ + pool: zmirror + state: ONLINE +config: + + NAME STATE READ WRITE CKSUM + zmirror ONLINE 0 0 0 + mirror-0 ONLINE 0 0 0 + /dev/nvme1n1p1 ONLINE 0 0 0 + /dev/nvme2n1p1 ONLINE 0 0 0 + +errors: No known data errors diff --git a/tests/fixtures/zfs_array/zpool_status_zmirror.txt b/tests/fixtures/zfs_array/zpool_status_zmirror.txt new file mode 100644 index 0000000..5d9cf6b --- /dev/null +++ b/tests/fixtures/zfs_array/zpool_status_zmirror.txt @@ -0,0 +1,11 @@ + pool: zmirror + state: ONLINE +config: + + NAME STATE READ WRITE CKSUM + zmirror ONLINE 0 0 0 + mirror-0 ONLINE 0 0 0 + nvme1n1p1 ONLINE 0 0 0 + nvme2n1p1 ONLINE 0 0 0 + +errors: No known data errors diff --git a/tests/run.php b/tests/run.php index 0c8adb1..289f9fd 100644 --- a/tests/run.php +++ b/tests/run.php @@ -553,7 +553,34 @@ function run_ported_dmap_alias_lines($root, $ctx, $case_name) assert_true(!isset($zfs_raw_info['zfs_disks']['sda1']), 'raw-device zfs_info does not expose raw sda1 key'); assert_true(empty($zfs_raw_info['warnings']), 'raw-device zfs_info suppresses alias warning when drivemap can resolve devices'); +$original_zfs_fixture_dir = getenv('DRIVEMAP_ZFS_FIXTURE_DIR'); +putenv('DRIVEMAP_ZFS_FIXTURE_DIR=' . $fixtures . '/zfs_array'); +try { + [$zfs_array_code, $zfs_array_body] = run_api_action($root, 'zfs_info'); + assert_equal($zfs_array_code, 0, 'zfs_info array-device endpoint exits successfully'); + $zfs_array_info = json_decode($zfs_array_body, true); + assert_true(is_array($zfs_array_info), 'zfs_info array-device endpoint returns JSON'); + assert_true(isset($zfs_array_info['zfs_disks']['1-1']), 'array-device zfs_info maps md1p1 pool to disk1 bay'); + assert_equal($zfs_array_info['zfs_disks']['1-1']['zpool_name'] ?? '', 'disk1', 'array-device zfs_info reports disk1 pool on bay 1-1'); + assert_true(!isset($zfs_array_info['zfs_disks']['/dev/mapper/md1p1']), 'array-device zfs_info does not expose md mapper key'); + assert_true(!isset($zfs_array_info['zfs_disks']['nvme1n1p1']), 'array-device zfs_info ignores unmapped nvme pool members'); + assert_true(!isset($zfs_array_info['zfs_disks']['nvme2n1p1']), 'array-device zfs_info ignores second unmapped nvme pool member'); + assert_true(empty($zfs_array_info['warnings']), 'array-device zfs_info suppresses warning for non-slot zfs devices'); +} finally { + if ($original_zfs_fixture_dir === false) { + putenv('DRIVEMAP_ZFS_FIXTURE_DIR'); + } else { + putenv('DRIVEMAP_ZFS_FIXTURE_DIR=' . $original_zfs_fixture_dir); + } +} + require_once $root . '/php/zfs_info.php'; +$malformed_alerts = verify_zfs_device_format([ + 'tank' => "\ttank ONLINE 0 0 0\n\t mirror-0 ONLINE 0 0 0\n\t bad-disk ONLINE 0 0 0\n", +], 'tank', []); +assert_true(!empty($malformed_alerts), 'zfs device format warns for unresolved malformed members'); +assert_true(strpos(implode('', $malformed_alerts), 'bad-disk') !== false, 'zfs device format warning names malformed member'); + $lookup_refresh_path = $ctx['out_dir'] . '/zfs_lookup_refresh.json'; file_put_contents($lookup_refresh_path, json_encode([ 'rows' => [[