Compare commits

...

31 Commits

Author SHA1 Message Date
8dd49393f2 Exit with code 1 instead of aborting when the OSD fails to open its devices
Blockstore errors at startup, such as a data device that cannot be opened,
escaped from the etcd callback that creates the blockstore, and configuration
errors escaped from osd_t construction. vitastor-osd then died with
"terminate called after throwing std::runtime_error" and a core dump. Print
the error and exit with code 1, so that a startup error can be told from a
crash. Other exceptions still abort.
2026-10-26 21:03:12 +03:00
3e803e07d6 Fix that UAF from Yury Luneff :) 2026-10-26 01:46:06 +03:00
Yury Luneff
d5465622ed Add a test for UAF in continue_ops() on slice_rw() failure
I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-26 01:45:35 +03:00
63dc07b4c9 Validate min_offset / max_offset in rm-data 2026-10-25 22:37:06 +03:00
a39319beb7 Fix vitastor-cli ls --tree hiding layers whose parent is filtered out (#101)
I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 22:24:53 +03:00
801231e8d4 Fix vitastor-cli ls-pgs printing an empty table instead of an error
I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 22:23:10 +03:00
33195f9c41 Fix vitastor-cli ls-pgs DEGRADED column showing misplaced data
I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 22:22:37 +03:00
aca9dcb1a9 Tests to yaml 2026-10-25 22:19:29 +03:00
9d1489c86f Remove json_printed flag 2026-10-25 22:19:29 +03:00
be897e278c Fix vitastor-cli fix hanging when an OSD is down
The client queues a request to an OSD which is down until the OSD comes
back, so fix hung forever when the primary of an object's PG or one of
--bad-osds was down. Refuse to start while one of --bad-osds is down,
skip objects whose PG primary is down as it is done for inactive PGs,
and exit with EIO when some objects were skipped or a request failed,
so that a partial repair is not taken for a complete one.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 22:19:27 +03:00
b06faf04cb Fix vitastor-cli describe hanging when an OSD is down
The client queues a request to an OSD which is down until the OSD comes
back, so describe hung forever when a PG primary or an OSD from --osds
was down. Skip such PGs and OSDs with a warning, ask only the primary
of the PG when --pool and --pg are given, and exit with EIO after
printing what could be listed, so that a partial listing is not taken
for a complete one. With --json, stdout stays a valid JSON array.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 22:19:25 +03:00
54b3376d00 Fix vitastor-cli describe listing only one object with --inode
describe --inode sent the range from offset 0 to offset 0 of the inode
and ignored --min-offset and --max-offset, and --max-inode without
--max-offset also ended the range at offset 0 of that inode. The OSD
ends the listing at (max_inode, max_offset) inclusive, so only the
object at offset 0 of the last inode was listed. Honour the offsets
with --inode and end the range at the last offset of max_inode when
--max-offset is not given.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 22:19:23 +03:00
7622bbf2e1 Set CXX standard 2026-10-25 18:15:35 +03:00
48a8720ba8 Fix some compile warnings 2026-10-25 18:08:23 +03:00
dacdd9e8d3 Print vitastor-cli help for ls-osds and user-modify aliases (#104)
vitastor-cli accepts ls-osds and user-modify, but --help does not list
them, so "vitastor-cli --help ls-osds" printed "Unknown command". Add
them to the help and to the docs, which also lacked the pgs alias in the
Russian version. Test that --help knows every name the CLI accepts.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md

Co-authored-by: Yury Luneff <koolkhel@mail.ru>
Co-committed-by: Yury Luneff <koolkhel@mail.ru>
2026-10-25 17:22:42 +03:00
5a6343f21d Refuse an invalid --resize value in vitastor-cli modify (#98)
parse_size() returns 0 for a value it cannot parse, such as 1.5G, and
modify took 0 as "no resize": it printed "No change" and exited with 0.
Check the parse result and fail with EINVAL, as create does for --size.
Numeric sizes passed by internal callers (NFS, dd) parse as before.

Tested: new tests/test_modify_image_resize.sh (`--resize 1.5G`, `10X`, `abc`, empty), fails without the fix and passes with it.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md

Co-authored-by: Yury Luneff <koolkhel@mail.ru>
Co-committed-by: Yury Luneff <koolkhel@mail.ru>
2026-10-25 17:20:17 +03:00
c4359a4a8d Fix missing space in vitastor-cli rm-osd "still up" message
rm-osd printed "OSD 1is still up" and "OSDs 2, 3are still up" because
the verb was appended to the OSD list without a space. Also say "it"
instead of "them" when only one OSD is up.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 17:18:02 +03:00
d3e708dd8d Fix vitastor-cli osd-tree crash when the etcd request fails (#100)
load_osd_tree() stored the etcd error, but loop() still called
format_tree() which dereferenced the placement tree that was never
built, so osd-tree and ls-osd died with SIGSEGV. Return the error
instead.

Tested: no automated test, the test harness cannot fail just this one etcd request. Reproduced with a failure-injecting HTTP proxy in front of etcd: osd-tree and ls-osd exit with 139 (SIGSEGV) before the fix and print the etcd error with EIO after it.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 17:15:23 +03:00
d775042a36 cli/dd: Read a character device input like a pipe
A character device reports st_size 0, so one that supports lseek (e.g.
/dev/mem or an MTD device) was taken as an empty seekable input and dd
always failed with "Input seek position is beyond end of input". Treat
every character device input as non-seekable with unknown size and read
it up to count=, size= or its end. /dev/zero and /dev/urandom don't
seek and were already read this way.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 17:13:55 +03:00
Arsenii Ermilov
303c602ea5 cli/dd: Free the skip buffer when reading the input fails
skip_read() returned on a read error or EOF without freeing the buffer
it allocated, leaking it once per failed dd.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 17:13:14 +03:00
c159b9019d Correct vitastor-cli help for rm, raw-ls, fix and rm-osd
- rm: the --exact and --matching descriptions were swapped.
- raw-ls: show that --pool_id is required, and --pg_num unless --offset
  is given, and when the pool configuration fills in the rest.
- fix: show --bad-osds as required.
- rm-osd: describe -f, --allow-up and --allow-data-loss. Up OSDs are
  refused too, -f covers them and degraded/offline PGs, and
  --allow-data-loss covers incomplete ones and implies -f.

Apply the same corrections to the CLI documentation.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 17:13:00 +03:00
cd2ccf9f37 Merge tests to test_modify_pool 2026-10-25 16:14:44 +03:00
4b5442e84c Limit pg_size to 64 in vitastor-cli create-pool and modify-pool
The CLI accepted pg_size up to 256, but clients and OSDs skip a pool
with pg_size > 64 and the monitor refuses it, so such a pool was
created or switched to a size nobody could serve, without an error.
Refuse pg_size > 64 with EINVAL, also with --force.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 16:13:19 +03:00
32421ed404 Allow to remove level_placement with an empty modify-pool value
An empty --level_placement was dropped before the merge with the old
pool config, so the old rules stayed and could not be removed at all,
and a malformed value was silently dropped the same way. Make an empty
value remove the key, which the monitor treats as no extra rules, and
refuse a value that is not <level>=<rule>[,...] with EINVAL.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 16:12:51 +03:00
8ef4e7db93 Refuse renaming a pool to the name of another pool in vitastor-cli
modify-pool --name did not check the new name against other pools, so
two pools could end up with the same name, and commands addressing a
pool by name then picked either of them. Fail with EEXIST, as
create-pool does. The check uses the pool list that the CAS write is
based on, so a concurrent rename or create is caught by the retry.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 16:11:55 +03:00
beef3d64df cli/dd: Check write alignment before creating the output image
The "Unaligned write to Vitastor is not supported" check ran after the
output image was created or resized, so a seek= or bs= not aligned to
the pool's bitmap granularity failed with EINVAL but left a new or
resized image behind. Check it as soon as the granularity is known.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 16:08:28 +03:00
f308a54fb5 cli/dd: Copy a pipe input to its end when no count= or size= is given
With a non-seekable input and no count= or size=, read_end stayed 0, or
was capped at zero bytes by out_size defaulting to seek=, so dd stopped
after the first block: "cat file | vitastor-cli dd of=copy" copied 1 MB
and exited 0. Leave read_end unlimited then and default out_size only
when the input length is known. A new or truncated image still needs
size=; conv=trunc on a file still truncates it at the seek offset.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 16:08:05 +03:00
9684e1afb3 cli/dd: Fix oflag=append
oflag=append replaced O_RDWR with O_APPEND, so the output file was
opened read-only and every write failed with EBADF. Open it with
O_WRONLY|O_APPEND. An appending output is not seekable, which also
skipped the final fsync: skip it only for outputs that can't be synced
(anything but regular files and block devices). Refuse conv=trunc with
oflag=append, the two contradict each other.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 16:05:38 +03:00
911a79f03b cli/dd: Implement status=noxfer
status=noxfer is listed in the help, but dd ignored it. Make it drop
the final transfer statistics like in GNU dd. The progress line still
follows --progress and, if it was shown, is cleared at the end instead
of being replaced by the statistics. --json still returns them.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 16:05:24 +03:00
ddb699c5e8 cli/dd: Make status=none also suppress the progress output
status=none only dropped the final statistics when --progress 0 was
also given; with the default --progress 1 dd still printed the progress
line and the statistics. Turn progress off with status=none, so only
errors are printed, like in GNU dd. --json still returns the result.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 15:55:50 +03:00
d2b879098d cli/dd: Detect input size also when size= is given
size= turned input size detection off, so dd from an image, a file or a
block device with size= always failed with "Input seek position is
beyond end of input", after the output image was already created or
resized. Always detect the input size: size= only sets the output size.
Also check the input seek position before opening the output.

I accept Vitastor CLA agreement: https://git.yourcmc.ru/vitalif/vitastor/src/branch/master/CLA-en.md
2026-10-25 15:55:34 +03:00
37 changed files with 843 additions and 126 deletions

View File

@@ -221,6 +221,24 @@ jobs:
echo ""
done
test_modify_pool:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_modify_pool.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_etcd_fail:
runs-on: ubuntu-latest
needs: build
@@ -779,6 +797,24 @@ jobs:
echo ""
done
test_modify_image_resize:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_modify_image_resize.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_rm_degraded:
runs-on: ubuntu-latest
needs: build
@@ -797,6 +833,24 @@ jobs:
echo ""
done
test_describe:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_describe.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_snapshot_chain:
runs-on: ubuntu-latest
needs: build
@@ -1193,6 +1247,24 @@ jobs:
echo ""
done
test_cli_help:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_cli_help.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_root_node:
runs-on: ubuntu-latest
needs: build
@@ -1571,6 +1643,24 @@ jobs:
echo ""
done
test_ls_tree:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_ls_tree.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_snapshot_read_bitmap:
runs-on: ubuntu-latest
needs: build
@@ -1823,6 +1913,24 @@ jobs:
echo ""
done
test_osd_open_error:
runs-on: ubuntu-latest
needs: build
container: ${{env.TEST_IMAGE}}:${{github.sha}}
steps:
- name: Run test
id: test
timeout-minutes: 3
run: /root/vitastor/tests/test_osd_open_error.sh
- name: Print logs
if: always() && steps.test.outcome == 'failure'
run: |
for i in /root/vitastor/testdata/*.log /root/vitastor/testdata/*.txt; do
echo "-------- $i --------"
cat $i
echo ""
done
test_enospc:
runs-on: ubuntu-latest
needs: build

View File

@@ -231,8 +231,8 @@ Options:
* `--writers-stopped` allows optimised removal in case of a single 'slim' read-write
child and 'fat' removed parent: the child is merged into parent and parent is renamed
to child in that case. In other cases parent layers are always merged into children.
* `--exact` - remove multiple images with names matching given glob patterns.
* `--matching` - remove multiple images with given names
* `--exact` - remove multiple images with given names.
* `--matching` - remove multiple images with names matching given glob patterns.
* `--down-ok` - continue deletion/merging even if some data will be left on unavailable OSDs.
## flatten
@@ -287,7 +287,7 @@ Describe unclean object locations in the cluster. Options:
## fix
`vitastor-cli fix [--objects <objects>] [--bad-osds <osds>] [--part <part>] [--check no]`
`vitastor-cli fix [--objects <objects>] --bad-osds <osds> [--part <part>] [--check no]`
Fix inconsistent objects in the cluster by deleting some copies.
@@ -298,7 +298,7 @@ Fix inconsistent objects in the cluster by deleting some copies.
Plain text format: 0x<inode>:0x<stripe> <any delimiter> 0x<inode>:0x<stripe> ...
JSON format: [{"inode":"0x...","stripe":"0x..."},...]
--bad-osds <osds>
Remove inconsistent copies/parts of objects from these OSDs, effectively
Required. Remove inconsistent copies/parts of objects from these OSDs, effectively
marking them bad and allowing Vitastor to recover objects from other copies.
--part <number>
Only remove EC part <number> (from 0 to pg_size-1), required for extreme
@@ -316,14 +316,17 @@ Allocate a new OSD number and reserve it by creating empty `/osd/stats/<n>` key.
## rm-osd
`vitastor-cli rm-osd [--force] [--allow-data-loss] [--dry-run] <osd_id> [osd_id...]`
`vitastor-cli rm-osd [-f|--force] [--allow-data-loss] [--allow-up] [--dry-run] <osd_id> [osd_id...]`
Remove metadata and configuration for specified OSD(s) from etcd.
Refuses to remove OSDs with data without `--force` and `--allow-data-loss`.
Refuses to remove OSDs that are up or whose removal makes PGs degraded, offline or incomplete.
With `--dry-run` only checks if deletion is possible without data loss and
redundancy degradation.
* `-f|--force` - remove OSDs even if they are up or PGs become degraded or offline.
* `--allow-data-loss` - remove OSDs even if PGs or objects become incomplete (DATA LOSS). Implies `--force`.
* `--allow-up` - remove OSDs even if they are up.
* `--dry-run` - only check if deletion is possible without data loss and
redundancy degradation.
## osd-tree
@@ -355,7 +358,7 @@ host stump
## ls-osd
`vitastor-cli osds|ls-osd|osd-ls [-l|--long]`
`vitastor-cli osds|ls-osds|ls-osd|osd-ls [-l|--long]`
Show current OSDs as list, optionally with I/O statistics if -l is specified.
@@ -410,8 +413,8 @@ Create a pool. Required parameters:
| <!-- --> | <!-- --> |
|--------------------------|-----------------------------------------------------------------------------------------|
| `-s R` or `--pg_size R` | Number of replicas for replicated pools |
| `--ec N+K` | Number of data (N) and parity (K) chunks for erasure-coded pools |
| `-s R` or `--pg_size R` | Number of replicas for replicated pools, from 1 to 64 |
| `--ec N+K` | Number of data (N) and parity (K) chunks for erasure-coded pools, N+K from 3 to 64 |
| `-n N` or `--pg_count N` | PG count for the new pool (start with 10*\<OSD count\>/pg_size rounded to a power of 2) |
Optional parameters:
@@ -425,7 +428,7 @@ Optional parameters:
| `--block_size 128k` | Put pool only on OSDs with this data block size |
| `--bitmap_granularity 4k` | Put pool only on OSDs with this logical sector size |
| `--immediate_commit none` | Put pool only on OSDs with this or larger immediate_commit (none < small < all) |
| `--level_placement <rules>` | Use additional failure domain rules (example: "dc=112233") |
| `--level_placement <rules>` | Use additional failure domain rules (example: "dc=112233", "" to remove) |
| `--raw_placement <rules>` | Specify raw PG generation rules ([details](../config/pool.en.md#raw_placement)) |
| `--local_reads primary` | Local read policy for replicated pools: primary, nearest or random |
| `--primary_affinity_tags tags` | Prefer to put primary copies on OSDs with all specified tags |
@@ -470,6 +473,8 @@ These, however, can still be modified with -f|--force.
See [create-pool](#create-pool) for parameter descriptions.
Renaming with --name fails if the new name is already taken by a different pool.
Examples:
`vitastor-cli modify-pool pool_A --name pool_B`
@@ -508,7 +513,7 @@ the Vitastor authorization model.
## modify-user
`vitastor-cli modify-user --groups <group1,group2,...> <username>`
`vitastor-cli modify-user|user-modify --groups <group1,group2,...> <username>`
Create or update a user with the given name (matching TLS certificate CN).

View File

@@ -239,8 +239,8 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
у удаляемой цепочки есть только один дочерний образ, содержащий небольшой объём данных.
В этом случае дочерний образ вливается в родительский и удаляется, а родительский
переименовывается в дочерний.
* `--exact` - удалить все образы с именами, подходящими под переданные glob-шаблоны.
* `--matching` - удалить все образы с точно заданными именами.
* `--exact` - удалить все образы с точно заданными именами.
* `--matching` - удалить все образы с именами, подходящими под переданные glob-шаблоны.
* `--down-ok` - продолжать удаление/слияние, даже если часть данных останется неудалённой на недоступных OSD.
## flatten
@@ -302,7 +302,7 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
## fix
`vitastor-cli fix [--objects <объекты>] [--bad-osds <osds>] [--part <номер>] [--check no]`
`vitastor-cli fix [--objects <объекты>] --bad-osds <osds> [--part <номер>] [--check no]`
Исправить неконсистентные (неоднозначные) объекты путём удаления части копий.
@@ -313,9 +313,9 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
Простой формат: 0x<инод>:0x<смещение> <любой разделитель> 0x<инод>:0x<смещение> ...
Формат JSON: [{"inode":"0x<инод>","stripe":"0x<смещение>"},...]
--bad-osds <osds>
Удалить неконсистентные копии/части объектов с данных OSD, таким образом
признавая потерю этих копий и позволяя Vitastor-у восстановить объекты из
других копий.
Обязательная опция. Удалить неконсистентные копии/части объектов с данных
OSD, таким образом признавая потерю этих копий и позволяя Vitastor-у
восстановить объекты из других копий.
--part <номер>
Удалить только части EC с заданным номером (от 0 до pg_size-1). Нужно только
в редких граничных случаях, когда один и тот же OSD содержит несколько частей
@@ -334,14 +334,19 @@ vitastor-cli dd [iimg=<image> | if=<file>] [oimg=<image> | of=<file>] [bs=1M] \
## rm-osd
`vitastor-cli rm-osd [--force] [--allow-data-loss] [--dry-run] <osd_id> [osd_id...]`
`vitastor-cli rm-osd [-f|--force] [--allow-data-loss] [--allow-up] [--dry-run] <osd_id> [osd_id...]`
Удалить метаданные и конфигурацию для заданных OSD из etcd.
Отказывается удалять OSD с данными без опций `--force` и `--allow-data-loss`.
Отказывается удалять работающие OSD, а также OSD, после удаления которых PG станут
деградированными, недоступными (offline) или неполными (incomplete).
С опцией `--dry-run` только проверяет, возможно ли удаление без потери данных и деградации
избыточности.
* `-f|--force` - удалить OSD, даже если они работают или PG станут деградированными или недоступными.
* `--allow-data-loss` - удалить OSD, даже если PG или объекты станут неполными (ПОТЕРЯ ДАННЫХ).
Включает действие `--force`.
* `--allow-up` - удалить OSD, даже если они работают.
* `--dry-run` - только проверить, возможно ли удаление без потери данных и деградации
избыточности.
## osd-tree
@@ -373,7 +378,7 @@ host stump
## ls-osd
`vitastor-cli osds|ls-osd|osd-ls [-l|--long]`
`vitastor-cli osds|ls-osds|ls-osd|osd-ls [-l|--long]`
Показать список OSD, со статистикой ввода-вывода, если установлено -l.
@@ -403,7 +408,7 @@ OSD PARENT UP SIZE USED% TAGS WEIGHT BLOCK BITMAP
## pg-list
`vitastor-cli pg-list|pg-ls|list-pg|ls-pg|ls-pgs [OPTIONS] [state1+state2] [^state3] [...]`
`vitastor-cli pg-list|pg-ls|list-pg|ls-pg|ls-pgs|pgs [OPTIONS] [state1+state2] [^state3] [...]`
Вывести список PG с состояними, удовлетворяющими любому из переданных фильтров (^ или !
в начале фильтра означает отрицание). Опции:
@@ -429,8 +434,8 @@ OSD PARENT UP SIZE USED% TAGS WEIGHT BLOCK BITMAP
| <!-- --> | <!-- --> |
|---------------------------|-----------------------------------------------------------------------------------------------|
| `-s R` или `--pg_size R` | Число копий данных для реплицированных пулов |
| `--ec N+K` | Число частей данных (N) и чётности (K) для пулов с кодами коррекции ошибок |
| `-s R` или `--pg_size R` | Число копий данных для реплицированных пулов, от 1 до 64 |
| `--ec N+K` | Число частей данных (N) и чётности (K) для пулов с кодами коррекции ошибок, N+K от 3 до 64 |
| `-n N` или `--pg_count N` | Число PG для нового пула (начните с 10*\<число OSD\>/pg_size, округлённого до степени двойки) |
Необязательные параметры:
@@ -444,7 +449,7 @@ OSD PARENT UP SIZE USED% TAGS WEIGHT BLOCK BITMAP
| `--block_size 128k` | ...только OSD с данным размером блока |
| `--bitmap_granularity 4k` | ...только OSD с данным размером логического сектора |
| `--immediate_commit none` | ...только OSD с этим или большим immediate_commit (none < small < all) |
| `--level_placement <rules>` | Задать правила дополнительных доменов отказа (пример: "dc=112233") |
| `--level_placement <rules>` | Задать правила дополнительных доменов отказа (пример: "dc=112233", "" — удалить) |
| `--raw_placement <rules>` | Задать низкоуровневые правила генерации PG ([детали](../config/pool.ru.md#raw_placement)) |
| `--local_reads primary` | Политика локальных чтений для реплик: primary, nearest или random |
| `--primary_affinity_tags tags` | Предпочитать OSD со всеми данными тегами для роли первичных |
@@ -487,6 +492,8 @@ OSD PARENT UP SIZE USED% TAGS WEIGHT BLOCK BITMAP
Описания параметров смотрите в [create-pool](#create-pool).
Переименование с --name запрещается, если новое название уже занято другим пулом.
Примеры:
`vitastor-cli modify-pool pool_A --name pool_B`
@@ -527,7 +534,7 @@ OSD PARENT UP SIZE USED% TAGS WEIGHT BLOCK BITMAP
## modify-user
`vitastor-cli modify-user --groups <group1,group2,...> <username>`
`vitastor-cli modify-user|user-modify --groups <group1,group2,...> <username>`
Создать или изменить пользователя с указанным именем (совпадающим с CN TLS-сертификата).

View File

@@ -21,8 +21,11 @@ if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/local/?$")
endif()
set(ENABLE_COVERAGE false CACHE BOOL "Enable code coverage")
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_definitions(-DVITASTOR_VERSION="3.2.2")
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
add_definitions(-D_GNU_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wall -Wno-c99-designator -Wno-vla-cxx-extension -Wno-sign-compare -Wno-comment -Wno-parentheses -Wno-pointer-arith -fdiagnostics-color=always -fno-omit-frame-pointer -fvisibility=hidden -I ${CMAKE_SOURCE_DIR}/src)
add_link_options(-fno-omit-frame-pointer)
if (${WITH_ASAN})
add_definitions(-fsanitize=address)

View File

@@ -572,7 +572,7 @@ void blockstore_heap_t::finish_load()
if (postponed_items.size())
{
// Sort "postponed" items and load in batches
std::sort(postponed_items.begin(), postponed_items.end(), [this](const heap_list_item_t* a, const heap_list_item_t* b)
std::sort(postponed_items.begin(), postponed_items.end(), [](const heap_list_item_t* a, const heap_list_item_t* b)
{
return a->entry.inode < b->entry.inode || a->entry.inode == b->entry.inode &&
(a->entry.stripe < b->entry.stripe || a->entry.stripe == b->entry.stripe &&

View File

@@ -273,7 +273,7 @@ resume_6:
rc->buf = (uint8_t*)memalign_or_die(MEM_ALIGNMENT, rc->len);
data = ((ring_data_t*)sqe->user_data);
data->iov = (iovec){ rc->buf, rc->len };
data->callback = [this, rc](ring_data_t *data)
data->callback = [rc](ring_data_t *data)
{
if (data->res < 0)
{

View File

@@ -355,7 +355,7 @@ resume_11:
BS_SUBMIT_GET_SQE(sqe, data);
io_uring_prep_fsync(sqe, dsk.data_fd, IORING_FSYNC_DATASYNC);
data->iov = { 0 };
data->callback = [this, op, fs = data_fsync_cur](ring_data_t *data)
data->callback = [this, fs = data_fsync_cur](ring_data_t *data)
{
live = true;
if (data->res != 0)

View File

@@ -83,10 +83,8 @@ int blockstore_impl_t::continue_sync(blockstore_op_t *op)
if (dsk.csum_block_size)
{
// More complex check because all journal entries have different lengths
int left = PRIV(op)->sync_big_writes.size();
for (auto & sbw: PRIV(op)->sync_big_writes)
{
left--;
auto & dirty_entry = dirty_db.at(sbw);
uint64_t dyn_size = dsk.dirty_dyn_size(dirty_entry.offset, dirty_entry.len);
if (!space_check.check_available(op, 1, sizeof(journal_entry_big_write) + dyn_size, 0))

View File

@@ -245,7 +245,7 @@ void cluster_client_t::inc_wait(uint64_t opcode, uint64_t flags, cluster_op_t *n
{
next->prev_wait += inc;
assert(next->prev_wait >= 0);
if (!next->prev_wait)
if (!next->prev_wait && !continuing_ops)
{
// Kind of std::vector with local "small vector optimisation"
if (bh_op_count >= bh_op_max)

View File

@@ -71,8 +71,8 @@ struct msgr_rdma_connection_t;
struct msgr_rdma_context_t;
#endif
struct op_aes_xts_encrypt_t;
struct op_aes_xts_decrypt_t;
class op_aes_xts_encrypt_t;
class op_aes_xts_decrypt_t;
void destroy_aes_xts_encrypt(op_aes_xts_encrypt_t *encrypt_ctx);
void destroy_aes_xts_decrypt(op_aes_xts_decrypt_t *decrypt_ctx);
@@ -216,7 +216,7 @@ protected:
friend class copy_op_writer_t;
friend class gcm_op_writer_t;
friend class get_op_writer_t;
friend class osd_client_t;
friend struct osd_client_t;
int keepalive_timer_id = -1;

View File

@@ -284,7 +284,6 @@ bool msgr_handshake_t::on_error(const std::string & prefix)
bool msgr_handshake_t::init(bool server_mode)
{
this->ctx = ctx;
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> kctx(EVP_PKEY_CTX_new(ctx->params, NULL), EVP_PKEY_CTX_free);
if (!kctx)
return on_error("EVP_PKEY_CTX_new with EC params: ");

View File

@@ -108,8 +108,8 @@ static const char* help_text =
" --writers-stopped allows optimised removal in case of a single 'slim' read-write\n"
" child and 'fat' removed parent: the child is merged into parent and parent is renamed\n"
" to child in that case. In other cases parent layers are always merged into children.\n"
" --exact Remove multiple images with names matching given glob patterns.\n"
" --matching Remove multiple images with given names\n"
" --exact Remove multiple images with given names.\n"
" --matching Remove multiple images with names matching given glob patterns.\n"
" --writers-stopped Allow renaming inodes over their read/write children.\n"
" --down-ok Continue deletion/merging even if some data will be left on unavailable OSDs.\n"
"\n"
@@ -145,12 +145,15 @@ static const char* help_text =
" --min-offset, --max-offset\n"
" Restrict listing to specific offsets inside inodes.\n"
"\n"
"vitastor-cli raw-ls [OPTIONS]\n"
"vitastor-cli raw-ls --pool_id ID (--pg_num NUM|--offset NUM) [OPTIONS]\n"
" Find object(s) in the cluster using raw secondary listing operations. Options:\n"
" [--min_inode NUM] [--max_inode NUM] [--offset NUM] [--pg_num NUM] [--pg_count COUNT]\n"
" [--pg_stripe_size NUM] [--osds 1,2,3,...]\n"
" --pool_id (numeric pool ID) is required. --pg_num is required unless --offset is given,\n"
" in which case the PG is calculated from the offset. --pg_count, --pg_stripe_size and\n"
" --osds default to the pool configuration and are required if the pool does not exist.\n"
"\n"
"vitastor-cli fix [--objects <objects>] [--bad-osds <osds>] [--part <part>] [--check no]\n"
"vitastor-cli fix [--objects <objects>] --bad-osds <osds> [--part <part>] [--check no]\n"
" Fix inconsistent objects in the cluster by deleting some copies.\n"
" --objects <objects>\n"
" Objects to fix, either in plain text or JSON format. If not specified,\n"
@@ -158,7 +161,7 @@ static const char* help_text =
" Plain text format: 0x<inode>:0x<stripe> <any delimiter> 0x<inode>:0x<stripe> ...\n"
" JSON format: [{\"inode\":\"0x...\",\"stripe\":\"0x...\"},...]\n"
" --bad-osds <osds>\n"
" Remove inconsistent copies/parts of objects from these OSDs, effectively\n"
" Required. Remove inconsistent copies/parts of objects from these OSDs, effectively\n"
" marking them bad and allowing Vitastor to recover objects from other copies.\n"
" --part <number>\n"
" Only remove EC part <number> (from 0 to pg_size-1), required for extreme\n"
@@ -170,16 +173,19 @@ static const char* help_text =
"vitastor-cli alloc-osd\n"
" Allocate a new OSD number and reserve it by creating empty /osd/stats/<n> key.\n"
"\n"
"vitastor-cli rm-osd [--force] [--allow-data-loss] [--dry-run] <osd_id> [osd_id...]\n"
"vitastor-cli rm-osd [-f|--force] [--allow-data-loss] [--allow-up] [--dry-run] <osd_id> [osd_id...]\n"
" Remove metadata and configuration for specified OSD(s) from etcd.\n"
" Refuses to remove OSDs with data without --force and --allow-data-loss.\n"
" With --dry-run only checks if deletion is possible without data loss and\n"
" redundancy degradation.\n"
" Refuses to remove OSDs that are up or whose removal makes PGs degraded, offline or incomplete.\n"
" -f|--force Remove OSDs even if they are up or PGs become degraded or offline.\n"
" --allow-data-loss Remove OSDs even if PGs or objects become incomplete (DATA LOSS). Implies --force.\n"
" --allow-up Remove OSDs even if they are up.\n"
" --dry-run Only check if deletion is possible without data loss and\n"
" redundancy degradation.\n"
"\n"
"vitastor-cli osd-tree [-l|--long]\n"
" Show current OSD tree, optionally with I/O statistics if -l is specified.\n"
"\n"
"vitastor-cli osds|ls-osd|osd-ls [-l|--long]\n"
"vitastor-cli osds|ls-osds|ls-osd|osd-ls [-l|--long]\n"
" Show current OSDs as list, optionally with I/O statistics if -l is specified.\n"
"\n"
"vitastor-cli modify-osd [--tags tag1,tag2,...] [--reweight <number>] [--noout true/false] <osd_number>\n"
@@ -197,8 +203,8 @@ static const char* help_text =
"\n"
"vitastor-cli create-pool|pool-create <name> (-s <pg_size>|--ec <N>+<K>) -n <pg_count> [OPTIONS]\n"
" Create a pool. Required parameters:\n"
" -s|--pg_size R Number of replicas for replicated pools\n"
" --ec N+K Number of data (N) and parity (K) chunks for erasure-coded pools\n"
" -s|--pg_size R Number of replicas for replicated pools, from 1 to 64\n"
" --ec N+K Number of data (N) and parity (K) chunks for erasure-coded pools, N+K from 3 to 64\n"
" -n|--pg_count N PG count for the new pool (start with 10*<OSD count>/pg_size rounded to a power of 2)\n"
" Optional parameters:\n"
" --pg_minsize <number> R or N+K minus number of failures to tolerate without downtime\n"
@@ -208,7 +214,7 @@ static const char* help_text =
" --block_size 128k Put pool only on OSDs with this data block size\n"
" --bitmap_granularity 4k Put pool only on OSDs with this logical sector size\n"
" --immediate_commit all Put pool only on OSDs with this or larger immediate_commit (none < small < all)\n"
" --level_placement <rules> Use additional failure domain rules (example: \"dc=112233\")\n"
" --level_placement <rules> Use additional failure domain rules (example: \"dc=112233\", \"\" to remove)\n"
" --raw_placement <rules> Specify raw PG generation rules (see documentation for details)\n"
" --local_reads primary Local read policy for replicated pools: primary, nearest or random\n"
" --primary_affinity_tags tags Prefer to put primary copies on OSDs with all specified tags\n"
@@ -235,6 +241,7 @@ static const char* help_text =
" [--immediate_commit <all|small|none>] [--pg_stripe_size <size>]\n"
" These, however, can still be modified with -f|--force.\n"
" See create-pool for parameter descriptions.\n"
" Renaming with --name fails if another pool already has the new name.\n"
" Examples:\n"
" vitastor-cli modify-pool pool_A --name pool_B\n"
" vitastor-cli modify-pool 2 --pg_size 4 -n 128\n"
@@ -253,7 +260,7 @@ static const char* help_text =
"vitastor-cli ls-users|user-ls|ls-user|list-users [<name> ...]\n"
" List users (only with specified names if passed).\n"
"\n"
"vitastor-cli modify-user --type <type> --groups group1,group2,... <username>\n"
"vitastor-cli modify-user|user-modify --type <type> --groups group1,group2,... <username>\n"
" Create or update user permissions. User names match CN of their certificates.\n"
" --type TYPE Set user type: client or admin. Default is client.\n"
" --groups GROUPS Set user's groups.\n"

View File

@@ -37,7 +37,6 @@ struct dd_in_info_t
// in
std::string iimg, ifile;
bool in_direct = false;
bool detect_size = true;
// out
cli_result_t result;
@@ -68,10 +67,7 @@ struct dd_in_info_t
return;
}
in_granularity = pool_it->second.bitmap_granularity;
if (detect_size)
{
in_size = iwatch->cfg.size;
}
in_size = iwatch->cfg.size;
in_seekable = true;
return;
}
@@ -92,33 +88,31 @@ struct dd_in_info_t
{
ifd = 0;
}
if (detect_size)
struct stat st;
if (fstat(ifd, &st) < 0)
{
struct stat st;
if (fstat(ifd, &st) < 0)
result = (cli_result_t){ .err = errno, .text = "Failed to stat "+ifile+": "+std::string(strerror(errno)) };
close(ifd);
ifd = -1;
return;
}
if (S_ISREG(st.st_mode))
{
in_size = st.st_size;
}
else if (S_ISBLK(st.st_mode))
{
if (ioctl(ifd, BLKGETSIZE64, &in_size) < 0)
{
result = (cli_result_t){ .err = errno, .text = "Failed to stat "+ifile+": "+std::string(strerror(errno)) };
result = (cli_result_t){ .err = errno, .text = "Failed to get "+ifile+" size: "+std::string(strerror(errno)) };
close(ifd);
ifd = -1;
return;
}
if (S_ISREG(st.st_mode))
{
in_size = st.st_size;
}
else if (S_ISBLK(st.st_mode))
{
if (ioctl(ifd, BLKGETSIZE64, &in_size) < 0)
{
result = (cli_result_t){ .err = errno, .text = "Failed to get "+ifile+" size: "+std::string(strerror(errno)) };
close(ifd);
ifd = -1;
return;
}
}
}
in_seekable = false;
if (lseek(ifd, 1, SEEK_SET) == (off_t)1)
// a seekable character device has no size (st_size 0), read it like a pipe
if (!S_ISCHR(st.st_mode) && lseek(ifd, 1, SEEK_SET) == (off_t)1)
{
in_seekable = true;
lseek(ifd, 0, SEEK_SET);
@@ -175,7 +169,7 @@ struct dd_out_info_t
return NULL;
}
bool open_output(cli_tool_t *parent, int & state, int base_state)
bool open_output(cli_tool_t *parent, int & state, int base_state, uint64_t oseek, uint64_t blocksize)
{
if (state == base_state)
goto resume_1;
@@ -212,6 +206,13 @@ struct dd_out_info_t
return true;
}
}
if ((oseek % out_granularity) || (blocksize % out_granularity))
{
result = (cli_result_t){ .err = EINVAL, .text = "Unaligned write to Vitastor is not supported" };
parent->cli->st_cli->close_watch(owatch);
owatch = NULL;
return true;
}
if (out_size % 4096)
{
out_size += (4096 - (out_size % 4096));
@@ -288,13 +289,13 @@ resume_2:
}
if (ofile != "")
{
ofd = open(ofile.c_str(), (out_direct ? O_DIRECT : 0) | (out_append ? O_APPEND : O_RDWR) | (out_create ? O_CREAT : 0), 0666);
ofd = open(ofile.c_str(), (out_direct ? O_DIRECT : 0) | (out_append ? O_WRONLY|O_APPEND : O_RDWR) | (out_create ? O_CREAT : 0), 0666);
if (ofd < 0)
{
result = (cli_result_t){ .err = errno, .text = "Failed to open "+ofile+": "+std::string(strerror(errno)) };
return true;
}
if (out_trunc && ftruncate(ofd, out_size) < 0)
if (out_trunc && ftruncate(ofd, out_size ? out_size : oseek) < 0)
{
result = (cli_result_t){ .err = errno, .text = "Failed to truncate "+ofile+": "+std::string(strerror(errno)) };
return true;
@@ -314,6 +315,16 @@ resume_2:
out_seekable = true;
lseek(ofd, 0, SEEK_SET);
}
struct stat st;
if (fstat(ofd, &st) < 0)
{
result = (cli_result_t){ .err = errno, .text = "Failed to stat "+(ofile != "" ? ofile : "stdout")+": "+std::string(strerror(errno)) };
return true;
}
if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode))
{
end_fsync = false;
}
return true;
}
@@ -395,6 +406,7 @@ struct cli_dd_t
uint64_t written_size = 0;
uint64_t written_progress = 0;
timespec tv_begin = {}, tv_progress = {};
bool progress_shown = false;
int state = 0;
int copy_error = 0;
int in_waiting = 0, out_waiting = 0;
@@ -413,6 +425,7 @@ struct cli_dd_t
auto res = read(fd, buf, blocksize < to_skip ? blocksize : to_skip);
if (res <= 0)
{
free(buf);
return res == 0 ? -EPIPE : -errno;
}
to_skip -= res;
@@ -722,6 +735,12 @@ struct cli_dd_t
{
return;
}
if (end && !end_status && !parent->json_output)
{
if (progress_shown)
fprintf(stderr, "\r\033[K");
return;
}
timespec tv_now;
clock_gettime(CLOCK_REALTIME, &tv_now);
double sec_delta = ((tv_now.tv_sec - tv_progress.tv_sec) + (double)(tv_now.tv_nsec - tv_progress.tv_nsec)/1000000000.0);
@@ -764,6 +783,7 @@ struct cli_dd_t
format_size((uint64_t)(delta/sec_delta), true).c_str(),
format_size((uint64_t)(written_size/sec_total), true).c_str()
);
progress_shown = true;
}
}
@@ -826,6 +846,12 @@ struct cli_dd_t
state = 100;
return;
}
if (oinfo.ofile != "" && oinfo.out_append && oinfo.out_trunc)
{
result = (cli_result_t){ .err = EINVAL, .text = "conv=trunc and oflag=append can't be used together" };
state = 100;
return;
}
if ((iinfo.iimg != "" ? "i"+iinfo.iimg : "f"+iinfo.ifile) == (oinfo.oimg != "" ? "i"+oinfo.oimg : "f"+oinfo.ofile))
{
result = (cli_result_t){ .err = EINVAL, .text = "Input and output image/file can't be equal" };
@@ -848,13 +874,20 @@ struct cli_dd_t
state = 100;
return;
}
if (!oinfo.out_size)
if (iinfo.in_seekable && iseek >= iinfo.in_size)
{
iinfo.close_input(parent);
result = (cli_result_t){ .err = EINVAL, .text = "Input seek position is beyond end of input" };
state = 100;
return;
}
if (!oinfo.out_size && (iinfo.in_seekable || bytelimit))
{
oinfo.out_size = oseek + (iinfo.in_seekable && (!bytelimit || iinfo.in_size-iseek < bytelimit) ? iinfo.in_size-iseek : bytelimit);
}
resume_1:
resume_2:
if (!oinfo.open_output(parent, state, 1))
if (!oinfo.open_output(parent, state, 1, oseek, blocksize))
{
return;
}
@@ -871,11 +904,6 @@ resume_2:
goto close_end;
}
// Copy data
if (iinfo.in_seekable && iseek >= iinfo.in_size)
{
result = (cli_result_t){ .err = EINVAL, .text = "Input seek position is beyond end of input" };
goto close_end;
}
if (!iinfo.iwatch && !iinfo.in_seekable && iseek)
{
// Read and ignore some data from input
@@ -889,9 +917,8 @@ resume_2:
in_iodepth = iinfo.in_seekable ? iodepth : 1;
out_iodepth = oinfo.out_seekable ? iodepth : 1;
write_zero = write_zero || !oinfo.out_seekable;
oinfo.end_fsync = oinfo.end_fsync && oinfo.out_seekable;
read_offset = 0;
read_end = iinfo.in_seekable ? iinfo.in_size-iseek : 0;
read_end = iinfo.in_seekable ? iinfo.in_size-iseek : UINT64_MAX;
if (oinfo.out_size && (!read_end || read_end > oinfo.out_size-oseek))
read_end = oinfo.out_size-oseek;
if (bytelimit && (!read_end || read_end > bytelimit))
@@ -1000,10 +1027,14 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_dd(json11::Json cfg)
if (!dd->iodepth)
dd->iodepth = 4;
if (cfg["status"] == "none")
{
dd->end_status = false;
progress = false;
}
else if (cfg["status"] == "noxfer")
dd->end_status = false;
else if (cfg["status"] == "progress")
progress = true;
dd->iinfo.detect_size = cfg["size"].is_null();
dd->oinfo.out_size = parse_size(cfg["size"].as_string());
dd->conv = explode_json(",", cfg["conv"]);
dd->iflag = explode_json(",", cfg["iflag"]);

View File

@@ -41,6 +41,8 @@ struct cli_describe_t
std::vector<osd_num_t> only_osds;
uint64_t min_inode = 0, max_inode = 0;
uint64_t min_offset = 0, max_offset = 0;
std::map<osd_num_t, std::string> skipped_pgs;
bool incomplete = false;
cli_tool_t *parent = NULL;
int state = 0;
@@ -55,6 +57,13 @@ struct cli_describe_t
return state == 100;
}
bool is_osd_up(osd_num_t osd_num)
{
auto & peer_states = parent->cli->st_cli->peer_states;
auto peer_it = peer_states.find(osd_num);
return peer_it != peer_states.end() && !peer_it->second.is_null();
}
void parse_options(json11::Json cfg)
{
uint64_t pool_id;
@@ -83,19 +92,18 @@ struct cli_describe_t
}
only_pg = cfg["pg"].uint64_value();
min_inode = cfg["inode"].uint64_value();
min_offset = stoull_full(cfg["min_offset"].string_value(), 0);
max_offset = stoull_full(cfg["max_offset"].string_value(), 0);
if (min_inode)
{
if (!INODE_POOL(min_inode))
min_inode |= (uint64_t)only_pool << (64-POOL_ID_BITS);
max_inode = min_inode;
min_offset = max_offset = 0;
}
else
{
min_inode = stoull_full(cfg["min_inode"].string_value(), 0); // to support 0x...
max_inode = stoull_full(cfg["max_inode"].string_value(), 0);
min_offset = stoull_full(cfg["min_offset"].string_value(), 0);
max_offset = stoull_full(cfg["max_offset"].string_value(), 0);
if (!min_inode && !max_inode && only_pool)
{
min_inode = (uint64_t)only_pool << (64-POOL_ID_BITS);
@@ -103,6 +111,9 @@ struct cli_describe_t
(((uint64_t)1 << (64-POOL_ID_BITS)) - 1);
}
}
// The OSD ends the listing at (max_inode, max_offset) inclusive
if (max_inode && cfg["max_offset"].is_null())
max_offset = UINT64_MAX;
only_osds = parse_uint64_list(cfg["osds"]);
object_state = stoull_full(cfg["object_state"].string_value(), 0);
if (!object_state && cfg["object_state"].is_string())
@@ -158,10 +169,43 @@ struct cli_describe_t
if (pp.first >= min_pool && (!max_pool || pp.first <= max_pool))
{
for (auto & pgp: pp.second.pg_config)
only_osds.push_back(pgp.second.cur_primary);
{
if (only_pool && only_pg && (pp.first != only_pool || pgp.first != only_pg))
continue;
if (is_osd_up(pgp.second.cur_primary))
only_osds.push_back(pgp.second.cur_primary);
else
{
auto & pgs = skipped_pgs[pgp.second.cur_primary];
pgs += (pgs == "" ? "" : ", ")+std::to_string(pp.first)+"/"+std::to_string(pgp.first);
}
}
}
}
}
else
{
std::vector<osd_num_t> filtered_osds;
for (auto osd_num: only_osds)
{
if (is_osd_up(osd_num))
filtered_osds.push_back(osd_num);
else
skipped_pgs[osd_num] = "";
}
only_osds = filtered_osds;
}
// Requests to an OSD which is down are never answered
for (auto & sk: skipped_pgs)
{
if (!sk.first)
fprintf(stderr, "No primary OSD for PG %s, skipping\n", sk.second.c_str());
else if (sk.second != "")
fprintf(stderr, "OSD %ju is down, skipping PG %s\n", sk.first, sk.second.c_str());
else
fprintf(stderr, "OSD %ju is down, skipping it\n", sk.first);
incomplete = true;
}
remove_duplicates(only_osds);
parent->cli->init_msgr();
if (parent->json_output && parent->is_command_line)
@@ -190,6 +234,7 @@ struct cli_describe_t
{
if (op->reply.hdr.retval < 0)
{
incomplete = true;
fprintf(
stderr, "Failed to describe objects on OSD %ju (retval=%jd)\n",
osd_num, op->reply.hdr.retval
@@ -197,6 +242,7 @@ struct cli_describe_t
}
else if (op->reply.describe.result_bytes != op->reply.hdr.retval * sizeof(osd_reply_describe_item_t))
{
incomplete = true;
fprintf(
stderr, "Invalid response size from OSD %ju (expected %ju bytes, got %ju bytes)\n",
osd_num, op->reply.hdr.retval * sizeof(osd_reply_describe_item_t), op->reply.describe.result_bytes
@@ -263,6 +309,13 @@ struct cli_describe_t
{
result.data = describe_items;
}
if (incomplete)
{
result.err = EIO;
if (parent->json_output && parent->is_command_line)
parent->json_output = false;
result.text = "The listing is incomplete, see the messages above";
}
state = 100;
describe_items.clear();
}

View File

@@ -21,12 +21,20 @@ struct cli_fix_t
json11::Json options;
cli_result_t result;
json11::Json::array fix_result;
bool incomplete = false;
bool is_done()
{
return state == 100;
}
bool is_osd_up(osd_num_t osd_num)
{
auto & peer_states = parent->cli->st_cli->peer_states;
auto peer_it = peer_states.find(osd_num);
return peer_it != peer_states.end() && !peer_it->second.is_null();
}
void parse_objects_str(std::string str)
{
str = trim(str);
@@ -123,6 +131,20 @@ struct cli_fix_t
state = 100;
return;
}
{
std::string down_osds;
for (auto osd_num: bad_osds)
{
if (!is_osd_up(osd_num))
down_osds += (down_osds == "" ? "" : ", ")+std::to_string(osd_num);
}
if (down_osds != "")
{
result = (cli_result_t){ .err = EAGAIN, .text = "Bad OSD(s) "+down_osds+" are down, can't remove object copies from them" };
state = 100;
return;
}
}
remove_duplicates(objects);
parent->cli->init_msgr();
resume_1:
@@ -138,6 +160,7 @@ struct cli_fix_t
if (pool_cfg_it == parent->cli->st_cli->pool_config.end())
{
fprintf(stderr, "Object %jx:%jx is from unknown pool\n", obj.inode, obj.stripe);
incomplete = true;
continue;
}
auto & pool_cfg = pool_cfg_it->second;
@@ -150,9 +173,20 @@ struct cli_fix_t
stderr, "Object %jx:%jx is from PG %u/%u which is not currently active\n",
obj.inode, obj.stripe, pool_cfg_it->first, pg_num
);
incomplete = true;
continue;
}
osd_num_t primary_osd = pg_it->second.cur_primary;
if (!is_osd_up(primary_osd))
{
// Requests to an OSD which is down are never answered
fprintf(
stderr, "Object %jx:%jx is from PG %u/%u whose primary OSD %ju is down\n",
obj.inode, obj.stripe, pool_cfg_it->first, pg_num, primary_osd
);
incomplete = true;
continue;
}
// Describe -> Remove some copies -> Scrub again
osd_op_t *op = new osd_op_t;
op->req = (osd_any_op_t){
@@ -172,6 +206,7 @@ struct cli_fix_t
if (op->reply.hdr.retval < 0 || op->reply.describe.result_bytes != op->reply.hdr.retval * sizeof(osd_reply_describe_item_t))
{
fprintf(stderr, "Failed to describe objects on OSD %ju (retval=%jd)\n", primary_osd, op->reply.hdr.retval);
incomplete = true;
parent->waiting--;
loop();
}
@@ -208,6 +243,7 @@ struct cli_fix_t
(*rm_count)--;
if (rm_op->reply.hdr.retval < 0)
{
incomplete = true;
fprintf(
stderr, "Failed to remove object %jx:%jx from OSD %ju (retval=%jd)\n",
rm_op->req.sec_del.oid.inode, rm_op->req.sec_del.oid.stripe,
@@ -252,6 +288,7 @@ struct cli_fix_t
{
if (scrub_op->reply.hdr.retval < 0 && scrub_op->reply.hdr.retval != -ENOENT)
{
incomplete = true;
fprintf(
stderr, "Failed to scrub %jx:%jx on OSD %ju (retval=%jd)\n",
obj.inode, obj.stripe, primary_osd, scrub_op->reply.hdr.retval
@@ -289,6 +326,11 @@ struct cli_fix_t
{
result.data = fix_result;
}
if (incomplete)
{
result.err = EIO;
result.text = "Some objects were not fixed, see the messages above";
}
state = 100;
}
};

View File

@@ -250,11 +250,17 @@ resume_1:
json11::Json::array to_tree(const json11::Json::array & array)
{
std::set<uint64_t> listed;
for (const auto & item: array)
{
listed.insert(item["inode_id"].uint64_value());
}
std::map<uint64_t, json11::Json::array> children;
for (const auto & item: array)
{
uint64_t parent_id = item["parent_inode_id"].uint64_value();
children[parent_id].push_back(item);
// Layers with a filtered out parent are shown as roots
children[listed.find(parent_id) != listed.end() ? parent_id : 0].push_back(item);
}
json11::Json::array tree;
std::function<void(uint64_t, const std::string &)> add_children = [&](uint64_t parent_id, const std::string & prefix)

View File

@@ -287,7 +287,20 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_modify(json11::Json cfg)
changer->parent = this;
changer->image_name = cfg["image"].string_value();
changer->new_name = cfg["rename"].string_value();
changer->new_size = parse_size(cfg["resize"].as_string());
if (!cfg["resize"].is_null())
{
bool ok = false;
changer->new_size = parse_size(cfg["resize"].as_string(), &ok);
if (!ok)
{
delete changer;
return [size = cfg["resize"].as_string()](cli_result_t & result)
{
result = (cli_result_t){ .err = EINVAL, .text = "Invalid syntax for size: "+size };
return true;
};
}
}
changer->force_size = cfg["force_size"].bool_value();
changer->inc_size = cfg["inc_size"].bool_value();
changer->force = cfg["force"].bool_value();

View File

@@ -412,6 +412,8 @@ resume_1:
load_osd_tree();
if (parent->waiting > 0)
return;
if (state == 100)
return;
format_tree();
state = 100;
}

View File

@@ -251,7 +251,7 @@ resume_1:
{ "title", "MISPLACED" },
});
cols.push_back(json11::Json::object{
{ "key", "fmt_misplaced" },
{ "key", "fmt_degraded" },
{ "title", "DEGRADED" },
});
cols.push_back(json11::Json::object{
@@ -273,6 +273,8 @@ resume_1:
load_pg_stats();
if (parent->waiting > 0)
return;
if (state == 100)
return;
format_pgs();
state = 100;
}

View File

@@ -103,24 +103,22 @@ std::string validate_pool_config(json11::Json::object & new_cfg, json11::Json ol
// level=rule, level=rule, ...
if (!value.is_object())
{
if (!value.is_null() && !value.is_string())
{
return key+" must be a string";
}
json11::Json::object obj;
for (auto & item: explode(",", value.string_value(), true))
{
auto pair = explode("=", item, true);
if (pair.size() >= 2)
if (pair.size() != 2)
{
obj[pair[0]] = pair[1];
return "--level_placement must be <level>=<rule>[,<level>=<rule>...], but it is "+value.string_value();
}
obj[pair[0]] = pair[1];
}
if (obj.size())
{
value = obj;
}
else
{
new_cfg.erase(kv_it++);
continue;
}
// empty value = remove the rules; null keeps the old ones from being merged back
value = obj.size() ? json11::Json(obj) : json11::Json();
}
}
else if (key == "osd_tags" || key == "primary_affinity_tags")
@@ -169,6 +167,10 @@ std::string validate_pool_config(json11::Json::object & new_cfg, json11::Json ol
{
new_cfg.erase("local_reads");
}
if (new_cfg.find("level_placement") != new_cfg.end() && new_cfg["level_placement"].is_null())
{
new_cfg.erase("level_placement");
}
// Prevent autovivification of object keys. Now we don't modify the config, we just check it
json11::Json cfg = new_cfg;
@@ -233,9 +235,10 @@ std::string validate_pool_config(json11::Json::object & new_cfg, json11::Json ol
{
return "PG size can't be smaller than 3 for EC/XOR pools";
}
if (pg_size > 256)
// same limit as in etcd_state_client.cpp and mon/pool_config.js
if (pg_size > 64)
{
return "PG size can't be greater than 256";
return "PG size can't be greater than 64";
}
// PG rules

View File

@@ -116,7 +116,21 @@ resume_1:
state = 100;
return;
}
if (new_cfg["name"] != old_cfg["name"])
{
for (auto & pce: kv.value.object_items())
{
if (pce.second["name"] == new_cfg["name"])
{
result = (cli_result_t){
.err = EEXIST,
.text = "Pool with name \""+new_cfg["name"].string_value()+"\" already exists (ID "+pce.first+")",
};
state = 100;
return;
}
}
}
if (new_cfg.find("used_for_app") != new_cfg.end() && !force)
{
// Check that pool doesn't have images

View File

@@ -329,8 +329,27 @@ std::function<bool(cli_result_t &)> cli_tool_t::start_rm_data(json11::Json cfg)
}
remover->down_ok = cfg["down_ok"].bool_value();
remover->pool_id = INODE_POOL(remover->inode);
remover->min_offset = cfg["min_offset"].uint64_value();
remover->max_offset = cfg["max_offset"].uint64_value();
bool min_max_ok = true;
if (!cfg["min_offset"].is_null())
{
remover->min_offset = cfg["min_offset"].uint64_value();
if (!remover->min_offset && cfg["min_offset"].as_string() != "0")
min_max_ok = false;
}
if (!cfg["max_offset"].is_null())
{
remover->max_offset = cfg["max_offset"].uint64_value();
if (!remover->max_offset && cfg["max_offset"].as_string() != "0")
min_max_ok = false;
}
if (!min_max_ok)
{
return [](cli_result_t & result)
{
result = (cli_result_t){ .err = EINVAL, .text = "Invalid min_offset or max_offset" };
return true;
};
}
return [remover](cli_result_t & result)
{
remover->loop();

View File

@@ -170,7 +170,8 @@ struct rm_osd_t
}
if (still_up.size() && !allow_up)
error += (still_up.size() == 1 ? "OSD " : "OSDs ") + implode(", ", still_up) +
(still_up.size() == 1 ? "is" : "are") + " still up. Use `vitastor-disk purge` to delete them.\n";
(still_up.size() == 1 ? " is" : " are") + " still up. Use `vitastor-disk purge` to delete " +
(still_up.size() == 1 ? "it" : "them") + ".\n";
if (is_dataloss && !force_dataloss && !dry_run)
error += "OSDs not deleted. Please move data to other OSDs or bypass this check with --allow-data-loss if you know what you are doing.\n";
else if (is_warning && !force_warning && !dry_run)

View File

@@ -236,7 +236,16 @@ void osd_t::init_blockstore(std::function<void()> on_init)
if (!json_is_true(this->config["disable_blockstore"]))
{
auto bs_cfg = json_to_string_map(this->config);
this->bs = bs_factory(bs_cfg);
try
{
this->bs = bs_factory(bs_cfg);
}
catch (std::exception & e)
{
fprintf(stderr, "[OSD %ju] %s\n", this->osd_num, e.what());
force_stop(1);
return;
}
// Pre-configure pool PG shards
for (auto & pool_item: st_cli->pool_config)
{

View File

@@ -67,13 +67,21 @@ int main(int narg, char *args[])
ring_loop_t *ringloop = new ring_loop_t(RINGLOOP_DEFAULT_SIZE);
epoll_manager_t *epmgr = new epoll_manager_t(ringloop);
auto st_cli = new etcd_state_client_http_t(epmgr->tfd);
osd = new osd_t(
config, ringloop, epmgr->tfd, std::unique_ptr<etcd_state_client_t>(st_cli),
[ringloop, tfd = epmgr->tfd](blockstore_config_t & cfg)
{
return blockstore_i::create(cfg, ringloop, tfd);
}
);
try
{
osd = new osd_t(
config, ringloop, epmgr->tfd, std::unique_ptr<etcd_state_client_t>(st_cli),
[ringloop, tfd = epmgr->tfd](blockstore_config_t & cfg)
{
return blockstore_i::create(cfg, ringloop, tfd);
}
);
}
catch (std::exception & e)
{
fprintf(stderr, "%s\n", e.what());
return 1;
}
osd->start();
while (1)
{

View File

@@ -558,13 +558,11 @@ void osd_t::scrub_check_results(osd_op_t *cur_op)
}
else
{
int total = 0;
for (int i = 0; i < op_data->stripe_count; i++)
{
if (!op_data->stripes[i].not_exists)
{
// use "missing" flag to distinguish actual read errors and inconsistent chunks
total++;
op_data->stripes[i].missing = true;
}
}

View File

@@ -371,7 +371,7 @@ int main(int narg, char *args[])
auto ringloop = new ring_loop_t(RINGLOOP_DEFAULT_SIZE);
auto epmgr = new epoll_manager_t(ringloop);
auto cli = cluster_client_t::create(ringloop, epmgr->tfd, cfg);
test_data_t data = { .cli = cli, .ringloop = ringloop, inode = inode };
test_data_t data = { .cli = cli, .ringloop = ringloop, .inode = inode };
// Wait for init
cli->on_ready([&]() { data.done = true; });
data.wait();

View File

@@ -1122,6 +1122,37 @@ void test_pg_config_disappears()
printf("[ok] test_pg_config_disappears\n");
}
void test_sync_released_during_continue_ops()
{
json11::Json config;
timerfd_manager_t *tfd = new timerfd_manager_t([](int fd, bool wr, std::function<void(int, int)> callback){});
etcd_mock_t etcd_store;
etcd_state_client_mock_t *mock = new etcd_state_client_mock_t(etcd_store);
mock->pause();
cluster_client_t *cli = new cluster_client_t(NULL, tfd, config, std::unique_ptr<etcd_state_client_t>(mock));
configure_single_pg_pool(mock);
mock->resume();
// The OSD isn't connected: the write waits, and the SYNC waits for the write
int *r1 = test_write(cli, 0, 4096, 0x55);
int *r2 = test_sync(cli);
// The write fails in continue_ops(), and the SYNC it releases completes and is freed at once:
// continue_ops() must not go on from the write to the SYNC
can_complete(r1);
can_complete(r2);
mock->set("/vitastor/pg/config", json11::Json::object {
{ "items", json11::Json::object { } }
});
assert(*r1 == -EINVAL);
check_completed(r2);
delete r1;
delete cli;
delete tfd;
printf("[ok] test_sync_released_during_continue_ops\n");
}
void test_snapshot_over_nonexistent_pool()
{
json11::Json config;
@@ -1326,6 +1357,7 @@ int main(int narg, char *args[])
test_sync_fail_dirty_state_loss();
cluster_client_test_t::test_vault();
test_pg_config_disappears();
test_sync_released_during_continue_ops();
test_snapshot_over_nonexistent_pool();
return 0;
}

View File

@@ -18,6 +18,8 @@ SCHEME=ec ./test_change_pg_count.sh
./test_create_nomaxid.sh
./test_modify_pool.sh
./test_etcd_fail.sh
ANTIETCD=1 ./test_etcd_fail.sh
@@ -64,7 +66,10 @@ OLD=1 ./test_move_reappear.sh
./test_rm.sh
./test_modify_image_resize.sh
./test_rm_degraded.sh
./test_describe.sh
./test_snapshot_chain.sh
SCHEME=ec ./test_snapshot_chain.sh
@@ -95,6 +100,8 @@ OLD=1 SCHEME=ec IMMEDIATE_COMMIT=1 ./test_rebalance_verify.sh
./test_dd.sh
./test_cli_help.sh
./test_root_node.sh
./test_switch_primary.sh
@@ -122,6 +129,7 @@ OLD=1 ./test_corrupt_all.sh
./test_reweight_half.sh
./test_snapshot_pool2.sh
./test_ls_tree.sh
./test_snapshot_read_bitmap.sh
TEST_NAME=csum_32k_dmj OSD_ARGS="--data_csum_type crc32c --csum_block_size 32k --inmemory_metadata false --inmemory_journal false" OFFSET_ARGS=$OSD_ARGS ./test_heal.sh
@@ -141,6 +149,8 @@ OLD=1 ./test_resize_auto.sh
./test_osd_tags.sh
./test_osd_open_error.sh
./test_enospc.sh
SCHEME=xor ./test_enospc.sh
IMMEDIATE_COMMIT=1 ./test_enospc.sh

14
tests/test_cli_help.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash -ex
# Test that vitastor-cli --help knows every command name and alias the CLI accepts
. `dirname $0`/common.sh
for cmd in $(grep -o 'cmd\[0\] == "[^"]*"' src/cmd/cli.cpp | sed 's/.*== "//; s/"$//'); do
build/src/cmd/vitastor-cli --help $cmd > ./testdata/help.txt 2>&1 || true
if grep -q '^Unknown command' ./testdata/help.txt; then
format_error "vitastor-cli --help $cmd does not know the command"
fi
done
format_green OK

View File

@@ -24,4 +24,85 @@ $VITASTOR_CLI dd iimg=testimg2 iodepth=4 bs=1M count=128 > ./testdata/testfile1
diff ./testdata/testfile ./testdata/testfile1
rm ./testdata/testfile1
# size= sets the output size, the input size is still detected
$VITASTOR_CLI dd iimg=testimg oimg=testimg3 iodepth=4 size=192M
$VITASTOR_CLI dd if=./testdata/testfile oimg=testimg4 iodepth=4 size=192M
for img in testimg3 testimg4; do
size=$($VITASTOR_CLI ls --json | jq -r '.[] | select(.name == "'$img'") | .size')
[[ "$size" = $((192*1024*1024)) ]] || format_error "dd size=192M created $img with size $size"
$VITASTOR_CLI dd iimg=$img iodepth=4 bs=1M count=128 > ./testdata/testfile1
diff ./testdata/testfile ./testdata/testfile1
rm ./testdata/testfile1
done
# skip beyond the end of input fails before the output image is created
rc=0
$VITASTOR_CLI dd iimg=testimg oimg=testimg5 bs=1M skip=200 count=1 || rc=$?
[[ $rc = 22 ]] || format_error "dd skip beyond the end of input returned $rc instead of EINVAL"
if $VITASTOR_CLI ls --json | jq -e '.[] | select(.name == "testimg5")'; then
format_error "dd skip beyond the end of input created the output image"
fi
# status=none prints nothing, also with the default --progress 1
$VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 status=none 2>./testdata/dd.err
[[ ! -s ./testdata/dd.err ]] || format_error "dd status=none printed: $(cat ./testdata/dd.err)"
diff ./testdata/testfile ./testdata/testfile1
rm ./testdata/testfile1
# status=noxfer drops the final statistics line
$VITASTOR_CLI --progress 0 dd iimg=testimg of=./testdata/testfile1 iodepth=4 status=noxfer 2>./testdata/dd.err
[[ ! -s ./testdata/dd.err ]] || format_error "dd status=noxfer printed: $(cat ./testdata/dd.err)"
rm ./testdata/testfile1
# with --progress 1 the progress line, if it was shown at all, is cleared at the end
clear_line=$'\r\033[K'
for count in 128 4; do
$VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 bs=1M count=$count status=noxfer 2>./testdata/dd.err
if [[ $(wc -l < ./testdata/dd.err) != 0 || ( -s ./testdata/dd.err && "$(cat ./testdata/dd.err)" != *copied*"$clear_line" ) ]]; then
format_error "dd status=noxfer count=$count printed: $(cat -v ./testdata/dd.err)"
fi
head -c ${count}M ./testdata/testfile | cmp - ./testdata/testfile1
rm ./testdata/testfile1
done
# oflag=append appends to the output file
cp ./testdata/over ./testdata/testfile1
$VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 bs=1M count=8 oflag=append
(cat ./testdata/over; head -c 8M ./testdata/testfile) | cmp - ./testdata/testfile1 || format_error "dd oflag=append did not append to the output file"
rm ./testdata/testfile1
# conv=trunc contradicts oflag=append: fail without touching the output file
cp ./testdata/over ./testdata/testfile1
rc=0
$VITASTOR_CLI dd iimg=testimg of=./testdata/testfile1 iodepth=4 bs=1M count=8 oflag=append conv=trunc || rc=$?
[[ $rc = 22 ]] || format_error "dd conv=trunc oflag=append returned $rc instead of EINVAL"
cmp ./testdata/over ./testdata/testfile1 || format_error "dd conv=trunc oflag=append changed the output file"
rm ./testdata/testfile1
# pipe in without count - copy to the end of input
$VITASTOR_CLI dd iimg=testimg iodepth=4 | $VITASTOR_CLI dd of=./testdata/testfile1 iodepth=4
diff ./testdata/testfile ./testdata/testfile1
rm ./testdata/testfile1
$VITASTOR_CLI dd iimg=testimg iodepth=4 | $VITASTOR_CLI dd of=./testdata/testfile1 iodepth=4 bs=1M seek=4
(head -c 4M /dev/zero; cat ./testdata/testfile) | cmp - ./testdata/testfile1 || format_error "dd from a pipe with seek= did not copy the whole input"
rm ./testdata/testfile1
# unaligned image I/O fails with EINVAL before the output image is created or resized
for args in "if=./testdata/testfile oimg=testimg6 seek=512B" "if=./testdata/testfile oimg=testimg2 seek=512B" "iimg=testimg oimg=testimg7 skip=512B"; do
rc=0
$VITASTOR_CLI dd iodepth=4 $args || rc=$?
[[ $rc = 22 ]] || format_error "dd $args returned $rc instead of EINVAL"
done
if $VITASTOR_CLI ls --json | jq -e '.[] | select(.name == "testimg6" or .name == "testimg7")'; then
format_error "dd with unaligned image I/O created the output image"
fi
size=$($VITASTOR_CLI ls --json | jq -r '.[] | select(.name == "testimg2") | .size')
[[ "$size" = $((128*1024*1024)) ]] || format_error "dd with unaligned seek resized testimg2 to $size"
# a character device is read like a pipe, up to count= or size=
$VITASTOR_CLI dd if=/dev/zero oimg=testimg8 size=32M
size=$($VITASTOR_CLI ls --json | jq -r '.[] | select(.name == "testimg8") | .size')
[[ "$size" = $((32*1024*1024)) ]] || format_error "dd if=/dev/zero size=32M created testimg8 with size $size"
$VITASTOR_CLI dd if=/dev/zero of=./testdata/testfile1 bs=1M count=4 conv=nosparse
head -c 4M /dev/zero | cmp - ./testdata/testfile1 || format_error "dd if=/dev/zero count=4 did not write 4 MiB of zeros"
rm ./testdata/testfile1
format_green OK

98
tests/test_describe.sh Executable file
View File

@@ -0,0 +1,98 @@
#!/bin/bash -ex
# Test vitastor-cli describe and fix on degraded objects and with down OSDs
PG_COUNT=4
PG_SIZE=3
PG_MINSIZE=2
# Keep degraded objects until the checks are done
GLOBAL_CONFIG=',"no_recovery":true'
. `dirname $0`/run_3osds.sh
$VITASTOR_CLI create -s 32M testimg
# Data written while OSD 3 is down is degraded when it comes back
kill -9 $OSD3_PID
$ETCDCTL del /vitastor/osd/state/3
wait_condition 60 "$ETCDCTL get --prefix /vitastor/pg/state/ --print-value-only | jq -s -e '[ .[] | select(.state | index(\"degraded\")) ] | length == $PG_COUNT'" "PG peering without OSD 3"
$VITASTOR_CLI dd if=/dev/urandom oimg=testimg bs=1M count=32
start_osd 3
wait_condition 60 "$ETCDCTL get --prefix /vitastor/pg/state/ --print-value-only | jq -s -e '[ .[] | select((.state | index(\"active\")) and (.state | index(\"has_degraded\"))) ] | length == $PG_COUNT'" "PG peering with OSD 3"
INODE_ID=$($VITASTOR_CLI ls --json | jq -r '.[] | select(.name == "testimg") | .inode_id')
INODE=$(printf '0x%x' $INODE_ID)
INODE_NUM=$((INODE_ID & 0xffffffffffff))
# --inode lists the whole inode, not only its first object
all=$($VITASTOR_CLI describe --json | jq -c '[ .[] | select(.inode == "'$INODE'") | .stripe ] | unique')
if [[ $(echo "$all" | jq length) -le 1 ]]; then
format_error "describe should list more than one degraded object of testimg: $all"
fi
one=$($VITASTOR_CLI describe --pool 1 --inode $INODE_NUM --json | jq -c '[ .[].stripe ] | unique')
if [[ "$one" != "$all" ]]; then
format_error "describe --inode lists objects $one instead of $all"
fi
# --min-offset and --max-offset narrow --inode, and --max-inode includes its whole inode
second=$(echo "$all" | jq -r '.[1]')
part=$($VITASTOR_CLI describe --pool 1 --inode $INODE_NUM --min-offset $second --max-offset $second --json | jq -c '[ .[].stripe ] | unique')
if [[ "$part" != "[\"$second\"]" ]]; then
format_error "describe --inode --min-offset $second --max-offset $second lists objects $part"
fi
range=$($VITASTOR_CLI describe --min-inode $INODE --max-inode $INODE --json | jq -c '[ .[].stripe ] | unique')
if [[ "$range" != "$all" ]]; then
format_error "describe --min-inode $INODE --max-inode $INODE lists objects $range instead of $all"
fi
# describe and fix must not wait for an OSD which is down.
# Stop the monitor so that PGs keep the down OSD as their primary,
# and stop a primary OSD which has the latest data, so other PGs stay degraded.
VICTIM=$($VITASTOR_CLI ls-pgs --json | jq -r '[ .[].cur_primary | select(. == 1 or . == 2) ] | first')
VICTIM_PG=$($VITASTOR_CLI ls-pgs --json | jq -r '[ .[] | select(.cur_primary == '$VICTIM') | .pg_num ] | first')
OTHER_PG=$($VITASTOR_CLI ls-pgs --json | jq -r '[ .[] | select(.cur_primary != '$VICTIM') | .pg_num ] | first')
OTHER_PG_COUNT=$($VITASTOR_CLI ls-pgs --json | jq '[ .[] | select(.cur_primary != '$VICTIM') ] | length')
if [[ "$VICTIM" = null || "$OTHER_PG_COUNT" = 0 ]]; then
format_error "Expected PGs with primary OSD 1 or 2 and PGs with other primary OSDs"
fi
VICTIM_OBJ=$($VITASTOR_CLI describe --pool 1 --pg $VICTIM_PG --json | jq -r '.[0] | .inode+":"+.stripe')
OTHER_OBJ=$($VITASTOR_CLI describe --pool 1 --pg $OTHER_PG --json | jq -r '.[0] | .inode+":"+.stripe')
kill -9 $MON_PID
eval kill -9 \$OSD${VICTIM}_PID
$ETCDCTL del /vitastor/osd/state/$VICTIM
wait_condition 60 "$ETCDCTL get --prefix /vitastor/pg/state/ --print-value-only | jq -s -e '[ .[] | select(.primary != $VICTIM and (.state | index(\"degraded\"))) ] | length == $OTHER_PG_COUNT'" "PG peering without OSD $VICTIM"
# Keep PG 1/$VICTIM_PG active with the down primary, as it is until the OSD's etcd lease expires
$ETCDCTL put /vitastor/pg/state/1/$VICTIM_PG '{"peers":[1,2,3],"primary":'$VICTIM',"state":["active"]}'
# A listing with skipped OSDs or PGs is incomplete: EIO, but still valid JSON on stdout
for args in "" "--osds 1,2,3" "--pool 1 --pg $VICTIM_PG"; do
rc=0
timeout 30 $VITASTOR_CLI describe $args --json > ./testdata/describe.json 2> ./testdata/describe.err || rc=$?
[[ $rc == 5 ]] || format_error "describe $args exited with $rc instead of EIO with OSD $VICTIM down"
grep -q "OSD $VICTIM is down, skipping \(it\|PG 1/$VICTIM_PG\)" ./testdata/describe.err || \
format_error "describe $args should warn about OSD $VICTIM: $(cat ./testdata/describe.err)"
grep -q "The listing is incomplete" ./testdata/describe.err || \
format_error "describe $args should say that the listing is incomplete: $(cat ./testdata/describe.err)"
count=$(jq length ./testdata/describe.json) || format_error "describe $args printed invalid JSON"
if [[ "$args" == --pool* ]]; then
[[ $count == 0 ]] || format_error "describe $args should list nothing"
else
[[ $count -gt 0 ]] || format_error "describe $args should still list objects from the other OSDs"
fi
done
if timeout 30 $VITASTOR_CLI fix --bad-osds $VICTIM --objects $OTHER_OBJ 2> ./testdata/fix.err; then
format_error "fix should refuse to work with bad OSD $VICTIM down"
fi
grep -q "Bad OSD(s) $VICTIM are down" ./testdata/fix.err || format_error "fix printed: $(cat ./testdata/fix.err)"
# An object which is skipped is not fixed: EIO
rc=0
timeout 30 $VITASTOR_CLI fix --bad-osds 3 --objects $VICTIM_OBJ 2> ./testdata/fix.err || rc=$?
[[ $rc == 5 ]] || format_error "fix exited with $rc instead of EIO on an object from PG 1/$VICTIM_PG with primary OSD $VICTIM down"
grep -q "is from PG 1/$VICTIM_PG whose primary OSD $VICTIM is down" ./testdata/fix.err || \
format_error "fix should skip the object from PG 1/$VICTIM_PG: $(cat ./testdata/fix.err)"
grep -q "Some objects were not fixed" ./testdata/fix.err || \
format_error "fix should say that it skipped objects: $(cat ./testdata/fix.err)"
format_green OK

View File

@@ -36,4 +36,23 @@ $ETCDCTL get --prefix /vitastor/pg/config --print-value-only | \
(. | map_values((. - 1) / 2 | floor) | unique | length) == 6 and
(. | map_values((. - 1) / 4 | floor) | unique | length) == 3) ] | length) == 32'
# A malformed --level_placement is refused, an empty one removes the rules
r=0
$VITASTOR_CLI modify-pool testpool --level_placement rack || r=$?
if [[ $r != 22 ]]; then
format_error "modify-pool --level_placement rack should fail with EINVAL (22), but exited with $r"
fi
$ETCDCTL get --print-value-only /vitastor/config/pools | jq -e '.["1"].level_placement == {"rack":"112233"}' || \
format_error "level_placement should stay rack=112233 after a refused change"
$VITASTOR_CLI modify-pool testpool --level_placement ''
$ETCDCTL get --print-value-only /vitastor/config/pools | jq -e '.["1"] | has("level_placement") | not' || \
format_error "modify-pool --level_placement '' should remove level_placement"
sleep 2
if grep -q 'configuration is invalid' ./testdata/mon.log; then
format_error "Monitor refused the pool configuration without level_placement"
fi
$ETCDCTL get --prefix /vitastor/pg/config --print-value-only | \
jq -s -e '([ .[0].items["1"] | .[].osd_set | map_values(. | tonumber) | select(
(. | map_values((. - 1) / 2 | floor) | unique | length) == 6) ] | length) == 32'
format_green OK

40
tests/test_ls_tree.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/bin/bash -ex
# Test vitastor-cli ls --tree with filters which leave out parent layers
. `dirname $0`/run_3osds.sh
$VITASTOR_CLI create -s 128M testimg
$VITASTOR_CLI snap-create testimg@snap1
$VITASTOR_CLI create --parent testimg@snap1 testclone
$VITASTOR_CLI create-pool testpool2 -s 3 -n 4 --failure_domain osd
wait_pool_up 30 2 3 4
$VITASTOR_CLI create --parent testimg@snap1 -p testpool2 testclone2
# First column of each row without the header
check_tree()
{
local expected=$1
shift
local rows=$($VITASTOR_CLI ls --tree "$@" | tail -n +2 | sed 's/ .*//' | tr '\n' ';')
if [[ "$rows" != "$expected" ]]; then
format_error "ls --tree $* shows '$rows', expected '$expected'"
fi
}
$VITASTOR_CLI ls --tree
check_tree 'testimg@snap1;├─ testclone;├─ testclone2;└─ testimg;'
check_tree 'testimg@snap1;└─ testimg;' 'testimg*'
# Layers whose parent is filtered out are shown as roots
check_tree 'testimg;' testimg
check_tree 'testclone;testimg;' testclone testimg
check_tree 'testclone;' -n 1
check_tree 'testclone2;' -p testpool2
if ! $VITASTOR_CLI ls --tree testimg | grep -q ' testimg@snap1 *$'; then
format_error "ls --tree testimg should show testimg@snap1 as the parent"
fi
format_green OK

View File

@@ -0,0 +1,28 @@
#!/bin/bash -ex
# Test that modify refuses an invalid --resize value instead of ignoring it
. `dirname $0`/common.sh
wait_etcd
$ETCDCTL put /vitastor/config/pools '{"1":{"name":"testpool","scheme":"replicated","pg_size":2,"pg_minsize":1,"pg_count":1,"failure_domain":"osd"}}'
$VITASTOR_CLI create -s 1G --pool testpool testimg
for size in 1.5G 10X abc ''; do
r=0
$VITASTOR_CLI modify testimg --resize "$size" || r=$?
if [[ $r != 22 ]]; then
format_error "modify --resize '$size' should fail with EINVAL (22), but exited with $r"
fi
done
$ETCDCTL get --print-value-only /vitastor/config/inode/1/1 | jq -e '.size == 1073741824' || \
format_error "Image size should stay 1G after refused resizes"
$VITASTOR_CLI modify testimg --resize 2G
$ETCDCTL get --print-value-only /vitastor/config/inode/1/1 | jq -e '.size == 2147483648' || \
format_error "Image size should be 2G after --resize 2G"
format_green OK

53
tests/test_modify_pool.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/bin/bash -ex
# Test that modify-pool --name refuses to take the name of another pool
. `dirname $0`/common.sh
wait_etcd
POOL='"scheme":"replicated","pg_size":2,"pg_minsize":1,"pg_count":1,"failure_domain":"osd"'
$ETCDCTL put /vitastor/config/pools '{"1":{"name":"pool1",'$POOL'},"2":{"name":"pool2",'$POOL'}}'
for p in pool2 2; do
r=0
$VITASTOR_CLI modify-pool $p --name pool1 || r=$?
if [[ $r != 17 ]]; then
format_error "modify-pool $p --name pool1 should fail with EEXIST (17), but exited with $r"
fi
done
names=$($ETCDCTL get --print-value-only /vitastor/config/pools | jq -c '[ .[].name ]')
if [[ "$names" != '["pool1","pool2"]' ]]; then
format_error "Pool names should stay pool1, pool2, but they are $names"
fi
# A free name and the pool's own name are still accepted
$VITASTOR_CLI modify-pool pool2 --name pool3
$VITASTOR_CLI modify-pool pool3 --name pool3 --pg_count 2
cfg=$($ETCDCTL get --print-value-only /vitastor/config/pools | jq -c '[ .[] | [ .name, .pg_count ] ]')
if [[ "$cfg" != '[["pool1",1],["pool3",2]]' ]]; then
format_error "Pools should be pool1 (1 PG) and pool3 (2 PGs), but they are $cfg"
fi
# Test that create-pool and modify-pool refuse pg_size > 64, which OSDs and clients don't support
$ETCDCTL put /vitastor/config/pools '{"1":{"name":"testpool","scheme":"replicated","pg_size":2,"pg_minsize":1,"pg_count":1,"failure_domain":"osd"}}'
for args in "create-pool bigpool -s 65 -n 1 --force" "create-pool bigpool --ec 62+3 -n 1 --force" "modify-pool testpool -s 65"; do
r=0
$VITASTOR_CLI $args || r=$?
if [[ $r != 22 ]]; then
format_error "$args should fail with EINVAL (22), but exited with $r"
fi
done
$ETCDCTL get --print-value-only /vitastor/config/pools | jq -e 'keys == ["1"] and .["1"].pg_size == 2' || \
format_error "Pool configuration should not change after refused commands"
# 64 is still accepted
$VITASTOR_CLI modify-pool testpool -s 64
$ETCDCTL get --print-value-only /vitastor/config/pools | jq -e '.["1"].pg_size == 64' || \
format_error "modify-pool -s 64 should set pg_size to 64"
format_green OK

14
tests/test_osd_open_error.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash -ex
# An OSD that cannot open its data device must exit with code 1, not abort with a core dump
. `dirname $0`/common.sh
wait_etcd
code=0
build/src/osd/vitastor-osd --osd_num 1 --data_device ./testdata/bin/missing --etcd_address $ETCD_URL &> ./testdata/osd1.log || code=$?
grep -q 'Failed to open data device' ./testdata/osd1.log || format_error "OSD did not report the open error"
[[ $code -eq 1 ]] || format_error "OSD exited with code $code instead of 1"
format_green OK