- `jenkins test make check arm64`
- `jenkins test submodules`
- `jenkins test dashboard`
-- `jenkins test dashboard backend`
+- `jenkins test dashboard cephadm`
+- `jenkins test api`
- `jenkins test docs`
- `jenkins render docs`
- `jenkins test ceph-volume all`
# remove cmake/modules/FindPython* once 3.12 is required
project(ceph
- VERSION 15.2.14
+ VERSION 15.2.15
LANGUAGES CXX C ASM)
foreach(policy
# Contributor: John Coyle <dx9err@gmail.com>
# Maintainer: John Coyle <dx9err@gmail.com>
pkgname=ceph
-pkgver=15.2.14
+pkgver=15.2.15
pkgrel=0
pkgdesc="Ceph is a distributed object store and file system"
pkgusers="ceph"
xmlstarlet
yasm
"
-source="ceph-15.2.14.tar.bz2"
+source="ceph-15.2.15.tar.bz2"
subpackages="
$pkgname-base
$pkgname-common
_udevrulesdir=/etc/udev/rules.d
_python_sitelib=/usr/lib/python2.7/site-packages
-builddir=$srcdir/ceph-15.2.14
+builddir=$srcdir/ceph-15.2.15
build() {
export CEPH_BUILD_VIRTUALENV=$builddir
ceph_bench_log \
ceph_kvstorebench \
ceph_multi_stress_watch \
- ceph_erasure_code \
ceph_erasure_code_benchmark \
ceph_omapbench \
ceph_objectstore_bench \
ceph_bench_log \
ceph_kvstorebench \
ceph_multi_stress_watch \
- ceph_erasure_code \
ceph_erasure_code_benchmark \
ceph_omapbench \
ceph_objectstore_bench \
# main package definition
#################################################################################
Name: ceph
-Version: 15.2.14
+Version: 15.2.15
Release: 0%{?dist}
%if 0%{?fedora} || 0%{?rhel}
Epoch: 2
Group: System/Filesystems
%endif
URL: http://ceph.com/
-Source0: %{?_remote_tarball_prefix}ceph-15.2.14.tar.bz2
+Source0: %{?_remote_tarball_prefix}ceph-15.2.15.tar.bz2
%if 0%{?suse_version}
# _insert_obs_source_lines_here
ExclusiveArch: x86_64 aarch64 ppc64le s390x
# common
#################################################################################
%prep
-%autosetup -p1 -n ceph-15.2.14
+%autosetup -p1 -n ceph-15.2.15
%build
# LTO can be enabled as soon as the following GCC bug is fixed:
%files osd
%{_bindir}/ceph-clsinfo
%{_bindir}/ceph-bluestore-tool
+%{_bindir}/ceph-erasure-code-tool
%{_bindir}/ceph-objectstore-tool
%{_bindir}/ceph-osdomap-tool
%{_bindir}/ceph-osd
%{_bindir}/ceph_bench_log
%{_bindir}/ceph_kvstorebench
%{_bindir}/ceph_multi_stress_watch
-%{_bindir}/ceph_erasure_code
%{_bindir}/ceph_erasure_code_benchmark
%{_bindir}/ceph_omapbench
%{_bindir}/ceph_objectstore_bench
exit 0
fi
+# Stop ceph.target while relabeling if CEPH_AUTO_RESTART_ON_UPGRADE=yes
+SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
+if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+ source $SYSCONF_CEPH
+fi
+
# Check whether the daemons are running
/usr/bin/systemctl status ceph.target > /dev/null 2>&1
STATUS=$?
# Stop the daemons if they were running
if test $STATUS -eq 0; then
- /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
+ if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+ /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
+ fi
fi
# Relabel the files fix for first package install
# Start the daemons iff they were running before
if test $STATUS -eq 0; then
- /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
+ if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+ /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
+ fi
fi
exit 0
exit 0
fi
+ # Stop ceph.target while relabeling if CEPH_AUTO_RESTART_ON_UPGRADE=yes
+ SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
+ if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+ source $SYSCONF_CEPH
+ fi
+
# Check whether the daemons are running
/usr/bin/systemctl status ceph.target > /dev/null 2>&1
STATUS=$?
# Stop the daemons if they were running
if test $STATUS -eq 0; then
- /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
+ if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+ /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
+ fi
fi
/usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
# Start the daemons if they were running before
if test $STATUS -eq 0; then
- /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
+ if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+ /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
+ fi
fi
fi
exit 0
%files osd
%{_bindir}/ceph-clsinfo
%{_bindir}/ceph-bluestore-tool
+%{_bindir}/ceph-erasure-code-tool
%{_bindir}/ceph-objectstore-tool
%{_bindir}/ceph-osdomap-tool
%{_bindir}/ceph-osd
%{_bindir}/ceph_bench_log
%{_bindir}/ceph_kvstorebench
%{_bindir}/ceph_multi_stress_watch
-%{_bindir}/ceph_erasure_code
%{_bindir}/ceph_erasure_code_benchmark
%{_bindir}/ceph_omapbench
%{_bindir}/ceph_objectstore_bench
exit 0
fi
+# Stop ceph.target while relabeling if CEPH_AUTO_RESTART_ON_UPGRADE=yes
+SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
+if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+ source $SYSCONF_CEPH
+fi
+
# Check whether the daemons are running
/usr/bin/systemctl status ceph.target > /dev/null 2>&1
STATUS=$?
# Stop the daemons if they were running
if test $STATUS -eq 0; then
- /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
+ if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+ /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
+ fi
fi
# Relabel the files fix for first package install
# Start the daemons iff they were running before
if test $STATUS -eq 0; then
- /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
+ if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+ /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
+ fi
fi
exit 0
exit 0
fi
+ # Stop ceph.target while relabeling if CEPH_AUTO_RESTART_ON_UPGRADE=yes
+ SYSCONF_CEPH=%{_sysconfdir}/sysconfig/ceph
+ if [ -f $SYSCONF_CEPH -a -r $SYSCONF_CEPH ] ; then
+ source $SYSCONF_CEPH
+ fi
+
# Check whether the daemons are running
/usr/bin/systemctl status ceph.target > /dev/null 2>&1
STATUS=$?
# Stop the daemons if they were running
if test $STATUS -eq 0; then
- /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
+ if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+ /usr/bin/systemctl stop ceph.target > /dev/null 2>&1
+ fi
fi
/usr/sbin/fixfiles -C ${FILE_CONTEXT}.pre restore 2> /dev/null
# Start the daemons if they were running before
if test $STATUS -eq 0; then
- /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
+ if [ "X$CEPH_AUTO_RESTART_ON_UPGRADE" = "Xyes" ] ; then
+ /usr/bin/systemctl start ceph.target > /dev/null 2>&1 || :
+ fi
fi
fi
exit 0
-ceph (15.2.14-1bionic) bionic; urgency=medium
+ceph (15.2.15-1bionic) bionic; urgency=medium
- -- Jenkins Build Slave User <jenkins-build@adami08.front.sepia.ceph.com> Thu, 05 Aug 2021 17:22:18 +0000
+ -- Jenkins Build Slave User <jenkins-build@adami05.front.sepia.ceph.com> Wed, 20 Oct 2021 14:31:05 +0000
+
+ceph (15.2.15-1) stable; urgency=medium
+
+ * New upstream release
+
+ -- Ceph Release Team <ceph-maintainers@ceph.com> Wed, 20 Oct 2021 14:19:54 +0000
ceph (15.2.14-1) stable; urgency=medium
set(boost_sha256 59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722)
string(REPLACE "." "_" boost_version_underscore ${boost_version} )
set(boost_url
- https://dl.bintray.com/boostorg/release/${boost_version}/source/boost_${boost_version_underscore}.tar.bz2)
+ https://boostorg.jfrog.io/artifactory/main/release/${boost_version}/source/boost_${boost_version_underscore}.tar.bz2)
if(CMAKE_VERSION VERSION_GREATER 3.7)
set(boost_url
"${boost_url} http://downloads.sourceforge.net/project/boost/boost/${boost_version}/boost_${boost_version_underscore}.tar.bz2")
lib/systemd/system/ceph-volume@.service
usr/bin/ceph-bluestore-tool
usr/bin/ceph-clsinfo
+usr/bin/ceph-erasure-code-tool
usr/bin/ceph-objectstore-tool
usr/bin/ceph-osdomap-tool
usr/bin/${CEPH_OSD_BASENAME} => /usr/bin/ceph-osd
usr/bin/ceph-client-debug
usr/bin/ceph-coverage
usr/bin/ceph_bench_log
-usr/bin/ceph_erasure_code
usr/bin/ceph_erasure_code_benchmark
usr/bin/ceph_kvstorebench
usr/bin/ceph_multi_stress_watch
lvm/systemd
lvm/list
lvm/zap
+ lvm/migrate
+ lvm/newdb
+ lvm/newwal
simple/index
simple/activate
simple/scan
* :ref:`ceph-volume-lvm-list`
+* :ref:`ceph-volume-lvm-migrate`
+
+* :ref:`ceph-volume-lvm-newdb`
+
+* :ref:`ceph-volume-lvm-newwal`
+
.. not yet implemented
.. * :ref:`ceph-volume-lvm-scan`
--- /dev/null
+.. _ceph-volume-lvm-migrate:
+
+``migrate``
+===========
+
+Moves BlueFS data from source volume(s) to the target one, source volumes
+(except the main, i.e. data or block one) are removed on success.
+
+LVM volumes are permitted for Target only, both already attached or new one.
+
+In the latter case it is attached to the OSD replacing one of the source
+devices.
+
+Following replacement rules apply (in the order of precedence, stop
+on the first match):
+
+ - if source list has DB volume - target device replaces it.
+ - if source list has WAL volume - target device replaces it.
+ - if source list has slow volume only - operation is not permitted,
+ requires explicit allocation via new-db/new-wal command.
+
+Moves BlueFS data from main device to LV already attached as DB::
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from data --target vgname/db
+
+Moves BlueFS data from shared main device to LV which will be attached as a
+new DB::
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from data --target vgname/new_db
+
+Moves BlueFS data from DB device to new LV, DB is replaced::
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from db --target vgname/new_db
+
+Moves BlueFS data from main and DB devices to new LV, DB is replaced::
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from data db --target vgname/new_db
+
+Moves BlueFS data from main, DB and WAL devices to new LV, WAL is removed and
+DB is replaced::
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from data db wal --target vgname/new_db
+
+Moves BlueFS data from main, DB and WAL devices to main device, WAL and DB are
+removed::
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from db wal --target vgname/data
--- /dev/null
+.. _ceph-volume-lvm-newdb:
+
+``new-db``
+===========
+
+Attaches the given logical volume to OSD as a DB.
+Logical volume name format is vg/lv. Fails if OSD has already got attached DB.
+
+Attach vgname/lvname as a DB volume to OSD 1::
+
+ ceph-volume lvm new-db --osd-id 1 --osd-fsid 55BD4219-16A7-4037-BC20-0F158EFCC83D --target vgname/new_db
--- /dev/null
+.. _ceph-volume-lvm-newwal:
+
+``new-wal``
+===========
+
+Attaches the given logical volume to the given OSD as a WAL volume.
+Logical volume format is vg/lv. Fails if OSD has already got attached DB.
+
+Attach vgname/lvname as a WAL volume to OSD 1::
+
+ ceph-volume lvm new-wal --osd-id 1 --osd-fsid 55BD4219-16A7-4037-BC20-0F158EFCC83D --target vgname/new_wal
Optional arguments:
* [-h, --help] show the help message and exit
+* [--no-systemd] skip checking OSD systemd unit
Required arguments:
Optional arguments:
* [-h, --help] show the help message and exit
+* [--no-systemd] skip checking OSD systemd unit
Required arguments:
Optional arguments:
* [-h, --help] show the help message and exit
+* [--no-systemd] skip checking OSD systemd unit
Required arguments:
- **MAX AVAIL:** An estimate of the notional amount of data that can be written
to this pool.
- **OBJECTS:** The notional number of objects stored per pool.
+- **QUOTA OBJECTS:** The number of quota objects.
+- **QUOTA BYTES:** The number of bytes in the quota objects.
+- **DIRTY:** The number of objects in the cache pool that have been written to
+ the cache pool but have not been flushed yet to the base pool. This field is
+ only available when cache tiering is in use.
+- **USED COMPR:** amount of space allocated for compressed data (i.e. this
+ includes comrpessed data plus all the allocation, replication and erasure
+ coding overhead).
+- **UNDER COMPR:** amount of data passed through compression (summed over all
+ replicas) and beneficial enough to be stored in a compressed form.
.. note:: The numbers in the **POOLS** section are notional. They are not
inclusive of the number of replicas, snapshots or clones. As a result,
# at the three URLs referenced below (may involve uploading to download.ceph.com)
boost_version=1.72.0
download_boost $boost_version 59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722 \
- https://dl.bintray.com/boostorg/release/$boost_version/source \
+ https://boostorg.jfrog.io/artifactory/main/release/$boost_version/source \
https://downloads.sourceforge.net/project/boost/boost/$boost_version \
https://download.ceph.com/qa
"decimals": 2,
"format": "percentunit",
"gauge": {
- "maxValue": 100,
+ "maxValue": 1,
"minValue": 0,
"show": true,
"thresholdLabels": false,
"refId": "A"
}
],
- "thresholds": "70,80",
+ "thresholds": "0.7,0.8",
"title": "Capacity used",
"type": "singlestat",
"valueFontSize": "80%",
"steppedLine": false,
"targets": [
{
- "expr": "(node_memory_MemTotal{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_MemTotal_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"})- (\n (node_memory_MemFree{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_MemFree_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}) + \n (node_memory_Cached{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_Cached_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}) + \n (node_memory_Buffers{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_Buffers_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}) +\n (node_memory_Slab{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_Slab_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"})\n )\n \n",
+ "expr": "(node_memory_MemTotal{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_MemTotal_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"})- (\n (node_memory_MemFree{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_MemFree_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"}) + \n (node_memory_Cached{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_Cached_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"}) + \n (node_memory_Buffers{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_Buffers_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"}) +\n (node_memory_Slab{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_Slab_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"})\n )\n \n",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "used",
"refId": "D"
},
{
- "expr": "node_memory_MemFree{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_MemFree_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} ",
+ "expr": "node_memory_MemFree{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_MemFree_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"} ",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"refId": "A"
},
{
- "expr": "(node_memory_Cached{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_Cached_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}) + \n(node_memory_Buffers{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_Buffers_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}) +\n(node_memory_Slab{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_Slab_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}) \n",
+ "expr": "(node_memory_Cached{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_Cached_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"}) + \n(node_memory_Buffers{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_Buffers_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"}) +\n(node_memory_Slab{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_Slab_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"}) \n",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"refId": "C"
},
{
- "expr": "node_memory_MemTotal{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} or node_memory_MemTotal_bytes{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"} ",
+ "expr": "node_memory_MemTotal{instance=~\"$ceph_hosts([\\\\.:].*)?\"} or node_memory_MemTotal_bytes{instance=~\"$ceph_hosts([\\\\.:].*)?\"} ",
"format": "time_series",
"hide": false,
"intervalFactor": 1,
"steppedLine": false,
"targets": [
{
- "expr": "irate(node_network_receive_drop{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}[1m]) or irate(node_network_receive_drop_total{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}[1m])",
+ "expr": "irate(node_network_receive_drop{instance=~\"$ceph_hosts([\\\\.:].*)?\"}[1m]) or irate(node_network_receive_drop_total{instance=~\"$ceph_hosts([\\\\.:].*)?\"}[1m])",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"refId": "A"
},
{
- "expr": "irate(node_network_transmit_drop{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}[1m]) or irate(node_network_transmit_drop_total{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}[1m])",
+ "expr": "irate(node_network_transmit_drop{instance=~\"$ceph_hosts([\\\\.:].*)?\"}[1m]) or irate(node_network_transmit_drop_total{instance=~\"$ceph_hosts([\\\\.:].*)?\"}[1m])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "{{device}}.tx",
"steppedLine": false,
"targets": [
{
- "expr": "irate(node_network_receive_errs{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}[1m]) or irate(node_network_receive_errs_total{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}[1m])",
+ "expr": "irate(node_network_receive_errs{instance=~\"$ceph_hosts([\\\\.:].*)?\"}[1m]) or irate(node_network_receive_errs_total{instance=~\"$ceph_hosts([\\\\.:].*)?\"}[1m])",
"format": "time_series",
"instant": false,
"intervalFactor": 1,
"refId": "A"
},
{
- "expr": "irate(node_network_transmit_errs{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}[1m]) or irate(node_network_transmit_errs_total{instance=~\"[[ceph_hosts]]([\\\\.:].*)?\"}[1m])",
+ "expr": "irate(node_network_transmit_errs{instance=~\"$ceph_hosts([\\\\.:].*)?\"}[1m]) or irate(node_network_transmit_errs_total{instance=~\"$ceph_hosts([\\\\.:].*)?\"}[1m])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "{{device}}.tx",
},
"yaxes": [
{
- "format": "ms",
+ "format": "s",
"label": "Read (-) / Write (+)",
"logBase": 1,
"max": null,
"steppedLine": false,
"targets": [
{
- "expr": "rate(ceph_rgw_get_b{ceph_daemon=~\"[[rgw_servers]]\"}[30s])",
+ "expr": "rate(ceph_rgw_get_b{ceph_daemon=~\"$rgw_servers\"}[30s])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "GETs {{ceph_daemon}}",
"refId": "B"
},
{
- "expr": "rate(ceph_rgw_put_b{ceph_daemon=~\"[[rgw_servers]]\"}[30s])",
+ "expr": "rate(ceph_rgw_put_b{ceph_daemon=~\"$rgw_servers\"}[30s])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "PUTs {{ceph_daemon}}",
"steppedLine": false,
"targets": [
{
- "expr": "rate(ceph_rgw_failed_req{ceph_daemon=~\"[[rgw_servers]]\"}[30s])",
+ "expr": "rate(ceph_rgw_failed_req{ceph_daemon=~\"$rgw_servers\"}[30s])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "Requests Failed {{ceph_daemon}}",
"refId": "B"
},
{
- "expr": "rate(ceph_rgw_get{ceph_daemon=~\"[[rgw_servers]]\"}[30s])",
+ "expr": "rate(ceph_rgw_get{ceph_daemon=~\"$rgw_servers\"}[30s])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "GETs {{ceph_daemon}}",
"refId": "C"
},
{
- "expr": "rate(ceph_rgw_put{ceph_daemon=~\"[[rgw_servers]]\"}[30s])",
+ "expr": "rate(ceph_rgw_put{ceph_daemon=~\"$rgw_servers\"}[30s])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "PUTs {{ceph_daemon}}",
"refId": "D"
},
{
- "expr": "rate(ceph_rgw_req{ceph_daemon=~\"[[rgw_servers]]\"}[30s]) -\n (rate(ceph_rgw_get{ceph_daemon=~\"[[rgw_servers]]\"}[30s]) +\n rate(ceph_rgw_put{ceph_daemon=~\"[[rgw_servers]]\"}[30s]))",
+ "expr": "rate(ceph_rgw_req{ceph_daemon=~\"$rgw_servers\"}[30s]) -\n (rate(ceph_rgw_get{ceph_daemon=~\"$rgw_servers\"}[30s]) +\n rate(ceph_rgw_put{ceph_daemon=~\"$rgw_servers\"}[30s]))",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "Other {{ceph_daemon}}",
"strokeWidth": 1,
"targets": [
{
- "expr": "rate(ceph_rgw_failed_req{ceph_daemon=~\"[[rgw_servers]]\"}[30s])",
+ "expr": "rate(ceph_rgw_failed_req{ceph_daemon=~\"$rgw_servers\"}[30s])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "Failures {{ceph_daemon}}",
"refId": "A"
},
{
- "expr": "rate(ceph_rgw_get{ceph_daemon=~\"[[rgw_servers]]\"}[30s])",
+ "expr": "rate(ceph_rgw_get{ceph_daemon=~\"$rgw_servers\"}[30s])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "GETs {{ceph_daemon}}",
"refId": "B"
},
{
- "expr": "rate(ceph_rgw_put{ceph_daemon=~\"[[rgw_servers]]\"}[30s])",
+ "expr": "rate(ceph_rgw_put{ceph_daemon=~\"$rgw_servers\"}[30s])",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "PUTs {{ceph_daemon}}",
"refId": "C"
},
{
- "expr": "rate(ceph_rgw_req{ceph_daemon=~\"[[rgw_servers]]\"}[30s]) -\n (rate(ceph_rgw_get{ceph_daemon=~\"[[rgw_servers]]\"}[30s]) +\n rate(ceph_rgw_put{ceph_daemon=~\"[[rgw_servers]]\"}[30s]))",
+ "expr": "rate(ceph_rgw_req{ceph_daemon=~\"$rgw_servers\"}[30s]) -\n (rate(ceph_rgw_get{ceph_daemon=~\"$rgw_servers\"}[30s]) +\n rate(ceph_rgw_put{ceph_daemon=~\"$rgw_servers\"}[30s]))",
"format": "time_series",
"intervalFactor": 1,
"legendFormat": "Other (DELETE,LIST) {{ceph_daemon}}",
rate of the past 48 hours.
- alert: MTU Mismatch
- expr: node_network_mtu_bytes{device!="lo"} != on() group_left() (quantile(0.5, node_network_mtu_bytes{device!="lo"}))
+ expr: node_network_mtu_bytes{device!="lo"} * (node_network_up{device!="lo"} > 0) != on() group_left() (quantile(0.5, node_network_mtu_bytes{device!="lo"}))
labels:
severity: warning
type: ceph_default
- series: 'node_network_mtu_bytes{device="eth4",instance="node-exporter",
job="node-exporter"}'
values: '9000 9000 9000 9000 9000'
+ - series: 'node_network_up{device="eth0",instance="node-exporter",
+ job="node-exporter"}'
+ values: '0 0 0 0 0'
+ - series: 'node_network_up{device="eth1",instance="node-exporter",
+ job="node-exporter"}'
+ values: '0 0 0 0 0'
+ - series: 'node_network_up{device="eth2",instance="node-exporter",
+ job="node-exporter"}'
+ values: '1 1 1 1 1'
+ - series: 'node_network_up{device="eth3",instance="node-exporter",
+ job="node-exporter"}'
+ values: '0 0 0 0 0'
+ - series: 'node_network_up{device="eth4",instance="node-exporter",
+ job="node-exporter"}'
+ values: '1 1 1 1 1'
promql_expr_test:
- - expr: node_network_mtu_bytes{device!="lo"} != on() group_left()
+ - expr: node_network_mtu_bytes{device!="lo"} * (node_network_up{device!="lo"} > 0) != on() group_left()
(quantile(0.5, node_network_mtu_bytes{device!="lo"}))
eval_time: 1m
exp_samples:
- - labels: '{__name__="node_network_mtu_bytes", device="eth4",
- instance="node-exporter", job="node-exporter"}'
+ - labels: '{device="eth4", instance="node-exporter", job="node-exporter"}'
value: 9000
alert_rule_test:
- eval_time: 1m
--- /dev/null
+os_type: centos
+os_version: "8.2"
+overrides:
+ selinux:
+ whitelist:
+ - scontext=system_u:system_r:logrotate_t:s0
+
+tasks:
+- pexec:
+ all:
+ - sudo cp /etc/containers/registries.conf /etc/containers/registries.conf.backup
+ - sudo dnf -y module reset container-tools
+ - sudo dnf -y module install container-tools:3.0
+ - sudo cp /etc/containers/registries.conf.backup /etc/containers/registries.conf
+++ /dev/null
-os_type: centos
-os_version: "8.2"
-overrides:
- selinux:
- whitelist:
- - scontext=system_u:system_r:logrotate_t:s0
-
-tasks:
-- exec:
- all:
- - echo -e "[[registry]]\nlocation = 'docker.io'\n\n[[registry.mirror]]\nlocation='docker-mirror.front.sepia.ceph.com:5000'\n" | sudo tee /etc/containers/registries.conf
- - sudo cp /etc/containers/registries.conf /etc/containers/registries.conf.backup
- - sudo dnf -y module disable container-tools
- - sudo dnf -y install 'dnf-command(copr)'
- - sudo dnf -y copr enable rhcontainerbot/container-selinux
- - sudo curl -L -o /etc/yum.repos.d/devel:kubic:libcontainers:stable.repo https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/CentOS_8/devel:kubic:libcontainers:stable.repo
- - sudo dnf remove -y podman
- - sudo dnf -y install podman
- - sudo cp /etc/containers/registries.conf.backup /etc/containers/registries.conf
--- /dev/null
+os_type: centos
+os_version: "8.3"
+overrides:
+ selinux:
+ whitelist:
+ - scontext=system_u:system_r:logrotate_t:s0
+
+tasks:
+- pexec:
+ all:
+ - sudo cp /etc/containers/registries.conf /etc/containers/registries.conf.backup
+ - sudo dnf -y module reset container-tools
+ - sudo dnf -y module install container-tools:3.0
+ - sudo cp /etc/containers/registries.conf.backup /etc/containers/registries.conf
local dir=$1
shift
- export CEPH_MON="127.0.0.1:7146" # git grep '\<7146\>' : there must be only one
- export CEPH_ARGS
- CEPH_ARGS+="--fsid=$(uuidgen) --auth-supported=none "
- CEPH_ARGS+="--mon-host=$CEPH_MON "
- CEPH_ARGS+="--bluestore_block_size=2147483648 "
- CEPH_ARGS+="--bluestore_block_db_create=true "
- CEPH_ARGS+="--bluestore_block_db_size=1073741824 "
- CEPH_ARGS+="--bluestore_block_wal_size=536870912 "
- CEPH_ARGS+="--bluestore_bluefs_min=536870912 "
- CEPH_ARGS+="--bluestore_bluefs_min_free=536870912 "
- CEPH_ARGS+="--bluestore_block_wal_create=true "
- CEPH_ARGS+="--bluestore_fsck_on_mount=true "
local funcs=${@:-$(set | sed -n -e 's/^\(TEST_[0-9a-z_]*\) .*/\1/p')}
for func in $funcs ; do
setup $dir || return 1
if [ $flimit -lt 1536 ]; then
echo "Low open file limit ($flimit), test may fail. Increase to 1536 or higher and retry if that happens."
fi
+ export CEPH_MON="127.0.0.1:7146" # git grep '\<7146\>' : there must be only one
+ export CEPH_ARGS
+ CEPH_ARGS+="--fsid=$(uuidgen) --auth-supported=none "
+ CEPH_ARGS+="--mon-host=$CEPH_MON "
+ CEPH_ARGS+="--bluestore_block_size=2147483648 "
+ CEPH_ARGS+="--bluestore_block_db_create=true "
+ CEPH_ARGS+="--bluestore_block_db_size=1073741824 "
+ CEPH_ARGS+="--bluestore_block_wal_size=536870912 "
+ CEPH_ARGS+="--bluestore_bluefs_min=536870912 "
+ CEPH_ARGS+="--bluestore_bluefs_min_free=536870912 "
+ CEPH_ARGS+="--bluestore_block_wal_create=true "
+ CEPH_ARGS+="--bluestore_fsck_on_mount=true "
run_mon $dir a || return 1
run_mgr $dir x || return 1
wait_for_clean || return 1
}
+function TEST_bluestore2() {
+ local dir=$1
+
+ local flimit=$(ulimit -n)
+ if [ $flimit -lt 1536 ]; then
+ echo "Low open file limit ($flimit), test may fail. Increase to 1536 or higher and retry if that happens."
+ fi
+ export CEPH_MON="127.0.0.1:7146" # git grep '\<7146\>' : there must be only one
+ export CEPH_ARGS
+ CEPH_ARGS+="--fsid=$(uuidgen) --auth-supported=none "
+ CEPH_ARGS+="--mon-host=$CEPH_MON "
+ CEPH_ARGS+="--bluestore_block_size=4294967296 "
+ CEPH_ARGS+="--bluestore_block_db_create=true "
+ CEPH_ARGS+="--bluestore_block_db_size=1073741824 "
+ CEPH_ARGS+="--bluestore_bluefs_min=536870912 "
+ CEPH_ARGS+="--bluestore_bluefs_min_free=536870912 "
+ CEPH_ARGS+="--bluestore_block_wal_create=false "
+ CEPH_ARGS+="--bluestore_fsck_on_mount=true "
+ CEPH_ARGS+="--osd_pool_default_size=1 "
+ CEPH_ARGS+="--osd_pool_default_min_size=1 "
+ CEPH_ARGS+="--bluestore_debug_enforce_settings=ssd "
+
+ run_mon $dir a || return 1
+ run_mgr $dir x || return 1
+ run_osd $dir 0 || return 1
+ osd_pid0=$(cat $dir/osd.0.pid)
+
+ sleep 5
+ create_pool foo 16
+
+ # write some objects
+ timeout 60 rados bench -p foo 10 write --write-omap --no-cleanup #|| return 1
+
+ #give RocksDB some time to cooldown and put files to slow level(s)
+ sleep 10
+
+ spilled_over=$( ceph tell osd.0 perf dump bluefs | jq ".bluefs.slow_used_bytes" )
+ test $spilled_over -gt 0 || return 1
+
+ while kill $osd_pid0; do sleep 1 ; done
+ ceph osd down 0
+
+ ceph-bluestore-tool --path $dir/0 \
+ --devs-source $dir/0/block.db \
+ --dev-target $dir/0/block \
+ --command bluefs-bdev-migrate || return 1
+
+ ceph-bluestore-tool --path $dir/0 \
+ --command bluefs-bdev-sizes || return 1
+
+ ceph-bluestore-tool --path $dir/0 \
+ --command fsck || return 1
+
+ activate_osd $dir 0 || return 1
+ osd_pid0=$(cat $dir/osd.0.pid)
+
+ wait_for_clean || return 1
+}
+
main osd-bluefs-volume-ops "$@"
# Local Variables:
--- /dev/null
+.qa/distros/podman/centos_8.2_container_tools_3.0.yaml
\ No newline at end of file
+++ /dev/null
-.qa/distros/podman/centos_8.2_kubic_stable.yaml
\ No newline at end of file
+++ /dev/null
-.qa/distros/podman/rhel_8.3_kubic_stable.yaml
\ No newline at end of file
+++ /dev/null
-.qa/distros/podman/centos_8.2_kubic_stable.yaml
\ No newline at end of file
+++ /dev/null
-meta:
-- desc: run the rbd_mirror_bootstrap.sh workunit to test the rbd-mirror daemon
-tasks:
-- workunit:
- clients:
- cluster1.client.mirror: [rbd/rbd_mirror_bootstrap.sh]
- env:
- # override workunit setting of CEPH_ARGS='--cluster'
- CEPH_ARGS: ''
- RBD_MIRROR_INSTANCES: '1'
- RBD_MIRROR_USE_EXISTING_CLUSTER: '1'
--- /dev/null
+meta:
+- desc: run the rbd_mirror_bootstrap.sh workunit to test the rbd-mirror daemon
+tasks:
+- workunit:
+ clients:
+ cluster1.client.mirror: [rbd/rbd_mirror_bootstrap.sh]
+ env:
+ # override workunit setting of CEPH_ARGS='--cluster'
+ CEPH_ARGS: ''
+ RBD_MIRROR_INSTANCES: '1'
+ RBD_MIRROR_USE_EXISTING_CLUSTER: '1'
+ MIRROR_POOL_MODE: 'pool'
+ MIRROR_IMAGE_MODE: 'journal'
--- /dev/null
+meta:
+- desc: run the rbd_mirror_bootstrap.sh workunit to test the rbd-mirror daemon
+tasks:
+- workunit:
+ clients:
+ cluster1.client.mirror: [rbd/rbd_mirror_bootstrap.sh]
+ env:
+ # override workunit setting of CEPH_ARGS='--cluster'
+ CEPH_ARGS: ''
+ RBD_MIRROR_INSTANCES: '1'
+ RBD_MIRROR_USE_EXISTING_CLUSTER: '1'
+ MIRROR_POOL_MODE: 'image'
+ MIRROR_IMAGE_MODE: 'snapshot'
Run ceph on two nodes, using one of them as a client,
with a separate client-only node.
Use xfs beneath the osds.
- install ceph/octopus v15.2.10 and the v15.2.x point versions
+ install ceph/octopus v15.2.14 and the v15.2.x point versions
run workload and upgrade-sequence in parallel
(every point release should be tested)
run workload and upgrade-sequence in parallel
count: 3
size: 30 # GB
tasks:
-- print: "**** done octopus v15.2.10 about to install"
+- print: "**** done octopus v15.2.13 about to install"
- install:
- tag: v15.2.10
+ tag: v15.2.13
# line below can be removed its from jewel test
#exclude_packages: ['ceph-mgr','libcephfs2','libcephfs-devel','libcephfs-dev', 'librgw2']
-- print: "**** done v15.2.10 install"
+- print: "**** done v15.2.13 install"
- ceph:
fs: xfs
add_osds_to_crush: true
- print: "**** done ceph xfs"
- sequential:
- workload
-- print: "**** done workload v15.2.10"
+- print: "**** done workload v15.2.13"
-####### upgrade to v15.2.11
+####### upgrade to v15.2.14
- install.upgrade:
#exclude_packages: ['ceph-mgr','libcephfs2','libcephfs-devel','libcephfs-dev']
mon.a:
- tag: v15.2.11
+ tag: v15.2.14
mon.b:
- tag: v15.2.11
+ tag: v15.2.14
- parallel:
- workload_octopus
- upgrade-sequence_octopus
-- print: "**** done parallel octopus v15.2.11"
+- print: "**** done parallel octopus v15.2.14"
#### upgrade to latest octopus
- install.upgrade:
meta:
-- desc: install ceph/octopus v15.2.11
+- desc: install ceph/octopus v15.2.14
tasks:
- install:
- tag: v15.2.11
+ tag: v15.2.14
exclude_packages: ['librados3']
extra_packages: ['librados2']
-- print: "**** done install octopus v15.2.11"
+- print: "**** done install octopus v15.2.14"
- ceph:
- exec:
osd.0:
"""
from functools import wraps
import contextlib
+import errno
import random
import signal
import time
Loop until quorum size is reached.
"""
self.log('waiting for quorum size %d' % size)
- start = time.time()
- while not len(self.get_mon_quorum()) == size:
- if timeout is not None:
- assert time.time() - start < timeout, \
- ('failed to reach quorum size %d '
- 'before timeout expired' % size)
- time.sleep(3)
+ sleep = 3
+ with safe_while(sleep=sleep,
+ tries=timeout // sleep,
+ action=f'wait for quorum size {size}') as proceed:
+ while proceed():
+ try:
+ if len(self.get_mon_quorum()) == size:
+ break
+ except CommandFailedError as e:
+ # could fail instea4d of blocked if the rotating key of the
+ # connected monitor is not updated yet after they form the
+ # quorum
+ if e.exitstatus == errno.EACCES:
+ pass
+ else:
+ raise
self.log("quorum is size %d" % size)
def get_mon_health(self, debug=False):
'tenant': JLeaf(str),
}, allow_unknown=True))
+ # List all buckets names without stats.
+ data = self._get('/api/rgw/bucket?stats=false')
+ self.assertStatus(200)
+ self.assertEqual(data, ['teuth-test-bucket'])
+
# Get the bucket.
data = self._get('/api/rgw/bucket/teuth-test-bucket')
self.assertStatus(200)
user_1 = self._get('/api/user/user1')
self.assertStatus(200)
+ # Let's wait 1 s to ensure pwd expiration date is not the same
+ time.sleep(1)
+
self.login('user1', 'mypassword10#')
self._post('/api/user/user1/change_password', {
'old_password': 'mypassword10#',
active_id = self.mgr_cluster.get_active_id()
self.mgr_cluster.mgr_restart(active_id)
- # ensure that at least one of the checks is present after the restart.
- # we don't for them all to be present because "earlier" checks may not
- # have sat in memory long enough to be flushed.
- all_missing = True
- report = self._insights()
- for check in check_names:
- if check in report["health"]["history"]["checks"]:
- all_missing = False
- break
- self.assertFalse(all_missing)
-
# pruning really removes history
self.mgr_cluster.mon_manager.raw_cluster_cmd_result(
"insights", "prune-health", "0")
cmdlog = LOG.getChild('run_cmd')
cmdlog.debug('{fc}'.format(fc=' '.join(full_cmd)))
- proc = subprocess.Popen(full_cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
-
- stdout = []
- stderr = []
- while True:
- try:
- out, err = proc.communicate()
- if out is not None:
- stdout += out.decode().split('\n')
- cmdlog.debug('stdout: {s}'.format(s=out))
- if err is not None:
- stdout += err.decode().split('\n')
- cmdlog.debug('stderr: {s}'.format(s=err))
- except ValueError:
- ret = proc.wait()
- break
-
- if ret != expects:
- cmdlog.error('cmd > {cmd}'.format(cmd=full_cmd))
- cmdlog.error("expected return '{expected}' got '{got}'".format(
- expected=expects, got=ret))
+ proc = subprocess.run(full_cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True)
+ if proc.returncode != expects:
+ cmdlog.error(f'cmd > {proc.args}')
+ cmdlog.error(f'expected return "{expects}" got "{proc.returncode}"')
cmdlog.error('stdout')
- for i in stdout:
- cmdlog.error('{x}'.format(x=i))
+ cmdlog.error(proc.stdout)
cmdlog.error('stderr')
- for i in stderr:
- cmdlog.error('{x}'.format(x=i))
+ cmdlog.error(proc.stderr)
-# end run_cmd
-
def gen_data(size, rnd):
chars = string.ascii_letters + string.digits
return ''.join(rnd.choice(chars) for _ in range(size))
[ "$(rbd --cluster ${CLUSTER1} --pool ${POOL} mirror pool info --format xml |
${XMLSTARLET} sel -t -v '//mirror/peers/peer[1]/uuid')" = "" ]
-create_image ${CLUSTER1} ${POOL} image1
+create_image_and_enable_mirror ${CLUSTER1} ${POOL} image1
wait_for_image_replay_started ${CLUSTER2} ${POOL} image1
write_image ${CLUSTER1} ${POOL} image1 100
CHECK_MAKEOPTS=${CHECK_MAKEOPTS:-$DEFAULT_MAKEOPTS}
if in_jenkins; then
- if ! ctest $CHECK_MAKEOPTS --no-compress-output --output-on-failure -T Test; then
+ if ! ctest $CHECK_MAKEOPTS --no-compress-output --output-on-failure --test-output-size-failed 1024000 -T Test; then
# do not return failure, as the jenkins publisher will take care of this
rm -fr ${TMPDIR:-/tmp}/ceph-asok.*
fi
-cd3bb7e87a2f62c1b862ff3fd8b1eec13391a5be
-15.2.14
+2dfb18841cfecc2f7eb7eb2afd65986ca4d95985
+15.2.15
if (ret < 0)
lderr(cct) << "failed to load " << filename
<< ": " << cpp_strerror(ret) << dendl;
- } else {
+ } else if (conf->key.empty() && conf->keyfile.empty()) {
lderr(cct) << "unable to find a keyring on " << conf->keyring
<< ": " << cpp_strerror(ret) << dendl;
}
add_custom_command(
OUTPUT ${CEPH_VOLUME_VIRTUALENV}/bin/python
- COMMAND ${CMAKE_SOURCE_DIR}/src/tools/setup-virtualenv.sh --python=${Python_EXECUTABLE} ${CEPH_VOLUME_VIRTUALENV}
+ COMMAND ${CMAKE_SOURCE_DIR}/src/tools/setup-virtualenv.sh --python=${Python3_EXECUTABLE} ${CEPH_VOLUME_VIRTUALENV}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/ceph-volume
COMMENT "ceph-volume venv is being created")
:returns: list of class PVolume object representing pvs on the system
"""
filters = make_filters_lvmcmd_ready(filters, tags)
- args = ['pvs', '--no-heading', '--readonly', '--separator=";"', '-S',
+ args = ['pvs', '--noheadings', '--readonly', '--separator=";"', '-S',
filters, '-o', fields]
stdout, stderr, returncode = process.call(args, verbose_on_failure=False)
terminal.warning('Verify OSDs are present with "ceph-volume lvm list"')
return
for osd_fsid, osd_id in osds.items():
- if systemctl.osd_is_active(osd_id):
+ if not args.no_systemd and systemctl.osd_is_active(osd_id):
terminal.warning(
'OSD ID %s FSID %s process is active. Skipping activation' % (osd_id, osd_fsid)
)
tags = {'ceph.osd_id': osd_id, 'ceph.osd_fsid': osd_fsid}
elif not osd_id and osd_fsid:
tags = {'ceph.osd_fsid': osd_fsid}
+ elif osd_id and not osd_fsid:
+ raise RuntimeError('could not activate osd.{}, please provide the '
+ 'osd_fsid too'.format(osd_id))
+ else:
+ raise RuntimeError('Please provide both osd_id and osd_fsid')
lvs = api.get_lvs(tags=tags)
if not lvs:
raise RuntimeError('could not find osd.%s with osd_fsid %s' %
requested_slots = fast_slots_per_device
requested_size = getattr(args, '{}_size'.format(type_), 0)
- if requested_size == 0:
+ if not requested_size or requested_size == 0:
# no size argument was specified, check ceph.conf
get_size_fct = getattr(prepare, 'get_{}_size'.format(type_))
requested_size = get_size_fct(lv_format=False)
if requested_size:
if requested_size <= abs_size:
abs_size = requested_size
+ relative_size = int(abs_size) / dev_size
else:
mlogger.error(
'{} was requested for {}, but only {} can be fulfilled'.format(
# fill up uneven distributions across fast devices: 5 osds and 2 fast
# devices? create 3 slots on each device rather then deploying
# heterogeneous osds
- if (requested_osds - len(lvm_devs)) % len(phys_devs):
- fast_slots_per_device = int((requested_osds - len(lvm_devs)) / len(phys_devs)) + 1
+ slot_divider = max(1, len(phys_devs))
+ if (requested_osds - len(lvm_devs)) % slot_divider:
+ fast_slots_per_device = int((requested_osds - len(lvm_devs)) / slot_divider) + 1
else:
- fast_slots_per_device = int((requested_osds - len(lvm_devs)) / len(phys_devs))
+ fast_slots_per_device = int((requested_osds - len(lvm_devs)) / slot_divider)
ret.extend(get_physical_fast_allocs(phys_devs,
ceph-volume lvm deactivate {ID} {FSID}
- To deactivate all volumes use the --all flag.
- ceph-volume lvm deactivate --all
""")
parser = argparse.ArgumentParser(
prog='ceph-volume lvm deactivate',
@decorators.needs_root
def migrate_osd(self):
- if self.args.osd_id:
+ if self.args.osd_id and not self.args.no_systemd:
osd_is_running = systemctl.osd_is_active(self.args.osd_id)
if osd_is_running:
mlogger.error('OSD is running, stop it with: '
devices,
target_lv)
- def parse_argv(self):
+ def make_parser(self, prog, sub_command_help):
+ parser = argparse.ArgumentParser(
+ prog=prog,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description=sub_command_help,
+ )
+
+ parser.add_argument(
+ '--osd-id',
+ required=True,
+ help='Specify an OSD ID to detect associated devices for zapping',
+ )
+
+ parser.add_argument(
+ '--osd-fsid',
+ required=True,
+ help='Specify an OSD FSID to detect associated devices for zapping',
+ )
+ parser.add_argument(
+ '--target',
+ required=True,
+ help='Specify target Logical Volume (LV) to migrate data to',
+ )
+ parser.add_argument(
+ '--from',
+ nargs='*',
+ dest='from_',
+ required=True,
+ choices=['data', 'db', 'wal'],
+ help='Copy BlueFS data from DB device',
+ )
+ parser.add_argument(
+ '--no-systemd',
+ dest='no_systemd',
+ action='store_true',
+ help='Skip checking OSD systemd unit',
+ )
+ return parser
+
+ def main(self):
sub_command_help = dedent("""
Moves BlueFS data from source volume(s) to the target one, source
volumes (except the main (i.e. data or block) one) are removed on
ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from db wal --target vgname/data
""")
- parser = argparse.ArgumentParser(
- prog='ceph-volume lvm migrate',
- formatter_class=argparse.RawDescriptionHelpFormatter,
- description=sub_command_help,
- )
- parser.add_argument(
- '--osd-id',
- required=True,
- help='Specify an OSD ID to detect associated devices for zapping',
- )
-
- parser.add_argument(
- '--osd-fsid',
- required=True,
- help='Specify an OSD FSID to detect associated devices for zapping',
- )
- parser.add_argument(
- '--target',
- required=True,
- help='Specify target Logical Volume (LV) to migrate data to',
- )
- parser.add_argument(
- '--from',
- nargs='*',
- dest='from_',
- required=True,
- choices=['data', 'db', 'wal'],
- help='Copy BlueFS data from DB device',
- )
+ parser = self.make_parser('ceph-volume lvm migrate', sub_command_help)
if len(self.argv) == 0:
print(sub_command_help)
return
+
self.args = parser.parse_args(self.argv)
- def main(self):
- self.parse_argv()
self.migrate_osd()
class NewVolume(object):
required=True,
help='Specify target Logical Volume (LV) to attach',
)
+ parser.add_argument(
+ '--no-systemd',
+ dest='no_systemd',
+ action='store_true',
+ help='Skip checking OSD systemd unit',
+ )
return parser
@decorators.needs_root
@decorators.needs_root
def new_volume(self):
- if self.args.osd_id:
+ if self.args.osd_id and not self.args.no_systemd:
osd_is_running = systemctl.osd_is_active(self.args.osd_id)
if osd_is_running:
mlogger.error('OSD ID is running, stop it with:'
import logging
from textwrap import dedent
from ceph_volume import decorators, process
+from ceph_volume.util import disk
logger = logging.getLogger(__name__)
+
def direct_report(devices):
"""
Other non-cli consumers of listing information will want to consume the
_list = List([])
return _list.generate(devices)
+def _get_bluestore_info(dev):
+ out, err, rc = process.call([
+ 'ceph-bluestore-tool', 'show-label',
+ '--dev', dev], verbose_on_failure=False)
+ if rc:
+ # ceph-bluestore-tool returns an error (below) if device is not bluestore OSD
+ # > unable to read label for <device>: (2) No such file or directory
+ # but it's possible the error could be for a different reason (like if the disk fails)
+ logger.debug('assuming device {} is not BlueStore; ceph-bluestore-tool failed to get info from device: {}\n{}'.format(dev, out, err))
+ return None
+ oj = json.loads(''.join(out))
+ if dev not in oj:
+ # should be impossible, so warn
+ logger.warning('skipping device {} because it is not reported in ceph-bluestore-tool output: {}'.format(dev, out))
+ return None
+ try:
+ if oj[dev]['description'] != 'main':
+ # ignore non-main devices, for now
+ logger.info('ignoring non-main device {}'.format(dev))
+ return None
+ whoami = oj[dev]['whoami']
+ return {
+ 'type': 'bluestore',
+ 'osd_id': int(whoami),
+ 'osd_uuid': oj[dev]['osd_uuid'],
+ 'ceph_fsid': oj[dev]['ceph_fsid'],
+ 'device': dev
+ }
+ except KeyError as e:
+ # this will appear for devices that have a bluestore header but aren't valid OSDs
+ # for example, due to incomplete rollback of OSDs: https://tracker.ceph.com/issues/51869
+ logger.error('device {} does not have all BlueStore data needed to be a valid OSD: {}\n{}'.format(dev, out, e))
+ return None
+
class List(object):
self.argv = argv
def generate(self, devs=None):
- if not devs:
- logger.debug('Listing block devices via lsblk...')
+ logger.debug('Listing block devices via lsblk...')
+
+ if devs is None or devs == []:
devs = []
- # adding '--inverse' allows us to get the mapper devices list in that command output.
- # not listing root devices containing partitions shouldn't have side effect since we are
- # in `ceph-volume raw` context.
- #
- # example:
- # running `lsblk --paths --nodeps --output=NAME --noheadings` doesn't allow to get the mapper list
- # because the output is like following :
- #
- # $ lsblk --paths --nodeps --output=NAME --noheadings
- # /dev/sda
- # /dev/sdb
- # /dev/sdc
- # /dev/sdd
- #
- # the dmcrypt mappers are hidden because of the `--nodeps` given they are displayed as a dependency.
- #
- # $ lsblk --paths --output=NAME --noheadings
- # /dev/sda
- # |-/dev/mapper/ceph-3b52c90d-6548-407d-bde1-efd31809702f-sda-block-dmcrypt
- # `-/dev/mapper/ceph-3b52c90d-6548-407d-bde1-efd31809702f-sda-db-dmcrypt
- # /dev/sdb
- # /dev/sdc
- # /dev/sdd
- #
- # adding `--inverse` is a trick to get around this issue, the counterpart is that we can't list root devices if they contain
- # at least one partition but this shouldn't be an issue in `ceph-volume raw` context given we only deal with raw devices.
+ # If no devs are given initially, we want to list ALL devices including children and
+ # parents. Parent disks with child partitions may be the appropriate device to return if
+ # the parent disk has a bluestore header, but children may be the most appropriate
+ # devices to return if the parent disk does not have a bluestore header.
out, err, ret = process.call([
- 'lsblk', '--paths', '--nodeps', '--output=NAME', '--noheadings', '--inverse'
+ 'lsblk', '--paths', '--output=NAME', '--noheadings', '--list'
])
assert not ret
devs = out
+
result = {}
+ logger.debug('inspecting devices: {}'.format(devs))
for dev in devs:
- logger.debug('Examining %s' % dev)
- # bluestore?
- out, err, ret = process.call([
- 'ceph-bluestore-tool', 'show-label',
- '--dev', dev], verbose_on_failure=False)
- if ret:
- logger.debug('No label on %s' % dev)
- continue
- oj = json.loads(''.join(out))
- if dev not in oj:
+ info = disk.lsblk(dev, abspath=True)
+ # Linux kernels built with CONFIG_ATARI_PARTITION enabled can falsely interpret
+ # bluestore's on-disk format as an Atari partition table. These false Atari partitions
+ # can be interpreted as real OSDs if a bluestore OSD was previously created on the false
+ # partition. See https://tracker.ceph.com/issues/52060 for more info. If a device has a
+ # parent, it is a child. If the parent is a valid bluestore OSD, the child will only
+ # exist if it is a phantom Atari partition, and the child should be ignored. If the
+ # parent isn't bluestore, then the child could be a valid bluestore OSD. If we fail to
+ # determine whether a parent is bluestore, we should err on the side of not reporting
+ # the child so as not to give a false negative.
+ if 'PKNAME' in info and info['PKNAME'] != "":
+ parent = info['PKNAME']
+ try:
+ if disk.has_bluestore_label(parent):
+ logger.warning(('ignoring child device {} whose parent {} is a BlueStore OSD.'.format(dev, parent),
+ 'device is likely a phantom Atari partition. device info: {}'.format(info)))
+ continue
+ except OSError as e:
+ logger.error(('ignoring child device {} to avoid reporting invalid BlueStore data from phantom Atari partitions.'.format(dev),
+ 'failed to determine if parent device {} is BlueStore. err: {}'.format(parent, e)))
+ continue
+
+ bs_info = _get_bluestore_info(dev)
+ if bs_info is None:
+ # None is also returned in the rare event that there is an issue reading info from
+ # a BlueStore disk, so be sure to log our assumption that it isn't bluestore
+ logger.info('device {} does not have BlueStore information'.format(dev))
continue
- if oj[dev]['description'] != 'main':
- # ignore non-main devices, for now
- continue
- whoami = oj[dev]['whoami']
- result[whoami] = {
- 'type': 'bluestore',
- 'osd_id': int(whoami),
- }
- for f in ['osd_uuid', 'ceph_fsid']:
- result[whoami][f] = oj[dev][f]
- result[whoami]['device'] = dev
+ result[bs_info['osd_uuid']] = bs_info
+
return result
@decorators.needs_root
dev.used_by_ceph = False
dev.vg_size = [size]
dev.vg_free = dev.vg_size
+ dev.available_lvm = True
+ dev.is_device = False
dev.lvs = [lvm.Volume(vg_name=dev.vg_name, lv_name=dev.lv_name, lv_size=size, lv_tags='')]
return dev
return mock_lv
with pytest.raises(RuntimeError):
activate.Activate([]).activate(args)
+ def test_osd_id_no_osd_fsid(self, is_root):
+ args = Args(osd_id=42, osd_fsid=None)
+ with pytest.raises(RuntimeError) as result:
+ activate.Activate([]).activate(args)
+ assert result.value.args[0] == 'could not activate osd.42, please provide the osd_fsid too'
+
+ def test_no_osd_id_no_osd_fsid(self, is_root):
+ args = Args(osd_id=None, osd_fsid=None)
+ with pytest.raises(RuntimeError) as result:
+ activate.Activate([]).activate(args)
+ assert result.value.args[0] == 'Please provide both osd_id and osd_fsid'
+
def test_filestore_no_systemd(self, is_root, monkeypatch, capture):
monkeypatch.setattr('ceph_volume.configuration.load', lambda: None)
fake_enable = Capture()
assert 'a8789a96ce8b process is active. Skipping activation' in err
assert 'b8218eaa1634 process is active. Skipping activation' in err
- def test_detects_osds_to_activate(self, is_root, capture, monkeypatch):
+ def test_detects_osds_to_activate_systemd(self, is_root, capture, monkeypatch):
monkeypatch.setattr('ceph_volume.devices.lvm.activate.direct_report', lambda: direct_report)
monkeypatch.setattr('ceph_volume.devices.lvm.activate.systemctl.osd_is_active', lambda x: False)
args = ['--all']
assert calls[1]['kwargs']['osd_id'] == '1'
assert calls[1]['kwargs']['osd_fsid'] == 'd0f3e4ad-e52a-4520-afc0-a8789a96ce8b'
+ def test_detects_osds_to_activate_no_systemd(self, is_root, capture, monkeypatch):
+ monkeypatch.setattr('ceph_volume.devices.lvm.activate.direct_report', lambda: direct_report)
+ args = ['--all', '--no-systemd']
+ activation = activate.Activate(args)
+ activation.activate = capture
+ activation.main()
+ calls = sorted(capture.calls, key=lambda x: x['kwargs']['osd_id'])
+ assert calls[0]['kwargs']['osd_id'] == '0'
+ assert calls[0]['kwargs']['osd_fsid'] == '957d22b7-24ce-466a-9883-b8218eaa1634'
+ assert calls[1]['kwargs']['osd_id'] == '1'
+ assert calls[1]['kwargs']['osd_fsid'] == 'd0f3e4ad-e52a-4520-afc0-a8789a96ce8b'
+
#
# Activate All fixture
#
'block_db', 2, 2, args)
assert len(fast) == 2
+ def test_batch_fast_allocations_one_block_db_length(self, factory, conf_ceph_stub,
+ mock_lv_device_generator):
+ conf_ceph_stub('[global]\nfsid=asdf-lkjh')
+
+ b = batch.Batch([])
+ db_lv_devices = [mock_lv_device_generator()]
+ fast = b.fast_allocations(db_lv_devices, 1, 0, 'block_db')
+ assert len(fast) == 1
+
@pytest.mark.parametrize('occupied_prior', range(7))
@pytest.mark.parametrize('slots,num_devs',
[l for sub in [list(zip([x]*x, range(1, x + 1))) for x in range(1,7)] for l in sub])
'--dev-target', '/dev/VolGroup/target_volume',
'--command', 'bluefs-bdev-new-db']
+ def test_newdb_active_systemd(self, is_root, monkeypatch, capsys):
+ source_tags = \
+ 'ceph.osd_id=0,ceph.type=data,ceph.osd_fsid=1234,'\
+ 'ceph.wal_uuid=wal_uuid,ceph.db_device=/dbdevice'
+ source_wal_tags = \
+ 'ceph.wal_uuid=uuid,ceph.wal_device=device,' \
+ 'ceph.osd_id=0,ceph.type=wal'
+
+ data_vol = api.Volume(lv_name='volume1', lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv1',
+ lv_tags=source_tags)
+ wal_vol = api.Volume(lv_name='volume3',
+ lv_uuid='waluuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv3',
+ lv_tags=source_wal_tags)
+
+ self.mock_single_volumes = {'/dev/VolGroup/lv1': data_vol,
+ '/dev/VolGroup/lv3': wal_vol}
+
+ monkeypatch.setattr(migrate.api, 'get_first_lv',
+ self.mock_get_first_lv)
+
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ self.mock_volume = api.Volume(lv_name='target_volume1', lv_uuid='y',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/target_volume',
+ lv_tags='')
+ monkeypatch.setattr(api, 'get_lv_by_fullname',
+ self.mock_get_lv_by_fullname)
+
+ monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active",
+ lambda id: True)
+
+ #find_associated_devices will call get_lvs() 4 times
+ # and it this needs results to be arranged that way
+ self.mock_volumes = []
+ self.mock_volumes.append([data_vol, wal_vol])
+ self.mock_volumes.append([data_vol])
+ self.mock_volumes.append([])
+ self.mock_volumes.append([wal_vol])
+
+ monkeypatch.setattr(migrate.api, 'get_lvs', self.mock_get_lvs)
+
+ monkeypatch.setattr(migrate, 'get_cluster_name',
+ lambda osd_id, osd_fsid: 'ceph_cluster')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
+
+ m = migrate.NewDB(argv=[
+ '--osd-id', '1',
+ '--osd-fsid', '55BD4219-16A7-4037-BC20-0F158EFCC83D',
+ '--target', 'vgname/new_db'])
+
+ with pytest.raises(SystemExit) as error:
+ m.main()
+
+ stdout, stderr = capsys.readouterr()
+
+ assert 'Unable to attach new volume for OSD: 1' == str(error.value)
+ assert '--> OSD ID is running, stop it with: systemctl stop ceph-osd@1' == stderr.rstrip()
+ assert not stdout
+
+ def test_newdb_no_systemd(self, is_root, monkeypatch):
+ source_tags = \
+ 'ceph.osd_id=0,ceph.type=data,ceph.osd_fsid=1234,'\
+ 'ceph.wal_uuid=wal_uuid,ceph.db_device=/dbdevice'
+ source_wal_tags = \
+ 'ceph.wal_uuid=uuid,ceph.wal_device=device,' \
+ 'ceph.osd_id=0,ceph.type=wal'
+
+ data_vol = api.Volume(lv_name='volume1', lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv1',
+ lv_tags=source_tags)
+ wal_vol = api.Volume(lv_name='volume3',
+ lv_uuid='waluuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv3',
+ lv_tags=source_wal_tags)
+
+ self.mock_single_volumes = {'/dev/VolGroup/lv1': data_vol,
+ '/dev/VolGroup/lv3': wal_vol}
+
+ monkeypatch.setattr(migrate.api, 'get_first_lv',
+ self.mock_get_first_lv)
+
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ self.mock_volume = api.Volume(lv_name='target_volume1', lv_uuid='y',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/target_volume',
+ lv_tags='')
+ monkeypatch.setattr(api, 'get_lv_by_fullname',
+ self.mock_get_lv_by_fullname)
+
+ #find_associated_devices will call get_lvs() 4 times
+ # and it this needs results to be arranged that way
+ self.mock_volumes = []
+ self.mock_volumes.append([data_vol, wal_vol])
+ self.mock_volumes.append([data_vol])
+ self.mock_volumes.append([])
+ self.mock_volumes.append([wal_vol])
+
+ monkeypatch.setattr(migrate.api, 'get_lvs', self.mock_get_lvs)
+
+ monkeypatch.setattr(migrate, 'get_cluster_name',
+ lambda osd_id, osd_fsid: 'ceph_cluster')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
+
+ migrate.NewDB(argv=[
+ '--osd-id', '1',
+ '--osd-fsid', '55BD4219-16A7-4037-BC20-0F158EFCC83D',
+ '--target', 'vgname/new_db',
+ '--no-systemd']).main()
+
+ n = len(self.mock_process_input)
+ assert n >= 5
+
+ assert self.mock_process_input[n - 5] == [
+ 'lvchange',
+ '--deltag', 'ceph.db_device=/dbdevice',
+ '/dev/VolGroup/lv1']
+ assert self.mock_process_input[n - 4] == [
+ 'lvchange',
+ '--addtag', 'ceph.db_uuid=y',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/target_volume',
+ '/dev/VolGroup/lv1']
+
+ assert self.mock_process_input[n - 3].sort() == [
+ 'lvchange',
+ '--addtag', 'ceph.wal_uuid=uuid',
+ '--addtag', 'ceph.osd_id=0',
+ '--addtag', 'ceph.type=db',
+ '--addtag', 'ceph.osd_fsid=1234',
+ '--addtag', 'ceph.db_uuid=y',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/target_volume',
+ '/dev/VolGroup/target_volume'].sort()
+
+ assert self.mock_process_input[n - 2] == [
+ 'lvchange',
+ '--addtag', 'ceph.db_uuid=y',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/target_volume',
+ '/dev/VolGroup/lv3']
+
+ assert self.mock_process_input[n - 1] == [
+ 'ceph-bluestore-tool',
+ '--path', '/var/lib/ceph/osd/ceph_cluster-1',
+ '--dev-target', '/dev/VolGroup/target_volume',
+ '--command', 'bluefs-bdev-new-db']
+
@patch('os.getuid')
def test_newwal(self, m_getuid, monkeypatch, capsys):
m_getuid.return_value = 0
'--dev-target', '/dev/VolGroup/target_volume',
'--command', 'bluefs-bdev-new-wal']
+ def test_newwal_active_systemd(self, is_root, monkeypatch, capsys):
+ source_tags = \
+ 'ceph.osd_id=0,ceph.type=data,ceph.osd_fsid=1234'
+
+ data_vol = api.Volume(lv_name='volume1', lv_uuid='datauuid', vg_name='vg',
+ lv_path='/dev/VolGroup/lv1', lv_tags=source_tags)
+
+ self.mock_single_volumes = {'/dev/VolGroup/lv1': data_vol}
+
+ monkeypatch.setattr(migrate.api, 'get_first_lv', self.mock_get_first_lv)
+
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ self.mock_volume = api.Volume(lv_name='target_volume1', lv_uuid='y', vg_name='vg',
+ lv_path='/dev/VolGroup/target_volume',
+ lv_tags='')
+ monkeypatch.setattr(api, 'get_lv_by_fullname', self.mock_get_lv_by_fullname)
+
+ monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active", lambda id: True)
+
+ #find_associated_devices will call get_lvs() 4 times
+ # and it this needs results to be arranged that way
+ self.mock_volumes = []
+ self.mock_volumes.append([data_vol])
+ self.mock_volumes.append([data_vol])
+ self.mock_volumes.append([])
+ self.mock_volumes.append([])
+
+ monkeypatch.setattr(migrate.api, 'get_lvs', self.mock_get_lvs)
+
+ monkeypatch.setattr(migrate, 'get_cluster_name', lambda osd_id, osd_fsid: 'cluster')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
+
+ m = migrate.NewWAL(argv=[
+ '--osd-id', '2',
+ '--osd-fsid', '55BD4219-16A7-4037-BC20-0F158EFCC83D',
+ '--target', 'vgname/new_wal'])
+
+ with pytest.raises(SystemExit) as error:
+ m.main()
+
+ stdout, stderr = capsys.readouterr()
+
+ assert 'Unable to attach new volume for OSD: 2' == str(error.value)
+ assert '--> OSD ID is running, stop it with: systemctl stop ceph-osd@2' == stderr.rstrip()
+ assert not stdout
+
+ def test_newwal_no_systemd(self, is_root, monkeypatch):
+ source_tags = \
+ 'ceph.osd_id=0,ceph.type=data,ceph.osd_fsid=1234'
+
+ data_vol = api.Volume(lv_name='volume1', lv_uuid='datauuid', vg_name='vg',
+ lv_path='/dev/VolGroup/lv1', lv_tags=source_tags)
+
+ self.mock_single_volumes = {'/dev/VolGroup/lv1': data_vol}
+
+ monkeypatch.setattr(migrate.api, 'get_first_lv', self.mock_get_first_lv)
+
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ self.mock_volume = api.Volume(lv_name='target_volume1', lv_uuid='y', vg_name='vg',
+ lv_path='/dev/VolGroup/target_volume',
+ lv_tags='')
+ monkeypatch.setattr(api, 'get_lv_by_fullname', self.mock_get_lv_by_fullname)
+
+ #find_associated_devices will call get_lvs() 4 times
+ # and it this needs results to be arranged that way
+ self.mock_volumes = []
+ self.mock_volumes.append([data_vol])
+ self.mock_volumes.append([data_vol])
+ self.mock_volumes.append([])
+ self.mock_volumes.append([])
+
+ monkeypatch.setattr(migrate.api, 'get_lvs', self.mock_get_lvs)
+
+ monkeypatch.setattr(migrate, 'get_cluster_name', lambda osd_id, osd_fsid: 'cluster')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
+
+ migrate.NewWAL(argv=[
+ '--osd-id', '2',
+ '--osd-fsid', '55BD4219-16A7-4037-BC20-0F158EFCC83D',
+ '--target', 'vgname/new_wal',
+ '--no-systemd']).main()
+
+ n = len(self.mock_process_input)
+ assert n >= 3
+
+ assert self.mock_process_input[n - 3] == [
+ 'lvchange',
+ '--addtag', 'ceph.wal_uuid=y',
+ '--addtag', 'ceph.wal_device=/dev/VolGroup/target_volume',
+ '/dev/VolGroup/lv1']
+
+ assert self.mock_process_input[n - 2].sort() == [
+ 'lvchange',
+ '--addtag', 'ceph.osd_id=0',
+ '--addtag', 'ceph.type=wal',
+ '--addtag', 'ceph.osd_fsid=1234',
+ '--addtag', 'ceph.wal_uuid=y',
+ '--addtag', 'ceph.wal_device=/dev/VolGroup/target_volume',
+ '/dev/VolGroup/target_volume'].sort()
+
+ assert self.mock_process_input[n - 1] == [
+ 'ceph-bluestore-tool',
+ '--path', '/var/lib/ceph/osd/cluster-2',
+ '--dev-target', '/dev/VolGroup/target_volume',
+ '--command', 'bluefs-bdev-new-wal']
+
class TestMigrate(object):
mock_volume = None
lv_path='/dev/VolGroup/lv1',
lv_tags=source_tags)
db_vol = api.Volume(lv_name='volume2',
- lv_uuid='datauuid',
+ lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2',
+ lv_tags=source_db_tags)
+
+ wal_vol = api.Volume(lv_name='volume3',
+ lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv3',
+ lv_tags=source_wal_tags)
+
+ self.mock_single_volumes = {
+ '/dev/VolGroup/lv1': data_vol,
+ '/dev/VolGroup/lv2': db_vol,
+ '/dev/VolGroup/lv3': wal_vol,
+ }
+ monkeypatch.setattr(migrate.api, 'get_first_lv',
+ self.mock_get_first_lv)
+
+ self.mock_volume = api.Volume(lv_name='volume2', lv_uuid='y',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2',
+ lv_tags='ceph.osd_id=5,ceph.osd_type=db')
+ monkeypatch.setattr(api, 'get_lv_by_fullname',
+ self.mock_get_lv_by_fullname)
+
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ devices = []
+ devices.append([Device('/dev/VolGroup/lv1'), 'block'])
+ devices.append([Device('/dev/VolGroup/lv2'), 'db'])
+ devices.append([Device('/dev/VolGroup/lv3'), 'wal'])
+
+ monkeypatch.setattr(migrate, 'find_associated_devices',
+ lambda osd_id, osd_fsid: devices)
+
+
+ argv = [
+ '--osd-id', '2',
+ '--osd-fsid', '55BD4219-16A7-4037-BC20-0F158EFCC83D',
+ '--from', 'data', 'wal',
+ '--target', 'vgname/new_wal'
+ ]
+ m = migrate.Migrate(argv=argv)
+ m.args = m.make_parser('ceph-volume lvm migation', 'help').parse_args(argv)
+ res_devices = m.get_source_devices(devices)
+
+ assert 2 == len(res_devices)
+ assert devices[0] == res_devices[0]
+ assert devices[2] == res_devices[1]
+
+ argv = [
+ '--osd-id', '2',
+ '--osd-fsid', '55BD4219-16A7-4037-BC20-0F158EFCC83D',
+ '--from', 'db', 'wal', 'data',
+ '--target', 'vgname/new_wal'
+ ]
+ m = migrate.Migrate(argv=argv)
+ m.args = m.make_parser('ceph-volume lvm migation', 'help').parse_args(argv)
+ res_devices = m.get_source_devices(devices)
+
+ assert 3 == len(res_devices)
+ assert devices[0] == res_devices[0]
+ assert devices[1] == res_devices[1]
+ assert devices[2] == res_devices[2]
+
+
+ def test_migrate_without_args(self, capsys):
+ help_msg = """
+Moves BlueFS data from source volume(s) to the target one, source
+volumes (except the main (i.e. data or block) one) are removed on
+success. LVM volumes are permitted for Target only, both already
+attached or new logical one. In the latter case it is attached to OSD
+replacing one of the source devices. Following replacement rules apply
+(in the order of precedence, stop on the first match):
+* if source list has DB volume - target device replaces it.
+* if source list has WAL volume - target device replace it.
+* if source list has slow volume only - operation is not permitted,
+ requires explicit allocation via new-db/new-wal command.
+
+Example calls for supported scenarios:
+
+ Moves BlueFS data from main device to LV already attached as DB:
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from data --target vgname/db
+
+ Moves BlueFS data from shared main device to LV which will be attached
+ as a new DB:
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from data --target vgname/new_db
+
+ Moves BlueFS data from DB device to new LV, DB is replaced:
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from db --target vgname/new_db
+
+ Moves BlueFS data from main and DB devices to new LV, DB is replaced:
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from data db --target vgname/new_db
+
+ Moves BlueFS data from main, DB and WAL devices to new LV, WAL is
+ removed and DB is replaced:
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from data db wal --target vgname/new_db
+
+ Moves BlueFS data from main, DB and WAL devices to main device, WAL
+ and DB are removed:
+
+ ceph-volume lvm migrate --osd-id 1 --osd-fsid <uuid> --from db wal --target vgname/data
+
+"""
+ m = migrate.Migrate(argv=[])
+ m.main()
+ stdout, stderr = capsys.readouterr()
+ assert help_msg in stdout
+ assert not stderr
+
+
+ @patch('os.getuid')
+ def test_migrate_data_db_to_new_db(self, m_getuid, monkeypatch):
+ m_getuid.return_value = 0
+
+ source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+
+ data_vol = api.Volume(lv_name='volume1',
+ lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv1',
+ lv_tags=source_tags)
+ db_vol = api.Volume(lv_name='volume2',
+ lv_uuid='dbuuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2',
+ lv_tags=source_db_tags)
+
+ self.mock_single_volumes = {
+ '/dev/VolGroup/lv1': data_vol,
+ '/dev/VolGroup/lv2': db_vol,
+ }
+ monkeypatch.setattr(migrate.api, 'get_first_lv',
+ self.mock_get_first_lv)
+
+ self.mock_volume = api.Volume(lv_name='volume2_new', lv_uuid='new-db-uuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2_new',
+ lv_tags='')
+ monkeypatch.setattr(api, 'get_lv_by_fullname',
+ self.mock_get_lv_by_fullname)
+
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ devices = []
+ devices.append([Device('/dev/VolGroup/lv1'), 'block'])
+ devices.append([Device('/dev/VolGroup/lv2'), 'db'])
+
+ monkeypatch.setattr(migrate, 'find_associated_devices',
+ lambda osd_id, osd_fsid: devices)
+
+
+ monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active",
+ lambda id: False)
+
+ monkeypatch.setattr(migrate, 'get_cluster_name',
+ lambda osd_id, osd_fsid: 'ceph')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
+ m = migrate.Migrate(argv=[
+ '--osd-id', '2',
+ '--osd-fsid', '1234',
+ '--from', 'data', 'db', 'wal',
+ '--target', 'vgname/new_wal'])
+ m.main()
+
+ n = len(self.mock_process_input)
+ assert n >= 5
+
+ assert self. mock_process_input[n-5] == [
+ 'lvchange',
+ '--deltag', 'ceph.osd_id=2',
+ '--deltag', 'ceph.type=db',
+ '--deltag', 'ceph.osd_fsid=1234',
+ '--deltag', 'ceph.cluster_name=ceph',
+ '--deltag', 'ceph.db_uuid=dbuuid',
+ '--deltag', 'ceph.db_device=db_dev',
+ '/dev/VolGroup/lv2']
+
+ assert self. mock_process_input[n-4] == [
+ 'lvchange',
+ '--deltag', 'ceph.db_uuid=dbuuid',
+ '--deltag', 'ceph.db_device=db_dev',
+ '/dev/VolGroup/lv1']
+
+ assert self. mock_process_input[n-3] == [
+ 'lvchange',
+ '--addtag', 'ceph.db_uuid=new-db-uuid',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
+ '/dev/VolGroup/lv1']
+
+ assert self. mock_process_input[n-2] == [
+ 'lvchange',
+ '--addtag', 'ceph.osd_id=2',
+ '--addtag', 'ceph.type=db',
+ '--addtag', 'ceph.osd_fsid=1234',
+ '--addtag', 'ceph.cluster_name=ceph',
+ '--addtag', 'ceph.db_uuid=new-db-uuid',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
+ '/dev/VolGroup/lv2_new']
+
+ assert self. mock_process_input[n-1] == [
+ 'ceph-bluestore-tool',
+ '--path', '/var/lib/ceph/osd/ceph-2',
+ '--dev-target', '/dev/VolGroup/lv2_new',
+ '--command', 'bluefs-bdev-migrate',
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block',
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block.db']
+
+ def test_migrate_data_db_to_new_db_active_systemd(self, is_root, monkeypatch, capsys):
+ source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+
+ data_vol = api.Volume(lv_name='volume1',
+ lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv1',
+ lv_tags=source_tags)
+ db_vol = api.Volume(lv_name='volume2',
+ lv_uuid='dbuuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2',
+ lv_tags=source_db_tags)
+
+ self.mock_single_volumes = {
+ '/dev/VolGroup/lv1': data_vol,
+ '/dev/VolGroup/lv2': db_vol,
+ }
+ monkeypatch.setattr(migrate.api, 'get_first_lv',
+ self.mock_get_first_lv)
+
+ self.mock_volume = api.Volume(lv_name='volume2_new', lv_uuid='new-db-uuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2_new',
+ lv_tags='')
+ monkeypatch.setattr(api, 'get_lv_by_fullname',
+ self.mock_get_lv_by_fullname)
+
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ devices = []
+ devices.append([Device('/dev/VolGroup/lv1'), 'block'])
+ devices.append([Device('/dev/VolGroup/lv2'), 'db'])
+
+ monkeypatch.setattr(migrate, 'find_associated_devices',
+ lambda osd_id, osd_fsid: devices)
+
+
+ monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active",
+ lambda id: True)
+
+ monkeypatch.setattr(migrate, 'get_cluster_name',
+ lambda osd_id, osd_fsid: 'ceph')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
+ m = migrate.Migrate(argv=[
+ '--osd-id', '2',
+ '--osd-fsid', '1234',
+ '--from', 'data', 'db', 'wal',
+ '--target', 'vgname/new_wal'])
+
+ with pytest.raises(SystemExit) as error:
+ m.main()
+
+ stdout, stderr = capsys.readouterr()
+
+ assert 'Unable to migrate devices associated with OSD ID: 2' == str(error.value)
+ assert '--> OSD is running, stop it with: systemctl stop ceph-osd@2' == stderr.rstrip()
+ assert not stdout
+
+ def test_migrate_data_db_to_new_db_no_systemd(self, is_root, monkeypatch):
+ source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+
+ data_vol = api.Volume(lv_name='volume1',
+ lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv1',
+ lv_tags=source_tags)
+ db_vol = api.Volume(lv_name='volume2',
+ lv_uuid='dbuuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2',
+ lv_tags=source_db_tags)
+
+ self.mock_single_volumes = {
+ '/dev/VolGroup/lv1': data_vol,
+ '/dev/VolGroup/lv2': db_vol,
+ }
+ monkeypatch.setattr(migrate.api, 'get_first_lv',
+ self.mock_get_first_lv)
+
+ self.mock_volume = api.Volume(lv_name='volume2_new', lv_uuid='new-db-uuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2_new',
+ lv_tags='')
+ monkeypatch.setattr(api, 'get_lv_by_fullname',
+ self.mock_get_lv_by_fullname)
+
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ devices = []
+ devices.append([Device('/dev/VolGroup/lv1'), 'block'])
+ devices.append([Device('/dev/VolGroup/lv2'), 'db'])
+
+ monkeypatch.setattr(migrate, 'find_associated_devices',
+ lambda osd_id, osd_fsid: devices)
+
+
+ monkeypatch.setattr(migrate, 'get_cluster_name',
+ lambda osd_id, osd_fsid: 'ceph')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
+ m = migrate.Migrate(argv=[
+ '--osd-id', '2',
+ '--osd-fsid', '1234',
+ '--from', 'data', 'db', 'wal',
+ '--target', 'vgname/new_wal',
+ '--no-systemd'])
+ m.main()
+
+ n = len(self.mock_process_input)
+ assert n >= 5
+
+ assert self. mock_process_input[n-5] == [
+ 'lvchange',
+ '--deltag', 'ceph.osd_id=2',
+ '--deltag', 'ceph.type=db',
+ '--deltag', 'ceph.osd_fsid=1234',
+ '--deltag', 'ceph.cluster_name=ceph',
+ '--deltag', 'ceph.db_uuid=dbuuid',
+ '--deltag', 'ceph.db_device=db_dev',
+ '/dev/VolGroup/lv2']
+
+ assert self. mock_process_input[n-4] == [
+ 'lvchange',
+ '--deltag', 'ceph.db_uuid=dbuuid',
+ '--deltag', 'ceph.db_device=db_dev',
+ '/dev/VolGroup/lv1']
+
+ assert self. mock_process_input[n-3] == [
+ 'lvchange',
+ '--addtag', 'ceph.db_uuid=new-db-uuid',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
+ '/dev/VolGroup/lv1']
+
+ assert self. mock_process_input[n-2] == [
+ 'lvchange',
+ '--addtag', 'ceph.osd_id=2',
+ '--addtag', 'ceph.type=db',
+ '--addtag', 'ceph.osd_fsid=1234',
+ '--addtag', 'ceph.cluster_name=ceph',
+ '--addtag', 'ceph.db_uuid=new-db-uuid',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
+ '/dev/VolGroup/lv2_new']
+
+ assert self. mock_process_input[n-1] == [
+ 'ceph-bluestore-tool',
+ '--path', '/var/lib/ceph/osd/ceph-2',
+ '--dev-target', '/dev/VolGroup/lv2_new',
+ '--command', 'bluefs-bdev-migrate',
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block',
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block.db']
+
+ @patch('os.getuid')
+ def test_migrate_data_db_to_new_db_skip_wal(self, m_getuid, monkeypatch):
+ m_getuid.return_value = 0
+
+ source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ source_wal_tags = 'ceph.osd_id=2,ceph.type=wal,ceph.osd_fsid=1234' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+
+ data_vol = api.Volume(lv_name='volume1',
+ lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv1',
+ lv_tags=source_tags)
+ db_vol = api.Volume(lv_name='volume2',
+ lv_uuid='dbuuid',
vg_name='vg',
lv_path='/dev/VolGroup/lv2',
lv_tags=source_db_tags)
monkeypatch.setattr(migrate.api, 'get_first_lv',
self.mock_get_first_lv)
- self.mock_volume = api.Volume(lv_name='volume2', lv_uuid='y',
+ self.mock_volume = api.Volume(lv_name='volume2_new', lv_uuid='new-db-uuid',
vg_name='vg',
- lv_path='/dev/VolGroup/lv2',
- lv_tags='ceph.osd_id=5,ceph.osd_type=db')
+ lv_path='/dev/VolGroup/lv2_new',
+ lv_tags='')
monkeypatch.setattr(api, 'get_lv_by_fullname',
self.mock_get_lv_by_fullname)
monkeypatch.setattr(migrate, 'find_associated_devices',
lambda osd_id, osd_fsid: devices)
+ monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active",
+ lambda id: False)
+ monkeypatch.setattr(migrate, 'get_cluster_name',
+ lambda osd_id, osd_fsid: 'ceph')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
m = migrate.Migrate(argv=[
'--osd-id', '2',
- '--osd-fsid', '55BD4219-16A7-4037-BC20-0F158EFCC83D',
- '--from', 'data', 'wal',
+ '--osd-fsid', '1234',
+ '--from', 'data', 'db',
'--target', 'vgname/new_wal'])
- m.parse_argv()
- res_devices = m.get_source_devices(devices)
+ m.main()
- assert 2 == len(res_devices)
- assert devices[0] == res_devices[0]
- assert devices[2] == res_devices[1]
+ n = len(self.mock_process_input)
+ assert n >= 7
- m = migrate.Migrate(argv=[
- '--osd-id', '2',
- '--osd-fsid', '55BD4219-16A7-4037-BC20-0F158EFCC83D',
- '--from', 'db', 'wal', 'data',
- '--target', 'vgname/new_wal'])
- m.parse_argv()
- res_devices = m.get_source_devices(devices)
+ assert self. mock_process_input[n-7] == [
+ 'lvchange',
+ '--deltag', 'ceph.osd_id=2',
+ '--deltag', 'ceph.type=db',
+ '--deltag', 'ceph.osd_fsid=1234',
+ '--deltag', 'ceph.cluster_name=ceph',
+ '--deltag', 'ceph.db_uuid=dbuuid',
+ '--deltag', 'ceph.db_device=db_dev',
+ '/dev/VolGroup/lv2']
- assert 3 == len(res_devices)
- assert devices[0] == res_devices[0]
- assert devices[1] == res_devices[1]
- assert devices[2] == res_devices[2]
+ assert self. mock_process_input[n-6] == [
+ 'lvchange',
+ '--deltag', 'ceph.db_uuid=dbuuid',
+ '--deltag', 'ceph.db_device=db_dev',
+ '/dev/VolGroup/lv1']
+
+ assert self. mock_process_input[n-5] == [
+ 'lvchange',
+ '--addtag', 'ceph.db_uuid=new-db-uuid',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
+ '/dev/VolGroup/lv1']
+
+ assert self. mock_process_input[n-4] == [
+ 'lvchange',
+ '--deltag', 'ceph.db_uuid=dbuuid',
+ '--deltag', 'ceph.db_device=db_dev',
+ '/dev/VolGroup/lv3']
+
+ assert self. mock_process_input[n-3] == [
+ 'lvchange',
+ '--addtag', 'ceph.db_uuid=new-db-uuid',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
+ '/dev/VolGroup/lv3']
+
+ assert self. mock_process_input[n-2] == [
+ 'lvchange',
+ '--addtag', 'ceph.osd_id=2',
+ '--addtag', 'ceph.type=db',
+ '--addtag', 'ceph.osd_fsid=1234',
+ '--addtag', 'ceph.cluster_name=ceph',
+ '--addtag', 'ceph.db_uuid=new-db-uuid',
+ '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
+ '/dev/VolGroup/lv2_new']
+ assert self. mock_process_input[n-1] == [
+ 'ceph-bluestore-tool',
+ '--path', '/var/lib/ceph/osd/ceph-2',
+ '--dev-target', '/dev/VolGroup/lv2_new',
+ '--command', 'bluefs-bdev-migrate',
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block',
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block.db']
@patch('os.getuid')
- def test_migrate_data_db_to_new_db(self, m_getuid, monkeypatch):
+ def test_migrate_data_db_wal_to_new_db(self, m_getuid, monkeypatch):
m_getuid.return_value = 0
source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
- 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ source_wal_tags = 'ceph.osd_id=0,ceph.type=wal,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
data_vol = api.Volume(lv_name='volume1',
lv_uuid='datauuid',
lv_path='/dev/VolGroup/lv2',
lv_tags=source_db_tags)
+ wal_vol = api.Volume(lv_name='volume3',
+ lv_uuid='waluuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv3',
+ lv_tags=source_wal_tags)
+
self.mock_single_volumes = {
'/dev/VolGroup/lv1': data_vol,
'/dev/VolGroup/lv2': db_vol,
+ '/dev/VolGroup/lv3': wal_vol,
}
monkeypatch.setattr(migrate.api, 'get_first_lv',
self.mock_get_first_lv)
devices = []
devices.append([Device('/dev/VolGroup/lv1'), 'block'])
devices.append([Device('/dev/VolGroup/lv2'), 'db'])
+ devices.append([Device('/dev/VolGroup/lv3'), 'wal'])
monkeypatch.setattr(migrate, 'find_associated_devices',
lambda osd_id, osd_fsid: devices)
-
monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active",
lambda id: False)
m.main()
n = len(self.mock_process_input)
- assert n >= 5
+ assert n >= 6
- assert self. mock_process_input[n-5] == [
+ assert self. mock_process_input[n-6] == [
'lvchange',
'--deltag', 'ceph.osd_id=2',
'--deltag', 'ceph.type=db',
'--deltag', 'ceph.db_device=db_dev',
'/dev/VolGroup/lv2']
+ assert self. mock_process_input[n-5] == [
+ 'lvchange',
+ '--deltag', 'ceph.osd_id=0',
+ '--deltag', 'ceph.type=wal',
+ '--deltag', 'ceph.osd_fsid=1234',
+ '--deltag', 'ceph.cluster_name=ceph',
+ '--deltag', 'ceph.db_uuid=dbuuid',
+ '--deltag', 'ceph.db_device=db_dev',
+ '--deltag', 'ceph.wal_uuid=waluuid',
+ '--deltag', 'ceph.wal_device=wal_dev',
+ '/dev/VolGroup/lv3']
+
assert self. mock_process_input[n-4] == [
'lvchange',
'--deltag', 'ceph.db_uuid=dbuuid',
'--deltag', 'ceph.db_device=db_dev',
+ '--deltag', 'ceph.wal_uuid=waluuid',
+ '--deltag', 'ceph.wal_device=wal_dev',
'/dev/VolGroup/lv1']
assert self. mock_process_input[n-3] == [
'--dev-target', '/dev/VolGroup/lv2_new',
'--command', 'bluefs-bdev-migrate',
'--devs-source', '/var/lib/ceph/osd/ceph-2/block',
- '--devs-source', '/var/lib/ceph/osd/ceph-2/block.db']
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block.db',
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block.wal']
@patch('os.getuid')
- def test_migrate_data_db_to_new_db_skip_wal(self, m_getuid, monkeypatch):
+ def test_dont_migrate_data_db_wal_to_new_data(self,
+ m_getuid,
+ monkeypatch,
+ capsys):
m_getuid.return_value = 0
source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
- source_wal_tags = 'ceph.osd_id=2,ceph.type=wal,ceph.osd_fsid=1234' \
- 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
data_vol = api.Volume(lv_name='volume1',
lv_uuid='datauuid',
lv_path='/dev/VolGroup/lv2',
lv_tags=source_db_tags)
- wal_vol = api.Volume(lv_name='volume3',
- lv_uuid='datauuid',
- vg_name='vg',
- lv_path='/dev/VolGroup/lv3',
- lv_tags=source_wal_tags)
-
self.mock_single_volumes = {
'/dev/VolGroup/lv1': data_vol,
'/dev/VolGroup/lv2': db_vol,
- '/dev/VolGroup/lv3': wal_vol,
}
monkeypatch.setattr(migrate.api, 'get_first_lv',
self.mock_get_first_lv)
devices = []
devices.append([Device('/dev/VolGroup/lv1'), 'block'])
devices.append([Device('/dev/VolGroup/lv2'), 'db'])
- devices.append([Device('/dev/VolGroup/lv3'), 'wal'])
monkeypatch.setattr(migrate, 'find_associated_devices',
lambda osd_id, osd_fsid: devices)
m = migrate.Migrate(argv=[
'--osd-id', '2',
'--osd-fsid', '1234',
- '--from', 'data', 'db',
- '--target', 'vgname/new_wal'])
- m.main()
+ '--from', 'data',
+ '--target', 'vgname/new_data'])
- n = len(self.mock_process_input)
- assert n >= 7
+ with pytest.raises(SystemExit) as error:
+ m.main()
+ stdout, stderr = capsys.readouterr()
+ expected = 'Unable to migrate to : vgname/new_data'
+ assert expected in str(error.value)
+ expected = 'Unable to determine new volume type,'
+ ' please use new-db or new-wal command before.'
+ assert expected in stderr
- assert self. mock_process_input[n-7] == [
- 'lvchange',
- '--deltag', 'ceph.osd_id=2',
- '--deltag', 'ceph.type=db',
- '--deltag', 'ceph.osd_fsid=1234',
- '--deltag', 'ceph.cluster_name=ceph',
- '--deltag', 'ceph.db_uuid=dbuuid',
- '--deltag', 'ceph.db_device=db_dev',
- '/dev/VolGroup/lv2']
+ @patch('os.getuid')
+ def test_dont_migrate_db_to_wal(self,
+ m_getuid,
+ monkeypatch,
+ capsys):
+ m_getuid.return_value = 0
- assert self. mock_process_input[n-6] == [
- 'lvchange',
- '--deltag', 'ceph.db_uuid=dbuuid',
- '--deltag', 'ceph.db_device=db_dev',
- '/dev/VolGroup/lv1']
+ source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
+ source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ source_wal_tags = 'ceph.osd_id=2,ceph.type=wal,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
- assert self. mock_process_input[n-5] == [
- 'lvchange',
- '--addtag', 'ceph.db_uuid=new-db-uuid',
- '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
- '/dev/VolGroup/lv1']
+ data_vol = api.Volume(lv_name='volume1',
+ lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv1',
+ lv_tags=source_tags)
+ db_vol = api.Volume(lv_name='volume2',
+ lv_uuid='dbuuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2',
+ lv_tags=source_db_tags)
- assert self. mock_process_input[n-4] == [
- 'lvchange',
- '--deltag', 'ceph.db_uuid=dbuuid',
- '--deltag', 'ceph.db_device=db_dev',
- '/dev/VolGroup/lv3']
+ wal_vol = api.Volume(lv_name='volume3',
+ lv_uuid='waluuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv3',
+ lv_tags=source_wal_tags)
- assert self. mock_process_input[n-3] == [
- 'lvchange',
- '--addtag', 'ceph.db_uuid=new-db-uuid',
- '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
- '/dev/VolGroup/lv3']
+ self.mock_single_volumes = {
+ '/dev/VolGroup/lv1': data_vol,
+ '/dev/VolGroup/lv2': db_vol,
+ '/dev/VolGroup/lv3': wal_vol,
+ }
+ monkeypatch.setattr(migrate.api, 'get_first_lv',
+ self.mock_get_first_lv)
- assert self. mock_process_input[n-2] == [
- 'lvchange',
- '--addtag', 'ceph.osd_id=2',
- '--addtag', 'ceph.type=db',
- '--addtag', 'ceph.osd_fsid=1234',
- '--addtag', 'ceph.cluster_name=ceph',
- '--addtag', 'ceph.db_uuid=new-db-uuid',
- '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
- '/dev/VolGroup/lv2_new']
+ self.mock_volume = wal_vol
+ monkeypatch.setattr(api, 'get_lv_by_fullname',
+ self.mock_get_lv_by_fullname)
- assert self. mock_process_input[n-1] == [
- 'ceph-bluestore-tool',
- '--path', '/var/lib/ceph/osd/ceph-2',
- '--dev-target', '/dev/VolGroup/lv2_new',
- '--command', 'bluefs-bdev-migrate',
- '--devs-source', '/var/lib/ceph/osd/ceph-2/block',
- '--devs-source', '/var/lib/ceph/osd/ceph-2/block.db']
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ devices = []
+ devices.append([Device('/dev/VolGroup/lv1'), 'block'])
+ devices.append([Device('/dev/VolGroup/lv2'), 'db'])
+ devices.append([Device('/dev/VolGroup/lv3'), 'wal'])
+
+ monkeypatch.setattr(migrate, 'find_associated_devices',
+ lambda osd_id, osd_fsid: devices)
+
+ monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active",
+ lambda id: False)
+
+ monkeypatch.setattr(migrate, 'get_cluster_name',
+ lambda osd_id, osd_fsid: 'ceph')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
+ m = migrate.Migrate(argv=[
+ '--osd-id', '2',
+ '--osd-fsid', '1234',
+ '--from', 'db',
+ '--target', 'vgname/wal'])
+
+ with pytest.raises(SystemExit) as error:
+ m.main()
+ stdout, stderr = capsys.readouterr()
+ expected = 'Unable to migrate to : vgname/wal'
+ assert expected in str(error.value)
+ expected = 'Migrate to WAL is not supported'
+ assert expected in stderr
@patch('os.getuid')
- def test_migrate_data_db_wal_to_new_db(self, m_getuid, monkeypatch):
+ def test_migrate_data_db_to_db(self,
+ m_getuid,
+ monkeypatch,
+ capsys):
m_getuid.return_value = 0
source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
- source_wal_tags = 'ceph.osd_id=0,ceph.type=wal,ceph.osd_fsid=1234,' \
+ source_wal_tags = 'ceph.osd_id=2,ceph.type=wal,ceph.osd_fsid=1234,' \
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
monkeypatch.setattr(migrate.api, 'get_first_lv',
self.mock_get_first_lv)
- self.mock_volume = api.Volume(lv_name='volume2_new', lv_uuid='new-db-uuid',
- vg_name='vg',
- lv_path='/dev/VolGroup/lv2_new',
- lv_tags='')
+ self.mock_volume = db_vol
monkeypatch.setattr(api, 'get_lv_by_fullname',
self.mock_get_lv_by_fullname)
m = migrate.Migrate(argv=[
'--osd-id', '2',
'--osd-fsid', '1234',
- '--from', 'data', 'db', 'wal',
- '--target', 'vgname/new_wal'])
+ '--from', 'db', 'data',
+ '--target', 'vgname/db'])
+
m.main()
n = len(self.mock_process_input)
- assert n >= 6
-
- assert self. mock_process_input[n-6] == [
- 'lvchange',
- '--deltag', 'ceph.osd_id=2',
- '--deltag', 'ceph.type=db',
- '--deltag', 'ceph.osd_fsid=1234',
- '--deltag', 'ceph.cluster_name=ceph',
- '--deltag', 'ceph.db_uuid=dbuuid',
- '--deltag', 'ceph.db_device=db_dev',
- '/dev/VolGroup/lv2']
-
- assert self. mock_process_input[n-5] == [
- 'lvchange',
- '--deltag', 'ceph.osd_id=0',
- '--deltag', 'ceph.type=wal',
- '--deltag', 'ceph.osd_fsid=1234',
- '--deltag', 'ceph.cluster_name=ceph',
- '--deltag', 'ceph.db_uuid=dbuuid',
- '--deltag', 'ceph.db_device=db_dev',
- '--deltag', 'ceph.wal_uuid=waluuid',
- '--deltag', 'ceph.wal_device=wal_dev',
- '/dev/VolGroup/lv3']
-
- assert self. mock_process_input[n-4] == [
- 'lvchange',
- '--deltag', 'ceph.db_uuid=dbuuid',
- '--deltag', 'ceph.db_device=db_dev',
- '--deltag', 'ceph.wal_uuid=waluuid',
- '--deltag', 'ceph.wal_device=wal_dev',
- '/dev/VolGroup/lv1']
-
- assert self. mock_process_input[n-3] == [
- 'lvchange',
- '--addtag', 'ceph.db_uuid=new-db-uuid',
- '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
- '/dev/VolGroup/lv1']
-
- assert self. mock_process_input[n-2] == [
- 'lvchange',
- '--addtag', 'ceph.osd_id=2',
- '--addtag', 'ceph.type=db',
- '--addtag', 'ceph.osd_fsid=1234',
- '--addtag', 'ceph.cluster_name=ceph',
- '--addtag', 'ceph.db_uuid=new-db-uuid',
- '--addtag', 'ceph.db_device=/dev/VolGroup/lv2_new',
- '/dev/VolGroup/lv2_new']
+ assert n >= 1
+ for s in self.mock_process_input:
+ print(s)
assert self. mock_process_input[n-1] == [
'ceph-bluestore-tool',
'--path', '/var/lib/ceph/osd/ceph-2',
- '--dev-target', '/dev/VolGroup/lv2_new',
+ '--dev-target', '/var/lib/ceph/osd/ceph-2/block.db',
'--command', 'bluefs-bdev-migrate',
- '--devs-source', '/var/lib/ceph/osd/ceph-2/block',
- '--devs-source', '/var/lib/ceph/osd/ceph-2/block.db',
- '--devs-source', '/var/lib/ceph/osd/ceph-2/block.wal']
-
- @patch('os.getuid')
- def test_dont_migrate_data_db_wal_to_new_data(self,
- m_getuid,
- monkeypatch,
- capsys):
- m_getuid.return_value = 0
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block']
+ def test_migrate_data_db_to_db_active_systemd(self, is_root, monkeypatch, capsys):
source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
- 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ source_wal_tags = 'ceph.osd_id=2,ceph.type=wal,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
data_vol = api.Volume(lv_name='volume1',
lv_uuid='datauuid',
lv_path='/dev/VolGroup/lv2',
lv_tags=source_db_tags)
+ wal_vol = api.Volume(lv_name='volume3',
+ lv_uuid='waluuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv3',
+ lv_tags=source_wal_tags)
+
self.mock_single_volumes = {
'/dev/VolGroup/lv1': data_vol,
'/dev/VolGroup/lv2': db_vol,
+ '/dev/VolGroup/lv3': wal_vol,
}
monkeypatch.setattr(migrate.api, 'get_first_lv',
self.mock_get_first_lv)
- self.mock_volume = api.Volume(lv_name='volume2_new', lv_uuid='new-db-uuid',
- vg_name='vg',
- lv_path='/dev/VolGroup/lv2_new',
- lv_tags='')
+ self.mock_volume = db_vol
monkeypatch.setattr(api, 'get_lv_by_fullname',
self.mock_get_lv_by_fullname)
devices = []
devices.append([Device('/dev/VolGroup/lv1'), 'block'])
devices.append([Device('/dev/VolGroup/lv2'), 'db'])
+ devices.append([Device('/dev/VolGroup/lv3'), 'wal'])
monkeypatch.setattr(migrate, 'find_associated_devices',
lambda osd_id, osd_fsid: devices)
monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active",
- lambda id: False)
+ lambda id: True)
monkeypatch.setattr(migrate, 'get_cluster_name',
lambda osd_id, osd_fsid: 'ceph')
m = migrate.Migrate(argv=[
'--osd-id', '2',
'--osd-fsid', '1234',
- '--from', 'data',
- '--target', 'vgname/new_data'])
+ '--from', 'db', 'data',
+ '--target', 'vgname/db'])
with pytest.raises(SystemExit) as error:
m.main()
+
stdout, stderr = capsys.readouterr()
- expected = 'Unable to migrate to : vgname/new_data'
- assert expected in str(error.value)
- expected = 'Unable to determine new volume type,'
- ' please use new-db or new-wal command before.'
- assert expected in stderr
- @patch('os.getuid')
- def test_dont_migrate_db_to_wal(self,
- m_getuid,
- monkeypatch,
- capsys):
- m_getuid.return_value = 0
+ assert 'Unable to migrate devices associated with OSD ID: 2' == str(error.value)
+ assert '--> OSD is running, stop it with: systemctl stop ceph-osd@2' == stderr.rstrip()
+ assert not stdout
+ def test_migrate_data_db_to_db_no_systemd(self, is_root, monkeypatch):
source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
monkeypatch.setattr(migrate.api, 'get_first_lv',
self.mock_get_first_lv)
- self.mock_volume = wal_vol
+ self.mock_volume = db_vol
monkeypatch.setattr(api, 'get_lv_by_fullname',
self.mock_get_lv_by_fullname)
monkeypatch.setattr(migrate, 'find_associated_devices',
lambda osd_id, osd_fsid: devices)
- monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active",
- lambda id: False)
-
monkeypatch.setattr(migrate, 'get_cluster_name',
lambda osd_id, osd_fsid: 'ceph')
monkeypatch.setattr(system, 'chown', lambda path: 0)
m = migrate.Migrate(argv=[
'--osd-id', '2',
'--osd-fsid', '1234',
- '--from', 'db',
- '--target', 'vgname/wal'])
+ '--from', 'db', 'data',
+ '--target', 'vgname/db',
+ '--no-systemd'])
- with pytest.raises(SystemExit) as error:
- m.main()
- stdout, stderr = capsys.readouterr()
- expected = 'Unable to migrate to : vgname/wal'
- assert expected in str(error.value)
- expected = 'Migrate to WAL is not supported'
- assert expected in stderr
+ m.main()
+
+ n = len(self.mock_process_input)
+ assert n >= 1
+ for s in self.mock_process_input:
+ print(s)
+
+ assert self. mock_process_input[n-1] == [
+ 'ceph-bluestore-tool',
+ '--path', '/var/lib/ceph/osd/ceph-2',
+ '--dev-target', '/var/lib/ceph/osd/ceph-2/block.db',
+ '--command', 'bluefs-bdev-migrate',
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block']
@patch('os.getuid')
- def test_migrate_data_db_to_db(self,
+ def test_migrate_data_wal_to_db(self,
m_getuid,
monkeypatch,
capsys):
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
- 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev'
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
source_wal_tags = 'ceph.osd_id=2,ceph.type=wal,ceph.osd_fsid=1234,' \
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
m = migrate.Migrate(argv=[
'--osd-id', '2',
'--osd-fsid', '1234',
- '--from', 'db', 'data',
+ '--from', 'db', 'data', 'wal',
'--target', 'vgname/db'])
m.main()
for s in self.mock_process_input:
print(s)
+ assert self. mock_process_input[n-4] == [
+ 'lvchange',
+ '--deltag', 'ceph.osd_id=2',
+ '--deltag', 'ceph.type=wal',
+ '--deltag', 'ceph.osd_fsid=1234',
+ '--deltag', 'ceph.cluster_name=ceph',
+ '--deltag', 'ceph.db_uuid=dbuuid',
+ '--deltag', 'ceph.db_device=db_dev',
+ '--deltag', 'ceph.wal_uuid=waluuid',
+ '--deltag', 'ceph.wal_device=wal_dev',
+ '/dev/VolGroup/lv3']
+ assert self. mock_process_input[n-3] == [
+ 'lvchange',
+ '--deltag', 'ceph.wal_uuid=waluuid',
+ '--deltag', 'ceph.wal_device=wal_dev',
+ '/dev/VolGroup/lv1']
+ assert self. mock_process_input[n-2] == [
+ 'lvchange',
+ '--deltag', 'ceph.wal_uuid=waluuid',
+ '--deltag', 'ceph.wal_device=wal_dev',
+ '/dev/VolGroup/lv2']
assert self. mock_process_input[n-1] == [
'ceph-bluestore-tool',
'--path', '/var/lib/ceph/osd/ceph-2',
'--dev-target', '/var/lib/ceph/osd/ceph-2/block.db',
'--command', 'bluefs-bdev-migrate',
- '--devs-source', '/var/lib/ceph/osd/ceph-2/block']
-
- @patch('os.getuid')
- def test_migrate_data_wal_to_db(self,
- m_getuid,
- monkeypatch,
- capsys):
- m_getuid.return_value = 0
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block',
+ '--devs-source', '/var/lib/ceph/osd/ceph-2/block.wal']
+ def test_migrate_data_wal_to_db_active_systemd(self, is_root, monkeypatch, capsys):
source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
lambda osd_id, osd_fsid: devices)
monkeypatch.setattr("ceph_volume.systemd.systemctl.osd_is_active",
- lambda id: False)
+ lambda id: True)
monkeypatch.setattr(migrate, 'get_cluster_name',
lambda osd_id, osd_fsid: 'ceph')
'--from', 'db', 'data', 'wal',
'--target', 'vgname/db'])
+ with pytest.raises(SystemExit) as error:
+ m.main()
+
+ stdout, stderr = capsys.readouterr()
+
+ assert 'Unable to migrate devices associated with OSD ID: 2' == str(error.value)
+ assert '--> OSD is running, stop it with: systemctl stop ceph-osd@2' == stderr.rstrip()
+ assert not stdout
+
+ def test_migrate_data_wal_to_db_no_systemd(self, is_root, monkeypatch):
+ source_tags = 'ceph.osd_id=2,ceph.type=data,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
+ source_db_tags = 'ceph.osd_id=2,ceph.type=db,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
+ source_wal_tags = 'ceph.osd_id=2,ceph.type=wal,ceph.osd_fsid=1234,' \
+ 'ceph.cluster_name=ceph,ceph.db_uuid=dbuuid,ceph.db_device=db_dev,' \
+ 'ceph.wal_uuid=waluuid,ceph.wal_device=wal_dev'
+
+ data_vol = api.Volume(lv_name='volume1',
+ lv_uuid='datauuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv1',
+ lv_tags=source_tags)
+ db_vol = api.Volume(lv_name='volume2',
+ lv_uuid='dbuuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv2',
+ lv_tags=source_db_tags)
+
+ wal_vol = api.Volume(lv_name='volume3',
+ lv_uuid='waluuid',
+ vg_name='vg',
+ lv_path='/dev/VolGroup/lv3',
+ lv_tags=source_wal_tags)
+
+ self.mock_single_volumes = {
+ '/dev/VolGroup/lv1': data_vol,
+ '/dev/VolGroup/lv2': db_vol,
+ '/dev/VolGroup/lv3': wal_vol,
+ }
+ monkeypatch.setattr(migrate.api, 'get_first_lv',
+ self.mock_get_first_lv)
+
+ self.mock_volume = db_vol
+ monkeypatch.setattr(api, 'get_lv_by_fullname',
+ self.mock_get_lv_by_fullname)
+
+ self.mock_process_input = []
+ monkeypatch.setattr(process, 'call', self.mock_process)
+
+ devices = []
+ devices.append([Device('/dev/VolGroup/lv1'), 'block'])
+ devices.append([Device('/dev/VolGroup/lv2'), 'db'])
+ devices.append([Device('/dev/VolGroup/lv3'), 'wal'])
+
+ monkeypatch.setattr(migrate, 'find_associated_devices',
+ lambda osd_id, osd_fsid: devices)
+
+ monkeypatch.setattr(migrate, 'get_cluster_name',
+ lambda osd_id, osd_fsid: 'ceph')
+ monkeypatch.setattr(system, 'chown', lambda path: 0)
+ m = migrate.Migrate(argv=[
+ '--osd-id', '2',
+ '--osd-fsid', '1234',
+ '--from', 'db', 'data', 'wal',
+ '--target', 'vgname/db',
+ '--no-systemd'])
+
m.main()
n = len(self.mock_process_input)
--- /dev/null
+import pytest
+from mock.mock import patch
+from ceph_volume.devices import raw
+
+# Sample lsblk output is below that overviews the test scenario. (--json output for reader clarity)
+# - sda and all its children are used for the OS
+# - sdb is a bluestore OSD with phantom Atari partitions
+# - sdc is an empty disk
+# - sdd has 2 LVM device children
+# > lsblk --paths --json
+# {
+# "blockdevices": [
+# {"name": "/dev/sda", "maj:min": "8:0", "rm": "0", "size": "128G", "ro": "0", "type": "disk", "mountpoint": null,
+# "children": [
+# {"name": "/dev/sda1", "maj:min": "8:1", "rm": "0", "size": "487M", "ro": "0", "type": "part", "mountpoint": null},
+# {"name": "/dev/sda2", "maj:min": "8:2", "rm": "0", "size": "1.9G", "ro": "0", "type": "part", "mountpoint": null},
+# {"name": "/dev/sda3", "maj:min": "8:3", "rm": "0", "size": "125.6G", "ro": "0", "type": "part", "mountpoint": "/etc/hosts"}
+# ]
+# },
+# {"name": "/dev/sdb", "maj:min": "8:16", "rm": "0", "size": "1T", "ro": "0", "type": "disk", "mountpoint": null,
+# "children": [
+# {"name": "/dev/sdb2", "maj:min": "8:18", "rm": "0", "size": "48G", "ro": "0", "type": "part", "mountpoint": null},
+# {"name": "/dev/sdb3", "maj:min": "8:19", "rm": "0", "size": "6M", "ro": "0", "type": "part", "mountpoint": null}
+# ]
+# },
+# {"name": "/dev/sdc", "maj:min": "8:32", "rm": "0", "size": "1T", "ro": "0", "type": "disk", "mountpoint": null},
+# {"name": "/dev/sdd", "maj:min": "8:48", "rm": "0", "size": "1T", "ro": "0", "type": "disk", "mountpoint": null,
+# "children": [
+# {"name": "/dev/mapper/ceph--osd--block--1", "maj:min": "253:0", "rm": "0", "size": "512G", "ro": "0", "type": "lvm", "mountpoint": null},
+# {"name": "/dev/mapper/ceph--osd--block--2", "maj:min": "253:1", "rm": "0", "size": "512G", "ro": "0", "type": "lvm", "mountpoint": null}
+# ]
+# }
+# ]
+# }
+
+def _devices_side_effect():
+ return {
+ "/dev/sda": {},
+ "/dev/sda1": {},
+ "/dev/sda2": {},
+ "/dev/sda3": {},
+ "/dev/sdb": {},
+ "/dev/sdb2": {},
+ "/dev/sdb3": {},
+ "/dev/sdc": {},
+ "/dev/sdd": {},
+ "/dev/mapper/ceph--osd--block--1": {},
+ "/dev/mapper/ceph--osd--block--2": {},
+ }
+
+def _lsblk_list_output():
+ return [
+ '/dev/sda',
+ '/dev/sda1',
+ '/dev/sda2',
+ '/dev/sda3',
+ '/dev/sdb',
+ '/dev/sdb2',
+ '/dev/sdb3',
+ '/dev/sdc',
+ '/dev/sdd',
+ '/dev/mapper/ceph--osd--block--1',
+ '/dev/mapper/ceph--osd--block--2',
+ ]
+
+# dummy lsblk output for device with optional parent output
+def _lsblk_output(dev, parent=None):
+ if parent is None:
+ parent = ""
+ ret = 'NAME="{}" KNAME="{}" PKNAME="{}"'.format(dev, dev, parent)
+ return [ret] # needs to be in a list form
+
+def _bluestore_tool_label_output_sdb():
+ return '''{
+ "/dev/sdb": {
+ "osd_uuid": "sdb-uuid",
+ "size": 1099511627776,
+ "btime": "2021-07-23T16:02:22.809186+0000",
+ "description": "main",
+ "bfm_blocks": "268435456",
+ "bfm_blocks_per_key": "128",
+ "bfm_bytes_per_block": "4096",
+ "bfm_size": "1099511627776",
+ "bluefs": "1",
+ "ceph_fsid": "sdb-fsid",
+ "kv_backend": "rocksdb",
+ "magic": "ceph osd volume v026",
+ "mkfs_done": "yes",
+ "osd_key": "AQAO6PpgK+y4CBAAixq/X7OVimbaezvwD/cDmg==",
+ "ready": "ready",
+ "require_osd_release": "16",
+ "whoami": "0"
+ }
+}'''
+
+def _bluestore_tool_label_output_sdb2():
+ return '''{
+ "/dev/sdb2": {
+ "osd_uuid": "sdb2-uuid",
+ "size": 1099511627776,
+ "btime": "2021-07-23T16:02:22.809186+0000",
+ "description": "main",
+ "bfm_blocks": "268435456",
+ "bfm_blocks_per_key": "128",
+ "bfm_bytes_per_block": "4096",
+ "bfm_size": "1099511627776",
+ "bluefs": "1",
+ "ceph_fsid": "sdb2-fsid",
+ "kv_backend": "rocksdb",
+ "magic": "ceph osd volume v026",
+ "mkfs_done": "yes",
+ "osd_key": "AQAO6PpgK+y4CBAAixq/X7OVimbaezvwD/cDmg==",
+ "ready": "ready",
+ "require_osd_release": "16",
+ "whoami": "2"
+ }
+}'''
+
+def _bluestore_tool_label_output_dm_okay():
+ return '''{
+ "/dev/mapper/ceph--osd--block--1": {
+ "osd_uuid": "lvm-1-uuid",
+ "size": 549751619584,
+ "btime": "2021-07-23T16:04:37.881060+0000",
+ "description": "main",
+ "bfm_blocks": "134216704",
+ "bfm_blocks_per_key": "128",
+ "bfm_bytes_per_block": "4096",
+ "bfm_size": "549751619584",
+ "bluefs": "1",
+ "ceph_fsid": "lvm-1-fsid",
+ "kv_backend": "rocksdb",
+ "magic": "ceph osd volume v026",
+ "mkfs_done": "yes",
+ "osd_key": "AQCU6Ppgz+UcIRAAh6IUjtPjiXBlEXfwO8ixzw==",
+ "ready": "ready",
+ "require_osd_release": "16",
+ "whoami": "2"
+ }
+}'''
+
+def _process_call_side_effect(command, **kw):
+ if "lsblk" in command:
+ if "/dev/" in command[-1]:
+ dev = command[-1]
+ if dev == "/dev/sda1" or dev == "/dev/sda2" or dev == "/dev/sda3":
+ return _lsblk_output(dev, parent="/dev/sda"), '', 0
+ if dev == "/dev/sdb2" or dev == "/dev/sdb3":
+ return _lsblk_output(dev, parent="/dev/sdb"), '', 0
+ if dev == "/dev/sda" or dev == "/dev/sdb" or dev == "/dev/sdc" or dev == "/dev/sdd":
+ return _lsblk_output(dev), '', 0
+ if "mapper" in dev:
+ return _lsblk_output(dev, parent="/dev/sdd"), '', 0
+ pytest.fail('dev {} needs behavior specified for it'.format(dev))
+ if "/dev/" not in command:
+ return _lsblk_list_output(), '', 0
+ pytest.fail('command {} needs behavior specified for it'.format(command))
+
+ if "ceph-bluestore-tool" in command:
+ if "/dev/sdb" in command:
+ # sdb is a bluestore OSD
+ return _bluestore_tool_label_output_sdb(), '', 0
+ if "/dev/sdb2" in command:
+ # sdb2 is a phantom atari partition that appears to have some valid bluestore info
+ return _bluestore_tool_label_output_sdb2(), '', 0
+ if "/dev/mapper/ceph--osd--block--1" in command:
+ # dm device 1 is a valid bluestore OSD (the other is corrupted/invalid)
+ return _bluestore_tool_label_output_dm_okay(), '', 0
+ # sda and children, sdb's children, sdc, sdd, dm device 2 all do NOT have bluestore OSD data
+ return [], 'fake No such file or directory error', 1
+ pytest.fail('command {} needs behavior specified for it'.format(command))
+
+def _has_bluestore_label_side_effect(disk_path):
+ if "/dev/sda" in disk_path:
+ return False # disk and all children are for the OS
+ if disk_path == "/dev/sdb":
+ return True # sdb is a valid bluestore OSD
+ if disk_path == "/dev/sdb2":
+ return True # sdb2 appears to be a valid bluestore OSD even though it should not be
+ if disk_path == "/dev/sdc":
+ return False # empty disk
+ if disk_path == "/dev/sdd":
+ return False # has LVM subdevices
+ if disk_path == "/dev/mapper/ceph--osd--block--1":
+ return True # good OSD
+ if disk_path == "/dev/mapper/ceph--osd--block--2":
+ return False # corrupted
+ pytest.fail('device {} needs behavior specified for it'.format(disk_path))
+
+class TestList(object):
+
+ @patch('ceph_volume.util.device.disk.get_devices')
+ @patch('ceph_volume.util.disk.has_bluestore_label')
+ @patch('ceph_volume.process.call')
+ def test_raw_list(self, patched_call, patched_bluestore_label, patched_get_devices):
+ raw.list.logger.setLevel("DEBUG")
+ patched_call.side_effect = _process_call_side_effect
+ patched_bluestore_label.side_effect = _has_bluestore_label_side_effect
+ patched_get_devices.side_effect = _devices_side_effect
+
+ result = raw.list.List([]).generate()
+ patched_call.assert_any_call(['lsblk', '--paths', '--output=NAME', '--noheadings', '--list'])
+ assert len(result) == 2
+
+ sdb = result['sdb-uuid']
+ assert sdb['osd_uuid'] == 'sdb-uuid'
+ assert sdb['osd_id'] == 0
+ assert sdb['device'] == '/dev/sdb'
+ assert sdb['ceph_fsid'] == 'sdb-fsid'
+ assert sdb['type'] == 'bluestore'
+
+ lvm1 = result['lvm-1-uuid']
+ assert lvm1['osd_uuid'] == 'lvm-1-uuid'
+ assert lvm1['osd_id'] == 2
+ assert lvm1['device'] == '/dev/mapper/ceph--osd--block--1'
+ assert lvm1['ceph_fsid'] == 'lvm-1-fsid'
+ assert lvm1['type'] == 'bluestore'
+
+ @patch('ceph_volume.util.device.disk.get_devices')
+ @patch('ceph_volume.util.disk.has_bluestore_label')
+ @patch('ceph_volume.process.call')
+ def test_raw_list_with_OSError(self, patched_call, patched_bluestore_label, patched_get_devices):
+ def _has_bluestore_label_side_effect_with_OSError(device_path):
+ if device_path == "/dev/sdd":
+ raise OSError('fake OSError')
+ return _has_bluestore_label_side_effect(device_path)
+
+ raw.list.logger.setLevel("DEBUG")
+ patched_call.side_effect = _process_call_side_effect
+ patched_bluestore_label.side_effect = _has_bluestore_label_side_effect_with_OSError
+ patched_get_devices.side_effect = _devices_side_effect
+
+ result = raw.list.List([]).generate()
+ assert len(result) == 1
+ assert 'sdb-uuid' in result
- hosts: mons
become: yes
tasks:
+ - name: mark osds down
+ command: "ceph --cluster {{ cluster }} osd down osd.{{ item }}"
+ with_items:
+ - 0
+ - 2
- name: destroy osd.2
command: "ceph --cluster {{ cluster }} osd destroy osd.2 --yes-i-really-mean-it"
+ register: result
+ retries: 30
+ delay: 1
+ until: result is succeeded
- name: destroy osd.0
command: "ceph --cluster {{ cluster }} osd destroy osd.0 --yes-i-really-mean-it"
+ register: result
+ retries: 30
+ delay: 1
+ until: result is succeeded
- hosts: osds
become: yes
- hosts: mons
become: yes
tasks:
+ - name: mark osds down
+ command: "ceph --cluster {{ cluster }} osd down osd.0"
- name: destroy osd.0
command: "ceph --cluster {{ cluster }} osd destroy osd.0 --yes-i-really-mean-it"
+ register: result
+ retries: 30
+ delay: 1
+ until: result is succeeded
- hosts: osds
- hosts: mons
become: yes
tasks:
+ - name: mark osds down
+ command: "ceph --cluster {{ cluster }} osd down osd.{{ item }}"
+ with_items:
+ - 0
+ - 2
- name: destroy osd.2
command: "ceph --cluster {{ cluster }} osd destroy osd.2 --yes-i-really-mean-it"
+ register: result
+ retries: 30
+ delay: 1
+ until: result is succeeded
- name: destroy osd.0
command: "ceph --cluster {{ cluster }} osd destroy osd.0 --yes-i-really-mean-it"
-
+ register: result
+ retries: 30
+ delay: 1
+ until: result is succeeded
- hosts: osds
become: yes
- hosts: mons
become: yes
tasks:
+ - name: mark osds down
+ command: "ceph --cluster {{ cluster }} osd down osd.{{ item }}"
+ with_items:
+ - 0
+ - 2
- name: destroy osd.2
command: "ceph --cluster {{ cluster }} osd destroy osd.2 --yes-i-really-mean-it"
+ register: result
+ retries: 30
+ delay: 1
+ until: result is succeeded
- name: destroy osd.0
command: "ceph --cluster {{ cluster }} osd destroy osd.0 --yes-i-really-mean-it"
+ register: result
+ retries: 30
+ delay: 1
+ until: result is succeeded
- hosts: osds
- hosts: mons
become: yes
tasks:
+ - name: mark osds down
+ command: "ceph --cluster {{ cluster }} osd down osd.{{ item }}"
+ with_items:
+ - 0
+ - 2
- name: destroy osd.2
command: "ceph --cluster {{ cluster }} osd destroy osd.2 --yes-i-really-mean-it"
+ register: result
+ retries: 30
+ delay: 1
+ until: result is succeeded
- name: destroy osd.0
command: "ceph --cluster {{ cluster }} osd destroy osd.0 --yes-i-really-mean-it"
-
+ register: result
+ retries: 30
+ delay: 1
+ until: result is succeeded
- hosts: osds
become: yes
def setup(self):
self.validator = arg_validators.ValidDevice()
- def test_path_is_valid(self, fake_call):
+ def test_path_is_valid(self, fake_call, patch_bluestore_label):
result = self.validator('/')
assert result.abspath == '/'
- def test_path_is_invalid(self, fake_call):
+ def test_path_is_invalid(self, fake_call, patch_bluestore_label):
with pytest.raises(argparse.ArgumentError):
self.validator('/device/does/not/exist')
from copy import deepcopy
from ceph_volume.util import device
from ceph_volume.api import lvm as api
+from mock.mock import patch, mock_open
class TestDevice(object):
def test_is_partition(self, device_info):
data = {"/dev/sda1": {"foo": "bar"}}
- lsblk = {"TYPE": "part"}
+ lsblk = {"TYPE": "part", "PKNAME": "sda"}
device_info(devices=data, lsblk=lsblk)
disk = device.Device("/dev/sda1")
assert disk.is_partition
def test_is_not_lvm_memeber(self, device_info):
data = {"/dev/sda1": {"foo": "bar"}}
- lsblk = {"TYPE": "part"}
+ lsblk = {"TYPE": "part", "PKNAME": "sda"}
device_info(devices=data, lsblk=lsblk)
disk = device.Device("/dev/sda1")
assert not disk.is_lvm_member
def test_is_lvm_memeber(self, device_info):
data = {"/dev/sda1": {"foo": "bar"}}
- lsblk = {"TYPE": "part"}
+ lsblk = {"TYPE": "part", "PKNAME": "sda"}
device_info(devices=data, lsblk=lsblk)
disk = device.Device("/dev/sda1")
assert not disk.is_lvm_member
assert not disk.available
assert "Has BlueStore device label" in disk.rejected_reasons
+ def test_reject_device_with_oserror(self, monkeypatch, patch_bluestore_label, device_info):
+ patch_bluestore_label.side_effect = OSError('test failure')
+ lsblk = {"TYPE": "disk"}
+ device_info(lsblk=lsblk)
+ disk = device.Device("/dev/sda")
+ assert not disk.available
+ assert "Failed to determine if device is BlueStore" in disk.rejected_reasons
+
@pytest.mark.usefixtures("device_info_not_ceph_disk_member",
"disable_kernel_queries")
def test_is_not_ceph_disk_member_lsblk(self, patch_bluestore_label):
def test_used_by_ceph(self, device_info,
monkeypatch, ceph_type):
data = {"/dev/sda": {"foo": "bar"}}
- lsblk = {"TYPE": "part"}
+ lsblk = {"TYPE": "part", "PKNAME": "sda"}
FooPVolume = api.PVolume(pv_name='/dev/sda', pv_uuid="0000",
lv_uuid="0000", pv_tags={}, vg_name="vg")
pvolumes = []
pvolumes = []
pvolumes.append(FooPVolume)
data = {"/dev/sda": {"foo": "bar"}}
- lsblk = {"TYPE": "part"}
+ lsblk = {"TYPE": "part", "PKNAME": "sda"}
lv_data = {"lv_path": "vg/lv", "vg_name": "vg", "lv_uuid": "0000", "tags": {"ceph.osd_id": 0, "ceph.type": "journal"}}
monkeypatch.setattr(api, 'get_pvs', lambda **kwargs: pvolumes)
disk = device.Device("/dev/sda")
assert disk._get_device_id() == 'ID_VENDOR_ID_MODEL_ID_SCSI_SERIAL'
+ def test_has_bluestore_label(self):
+ # patch device.Device __init__ function to do nothing since we want to only test the
+ # low-level behavior of has_bluestore_label
+ with patch.object(device.Device, "__init__", lambda self, path, with_lsm=False: None):
+ disk = device.Device("/dev/sda")
+ disk.abspath = "/dev/sda"
+ with patch('builtins.open', mock_open(read_data=b'bluestore block device\n')):
+ assert disk.has_bluestore_label
+ with patch('builtins.open', mock_open(read_data=b'not a bluestore block device\n')):
+ assert not disk.has_bluestore_label
class TestDeviceEncryption(object):
def test_partition_is_not_encrypted_lsblk(self, device_info):
- lsblk = {'TYPE': 'part', 'FSTYPE': 'xfs'}
+ lsblk = {'TYPE': 'part', 'FSTYPE': 'xfs', 'PKNAME': 'sda'}
device_info(lsblk=lsblk)
disk = device.Device("/dev/sda")
assert disk.is_encrypted is False
def test_partition_is_encrypted_lsblk(self, device_info):
- lsblk = {'TYPE': 'part', 'FSTYPE': 'crypto_LUKS'}
+ lsblk = {'TYPE': 'part', 'FSTYPE': 'crypto_LUKS', 'PKNAME': 'sda'}
device_info(lsblk=lsblk)
disk = device.Device("/dev/sda")
assert disk.is_encrypted is True
def test_partition_is_not_encrypted_blkid(self, device_info):
- lsblk = {'TYPE': 'part'}
+ lsblk = {'TYPE': 'part', 'PKNAME': 'sda'}
blkid = {'TYPE': 'ceph data'}
device_info(lsblk=lsblk, blkid=blkid)
disk = device.Device("/dev/sda")
assert disk.is_encrypted is False
def test_partition_is_encrypted_blkid(self, device_info):
- lsblk = {'TYPE': 'part'}
+ lsblk = {'TYPE': 'part', 'PKNAME': 'sda'}
blkid = {'TYPE': 'crypto_LUKS'}
device_info(lsblk=lsblk, blkid=blkid)
disk = device.Device("/dev/sda")
# -*- coding: utf-8 -*-
+import logging
import os
from functools import total_ordering
-from ceph_volume import sys_info, process
+from ceph_volume import sys_info
from ceph_volume.api import lvm
from ceph_volume.util import disk, system
from ceph_volume.util.lsmdisk import LSMDisk
from ceph_volume.util.constants import ceph_disk_guids
+
+logger = logging.getLogger(__name__)
+
+
report_template = """
{dev:<25} {size:<12} {rot!s:<7} {available!s:<9} {model}"""
def size(self):
return self.sys_api['size']
+ @property
+ def parent_device(self):
+ if 'PKNAME' in self.disk_api:
+ return '/dev/%s' % self.disk_api['PKNAME']
+ return None
+
@property
def lvm_size(self):
"""
@property
def has_bluestore_label(self):
- out, err, ret = process.call([
- 'ceph-bluestore-tool', 'show-label',
- '--dev', self.abspath], verbose_on_failure=False)
- if ret:
- return False
- return True
+ return disk.has_bluestore_label(self.abspath)
@property
def is_mapper(self):
rejected.append("Device type is not acceptable. It should be raw device or partition")
if self.is_ceph_disk_member:
rejected.append("Used by ceph-disk")
- if self.has_bluestore_label:
- rejected.append('Has BlueStore device label')
+
+ try:
+ if self.has_bluestore_label:
+ rejected.append('Has BlueStore device label')
+ except OSError as e:
+ # likely failed to open the device. assuming it is BlueStore is the safest option
+ # so that a possibly-already-existing OSD doesn't get overwritten
+ logger.error('failed to determine if device {} is BlueStore. device should not be used to avoid false negatives. err: {}'.format(self.abspath, e))
+ rejected.append('Failed to determine if device is BlueStore')
+
+ if self.is_partition:
+ try:
+ if disk.has_bluestore_label(self.parent_device):
+ rejected.append('Parent has BlueStore device label')
+ except OSError as e:
+ # likely failed to open the device. assuming the parent is BlueStore is the safest
+ # option so that a possibly-already-existing OSD doesn't get overwritten
+ logger.error('failed to determine if partition {} (parent: {}) has a BlueStore parent. partition should not be used to avoid false negatives. err: {}'.format(self.abspath, self.parent_device, e))
+ rejected.append('Failed to determine if parent device is BlueStore')
+
+ if self.has_gpt_headers:
+ rejected.append('Has GPT headers')
return rejected
def _check_lvm_reject_reasons(self):
device
"""
out, err, rc = process.call(
- ['blkid', '-s', 'PARTUUID', '-o', 'value', device]
+ ['blkid', '-c', '/dev/null', '-s', 'PARTUUID', '-o', 'value', device]
)
return ' '.join(out).strip()
PART_ENTRY_UUID PARTUUID
"""
out, err, rc = process.call(
- ['blkid', '-p', device]
+ ['blkid', '-c', '/dev/null', '-p', device]
)
return _blkid_parser(' '.join(out))
used for udev rules, but it is useful in this case as it is the only
consistent way to retrieve the GUID used by ceph-disk to identify devices.
"""
- out, err, rc = process.call(['blkid', '-p', '-o', 'udev', device])
+ out, err, rc = process.call(['blkid', '-c', '/dev/null', '-p', '-o', 'udev', device])
for line in out:
if 'ID_PART_ENTRY_TYPE=' in line:
return line.split('=')[-1].strip()
device is
"""
out, err, rc = process.call(
- ['blkid', '-t', 'PARTUUID="%s"' % partuuid, '-o', 'device']
+ ['blkid', '-c', '/dev/null', '-t', 'PARTUUID="%s"' % partuuid, '-o', 'device']
)
return ' '.join(out).strip()
:param device: A ``Device()`` object
"""
- parent_device = '/dev/%s' % device.disk_api['PKNAME']
udev_info = udevadm_property(device.abspath)
partition_number = udev_info.get('ID_PART_ENTRY_NUMBER')
if not partition_number:
raise RuntimeError('Unable to detect the partition number for device: %s' % device.abspath)
process.run(
- ['parted', parent_device, '--script', '--', 'rm', partition_number]
+ ['parted', device.parent_device, '--script', '--', 'rm', partition_number]
)
device_facts[diskname] = metadata
return device_facts
+
+def has_bluestore_label(device_path):
+ isBluestore = False
+ bluestoreDiskSignature = 'bluestore block device' # 22 bytes long
+
+ # throws OSError on failure
+ logger.info("opening device {} to check for BlueStore label".format(device_path))
+ with open(device_path, "rb") as fd:
+ # read first 22 bytes looking for bluestore disk signature
+ signature = fd.read(22)
+ if signature.decode('ascii', 'replace') == bluestoreDiskSignature:
+ isBluestore = True
+
+ return isBluestore
#!/usr/bin/python3
-DEFAULT_IMAGE='docker.io/ceph/ceph:v15'
-DEFAULT_IMAGE_IS_MASTER=False
+# Default container images -----------------------------------------------------
+DEFAULT_IMAGE = 'quay.io/ceph/ceph:v15'
+DEFAULT_IMAGE_IS_MASTER = False
+DEFAULT_PROMETHEUS_IMAGE = 'quay.io/prometheus/prometheus:v2.18.1'
+DEFAULT_NODE_EXPORTER_IMAGE = 'quay.io/prometheus/node-exporter:v0.18.1'
+DEFAULT_ALERT_MANAGER_IMAGE = 'quay.io/prometheus/alertmanager:v0.20.0'
+DEFAULT_GRAFANA_IMAGE = 'quay.io/ceph/ceph-grafana:6.7.4'
+# ------------------------------------------------------------------------------
+
LATEST_STABLE_RELEASE = 'octopus'
DATA_DIR = '/var/lib/ceph'
LOG_DIR = '/var/log/ceph'
'disable_existing_loggers': True,
'formatters': {
'cephadm': {
- 'format': '%(asctime)s %(levelname)s %(message)s'
+ 'format': '%(asctime)s %(thread)x %(levelname)s %(message)s'
},
},
'handlers': {
components = {
"prometheus": {
- "image": "docker.io/prom/prometheus:v2.18.1",
+ "image": DEFAULT_PROMETHEUS_IMAGE,
"cpus": '2',
"memory": '4GB',
"args": [
],
},
"node-exporter": {
- "image": "docker.io/prom/node-exporter:v0.18.1",
+ "image": DEFAULT_NODE_EXPORTER_IMAGE,
"cpus": "1",
"memory": "1GB",
"args": [
],
},
"grafana": {
- "image": "docker.io/ceph/ceph-grafana:6.7.4",
+ "image": DEFAULT_GRAFANA_IMAGE,
"cpus": "2",
"memory": "4GB",
"args": [],
],
},
"alertmanager": {
- "image": "docker.io/prom/alertmanager:v0.20.0",
+ "image": DEFAULT_ALERT_MANAGER_IMAGE,
"cpus": "2",
"memory": "2GB",
"args": [
public:
BIVerObjEntry(cls_method_context_t& _hctx, const cls_rgw_obj_key& _key) : hctx(_hctx), key(_key), initialized(false) {
+ // empty
}
int init(bool check_delete_marker = true) {
return -EINVAL;
}
- BIVerObjEntry obj(hctx, op.key);
- BIOLHEntry olh(hctx, op.key);
-
/* read instance entry */
+ BIVerObjEntry obj(hctx, op.key);
int ret = obj.init(op.delete_marker);
+
+ /* NOTE: When a delete is issued, a key instance is always provided,
+ * either the one for which the delete is requested or a new random
+ * one when no instance is specified. So we need to see which of
+ * these two cases we're dealing with. The variable `existed` will
+ * be true if the instance was specified and false if it was
+ * randomly generated. It might have been cleaner if the instance
+ * were empty and randomly generated here and returned in the reply,
+ * as that would better allow a typo in the instance id. This code
+ * should be audited and possibly cleaned up. */
+
bool existed = (ret == 0);
if (ret == -ENOENT && op.delete_marker) {
ret = 0;
return ret;
}
+ BIOLHEntry olh(hctx, op.key);
+ bool olh_read_attempt = false;
+ bool olh_found = false;
+ if (!existed && op.delete_marker) {
+ /* read olh */
+ ret = olh.init(&olh_found);
+ if (ret < 0) {
+ return ret;
+ }
+ olh_read_attempt = true;
+
+ // if we're deleting (i.e., adding a delete marker, and the OLH
+ // indicates it already refers to a delete marker, error out)
+ if (olh_found && olh.get_entry().delete_marker) {
+ CLS_LOG(10,
+ "%s: delete marker received for \"%s\" although OLH"
+ " already refers to a delete marker\n",
+ __func__, escape_str(op.key.to_string()).c_str());
+ return -ENOENT;
+ }
+ }
+
if (existed && !real_clock::is_zero(op.unmod_since)) {
timespec mtime = ceph::real_clock::to_timespec(obj.mtime());
timespec unmod = ceph::real_clock::to_timespec(op.unmod_since);
}
/* read olh */
- bool olh_found;
- ret = olh.init(&olh_found);
- if (ret < 0) {
- return ret;
+ if (!olh_read_attempt) { // only read if we didn't attempt earlier
+ ret = olh.init(&olh_found);
+ if (ret < 0) {
+ return ret;
+ }
+ olh_read_attempt = true;
}
+
const uint64_t prev_epoch = olh.get_epoch();
if (!olh.start_modify(op.olh_epoch)) {
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
-#ifndef CEPH_CLS_RGW_TYPES_H
-#define CEPH_CLS_RGW_TYPES_H
+#pragma once
#include <boost/container/flat_map.hpp>
#include "common/ceph_time.h"
#include "common/Formatter.h"
+#undef FMT_HEADER_ONLY
+#define FMT_HEADER_ONLY 1
+#include <fmt/format.h>
+
#include "rgw/rgw_basic_types.h"
#define CEPH_RGW_REMOVE 'r'
cls_rgw_obj_key(const string &_name) : name(_name) {}
cls_rgw_obj_key(const string& n, const string& i) : name(n), instance(i) {}
- void set(const string& _name) {
+ std::string to_string() const {
+ return fmt::format("{}({})", name, instance);
+ }
+
+ bool empty() const {
+ return name.empty();
+ }
+
+ void set(const std::string& _name) {
name = _name;
}
return (name.compare(k.name) == 0) &&
(instance.compare(k.instance) == 0);
}
+
bool operator<(const cls_rgw_obj_key& k) const {
int r = name.compare(k.name);
if (r == 0) {
}
return (r < 0);
}
+
bool operator<=(const cls_rgw_obj_key& k) const {
return !(k < *this);
}
- bool empty() const {
- return name.empty();
+
+ std::ostream& operator<<(std::ostream& out) const {
+ out << to_string();
+ return out;
}
- void encode(bufferlist &bl) const {
+
+ void encode(ceph::buffer::list &bl) const {
ENCODE_START(1, 1, bl);
encode(name, bl);
encode(instance, bl);
void get_key(string *key) const;
};
WRITE_CLASS_ENCODER(cls_rgw_reshard_entry)
-
-#endif
buffer::create_aligned(unaligned._len, align_memory)));
had_to_rebuild = true;
}
- _buffers.insert_after(p_prev, *ptr_node::create(unaligned._buffers.front()).release());
- _num += 1;
+ if (unaligned.get_num_buffers()) {
+ _buffers.insert_after(p_prev, *ptr_node::create(unaligned._buffers.front()).release());
+ _num += 1;
+ } else {
+ // a bufferlist containing only 0-length bptrs is rebuilt as empty
+ }
++p_prev;
}
return had_to_rebuild;
OPTION(rgw_data_log_obj_prefix, OPT_STR) //
OPTION(rgw_bucket_quota_ttl, OPT_INT) // time for cached bucket stats to be cached within rgw instance
-OPTION(rgw_bucket_quota_soft_threshold, OPT_DOUBLE) // threshold from which we don't rely on cached info for quota decisions
OPTION(rgw_bucket_quota_cache_size, OPT_INT) // number of entries in bucket quota cache
OPTION(rgw_bucket_default_quota_max_objects, OPT_INT) // number of objects allowed
OPTION(rgw_bucket_default_quota_max_size, OPT_LONGLONG) // Max size of object in bytes
.set_long_description("If this value is exceeded, the OSD will not read any new client data off of the network until memory is freed."),
Option("osd_client_message_cap", Option::TYPE_UINT, Option::LEVEL_ADVANCED)
- .set_default(0)
+ .set_default(256)
.set_description("maximum number of in-flight client requests"),
Option("osd_crush_update_weight_set", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
.set_long_description(
"Length of time for bucket stats to be cached within RGW instance."),
- Option("rgw_bucket_quota_soft_threshold", Option::TYPE_FLOAT, Option::LEVEL_BASIC)
- .set_default(0.95)
- .set_description("RGW quota soft threshold")
- .set_long_description(
- "Threshold from which RGW doesn't rely on cached info for quota "
- "decisions. This is done for higher accuracy of the quota mechanism at "
- "cost of performance, when getting close to the quota limit. The value "
- "configured here is the ratio between the data usage to the max usage "
- "as specified by the quota."),
-
Option("rgw_bucket_quota_cache_size", Option::TYPE_INT, Option::LEVEL_ADVANCED)
.set_default(10000)
.set_description("RGW quota stats cache size")
#include <memory>
#include <optional>
#include <poll.h>
+#include <regex>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
return 0;
}
+// wrap any of * ? [ between square brackets
+static std::string escape_glob(const std::string& s)
+{
+ std::regex glob_meta("([*?[])");
+ return std::regex_replace(s, glob_meta, "[$1]");
+}
+
static int __enumerate_devices(struct udev *udev, const krbd_spec& spec,
bool match_nspace, udev_enumerate_uptr *penm)
{
return r;
r = udev_enumerate_add_match_sysattr(enm.get(), "pool",
- spec.pool_name.c_str());
+ escape_glob(spec.pool_name).c_str());
if (r < 0)
return r;
if (match_nspace) {
r = udev_enumerate_add_match_sysattr(enm.get(), "pool_ns",
- spec.nspace_name.c_str());
+ escape_glob(spec.nspace_name).c_str());
} else {
/*
* Match _only_ devices that don't have pool_ns attribute.
return r;
r = udev_enumerate_add_match_sysattr(enm.get(), "name",
- spec.image_name.c_str());
+ escape_glob(spec.image_name).c_str());
if (r < 0)
return r;
r = udev_enumerate_add_match_sysattr(enm.get(), "current_snap",
- spec.snap_name.c_str());
+ escape_glob(spec.snap_name).c_str());
if (r < 0)
return r;
r = ceph_resolve_file_search(g_conf()->keyring, keyring_filename);
if (r) {
- derr << "unable to find a keyring file on " << g_conf()->keyring
- << ": " << cpp_strerror(r) << dendl;
if (g_conf()->key != "") {
string keyring_plaintext = "[mon.]\n\tkey = " + g_conf()->key +
"\n\tcaps mon = \"allow *\"\n";
return -EINVAL;
}
} else {
- return -ENOENT;
+ derr << "unable to find a keyring on " << g_conf()->keyring
+ << ": " << cpp_strerror(r) << dendl;
+ return r;
}
} else {
r = keyring.load(g_ceph_context, keyring_filename);
} // anonymous namespace
-void LastEpochClean::Lec::report(ps_t ps, epoch_t last_epoch_clean)
+void LastEpochClean::Lec::report(unsigned pg_num, ps_t ps,
+ epoch_t last_epoch_clean)
{
- if (epoch_by_pg.size() <= ps) {
- epoch_by_pg.resize(ps + 1, 0);
+ if (ps >= pg_num) {
+ // removed PG
+ return;
}
+ epoch_by_pg.resize(pg_num, 0);
const auto old_lec = epoch_by_pg[ps];
if (old_lec >= last_epoch_clean) {
// stale lec
report_by_pool.erase(pool);
}
-void LastEpochClean::report(const pg_t& pg, epoch_t last_epoch_clean)
+void LastEpochClean::report(unsigned pg_num, const pg_t& pg,
+ epoch_t last_epoch_clean)
{
auto& lec = report_by_pool[pg.pool()];
- return lec.report(pg.ps(), last_epoch_clean);
+ return lec.report(pg_num, pg.ps(), last_epoch_clean);
}
epoch_t LastEpochClean::get_lower_bound(const OSDMap& latest) const
osd_epochs[from] = beacon->version;
for (const auto& pg : beacon->pgs) {
- last_epoch_clean.report(pg, beacon->min_last_epoch_clean);
+ if (auto* pool = osdmap.get_pg_pool(pg.pool()); pool != nullptr) {
+ unsigned pg_num = pool->get_pg_num();
+ last_epoch_clean.report(pg_num, pg, beacon->min_last_epoch_clean);
+ }
}
if (osdmap.osd_xinfo[from].last_purged_snaps_scrub <
}
} else /* var != "all" */ {
choices_map_t::const_iterator found = ALL_CHOICES.find(var);
+ if (found == ALL_CHOICES.end()) {
+ ss << "pool '" << poolstr
+ << "': invalid variable: '" << var << "'";
+ r = -EINVAL;
+ goto reply;
+ }
+
osd_pool_get_choices selected = found->second;
if (!p->is_tier() &&
vector<epoch_t> epoch_by_pg;
ps_t next_missing = 0;
epoch_t floor = std::numeric_limits<epoch_t>::max();
- void report(ps_t pg, epoch_t last_epoch_clean);
+ void report(unsigned pg_num, ps_t pg, epoch_t last_epoch_clean);
};
std::map<uint64_t, Lec> report_by_pool;
public:
- void report(const pg_t& pg, epoch_t last_epoch_clean);
+ void report(unsigned pg_num, const pg_t& pg, epoch_t last_epoch_clean);
void remove_pool(uint64_t pool);
epoch_t get_lower_bound(const OSDMap& latest) const;
if (verbose) {
f->dump_int("quota_objects", pool->quota_max_objects);
f->dump_int("quota_bytes", pool->quota_max_bytes);
- f->dump_int("dirty", sum.num_objects_dirty);
+ if (pool->is_tier()) {
+ f->dump_int("dirty", sum.num_objects_dirty);
+ } else {
+ f->dump_int("dirty", 0);
+ }
f->dump_int("rd", sum.num_rd);
f->dump_int("rd_bytes", sum.num_rd_kb * 1024ull);
f->dump_int("wr", sum.num_wr);
tbl << "N/A";
else
tbl << stringify(si_u_t(pool->quota_max_objects));
-
if (pool->quota_max_bytes == 0)
tbl << "N/A";
else
tbl << stringify(byte_u_t(pool->quota_max_bytes));
-
- tbl << stringify(si_u_t(sum.num_objects_dirty))
- << stringify(byte_u_t(statfs.data_compressed_allocated))
- << stringify(byte_u_t(statfs.data_compressed_original))
- ;
+ if (pool->is_tier()) {
+ tbl << stringify(si_u_t(sum.num_objects_dirty));
+ } else {
+ tbl << "N/A";
+ }
+ tbl << stringify(byte_u_t(statfs.data_compressed_allocated));
+ tbl << stringify(byte_u_t(statfs.data_compressed_original));
}
}
}
bl.claim_append(t);
read_pos += r;
}
- seen_recs = true;
bluefs_transaction_t t;
try {
auto p = bl.cbegin();
decode(t, p);
- }
- catch (buffer::error& e) {
- derr << __func__ << " 0x" << std::hex << pos << std::dec
- << ": stop: failed to decode: " << e.what()
- << dendl;
- delete log_reader;
- return -EIO;
+ seen_recs = true;
+ }
+ catch (ceph::buffer::error& e) {
+ // Multi-block transactions might be incomplete due to unexpected
+ // power off. Hence let's treat that as a regular stop condition.
+ if (seen_recs && more) {
+ dout(10) << __func__ << " 0x" << std::hex << pos << std::dec
+ << ": stop: failed to decode: " << e.what()
+ << dendl;
+ } else {
+ derr << __func__ << " 0x" << std::hex << pos << std::dec
+ << ": stop: failed to decode: " << e.what()
+ << dendl;
+ delete log_reader;
+ return -EIO;
+ }
+ break;
}
ceph_assert(seq == t.seq);
dout(10) << __func__ << " 0x" << std::hex << pos << std::dec
return r;
}
}
+ } else if (noop && fnode.ino == 1) {
+ FileRef f = _get_file(fnode.ino);
+ f->fnode = fnode;
}
}
break;
}
}
}
+ // sanity
+ if (h->file->fnode.size >= (1ull << 30)) {
+ dout(10) << __func__ << " file is unexpectedly large:" << h->file->fnode << dendl;
+ }
delete h;
}
void OriginalVolumeSelector::get_paths(const std::string& base, paths& res) const
{
res.emplace_back(base, db_total);
- res.emplace_back(base + ".slow", slow_total);
+ res.emplace_back(base + ".slow",
+ slow_total ? slow_total : db_total); // use fake non-zero value if needed to
+ // avoid RocksDB complains
}
#undef dout_prefix
BlueFSVolumeSelector::paths paths;
bluefs->get_vselector_paths(fn, paths);
- if (bluefs_layout.shared_bdev == BlueFS::BDEV_SLOW) {
- // we have both block.db and block; tell rocksdb!
- // note: the second (last) size value doesn't really matter
+ {
ostringstream db_paths;
bool first = true;
for (auto& p : paths) {
<< "queue_transaction returned " << cpp_strerror(ret) << dendl;
goto umount_store;
}
+ ch->flush();
}
ret = write_meta(cct, store, sb.cluster_fsid, sb.osd_fsid, whoami, osdspec_affinity);
CEPH_TYPES = set(CEPH_UPGRADE_ORDER)
+# Default container images -----------------------------------------------------
+DEFAULT_IMAGE = 'quay.io/ceph/ceph'
+DEFAULT_PROMETHEUS_IMAGE = 'quay.io/prometheus/prometheus:v2.18.1'
+DEFAULT_NODE_EXPORTER_IMAGE = 'quay.io/prometheus/node-exporter:v0.18.1'
+DEFAULT_ALERT_MANAGER_IMAGE = 'quay.io/prometheus/alertmanager:v0.20.0'
+DEFAULT_GRAFANA_IMAGE = 'quay.io/ceph/ceph-grafana:6.7.4'
+# ------------------------------------------------------------------------------
+
class CephadmCompletion(orchestrator.Completion[T]):
def evaluate(self) -> None:
},
{
'name': 'container_image_base',
- 'default': 'docker.io/ceph/ceph',
+ 'default': DEFAULT_IMAGE,
'desc': 'Container image name, without the tag',
'runtime': True,
},
{
'name': 'container_image_prometheus',
- 'default': 'docker.io/prom/prometheus:v2.18.1',
+ 'default': DEFAULT_PROMETHEUS_IMAGE,
'desc': 'Prometheus container image',
},
{
'name': 'container_image_grafana',
- 'default': 'docker.io/ceph/ceph-grafana:6.7.4',
+ 'default': DEFAULT_GRAFANA_IMAGE,
'desc': 'Prometheus container image',
},
{
'name': 'container_image_alertmanager',
- 'default': 'docker.io/prom/alertmanager:v0.20.0',
+ 'default': DEFAULT_ALERT_MANAGER_IMAGE,
'desc': 'Prometheus container image',
},
{
'name': 'container_image_node_exporter',
- 'default': 'docker.io/prom/node-exporter:v0.18.1',
+ 'default': DEFAULT_NODE_EXPORTER_IMAGE,
'desc': 'Prometheus container image',
},
{
if not no_fsid:
final_args += ['--fsid', self._cluster_fsid]
+ if self.container_init and command == 'deploy':
+ final_args += ['--container-init']
+
final_args += args
# exec
permissions.
run-cephadm-e2e-tests.sh
-........................
+.........................
``run-cephadm-e2e-tests.sh`` runs a subset of E2E tests to verify that the Dashboard and cephadm as
Orchestrator backend behave correctly.
$ cd <your/ceph/repo/dir>
$ sudo chown -R $(id -un) src/pybind/mgr/dashboard/frontend/{dist,node_modules,src/environments}
$ ./src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh
- $ kcli delete plan -y ceph # After tests finish.
-You can also start a cluster in development mode and later run E2E tests by running::
+You can also start a cluster in development mode (so the frontend build starts in watch mode and you
+only have to reload the page for the changes to be reflected) by running::
$ ./src/pybind/mgr/dashboard/ci/cephadm/start-cluster.sh --dev-mode
- $ # Work on your feature, bug fix, ...
+
+Note:
+ Add ``--expanded`` if you need a cluster ready to deploy services (one with enough monitor
+ daemons spread across different hosts and enough OSDs).
+
+Test your changes by running:
+
$ ./src/pybind/mgr/dashboard/ci/cephadm/run-cephadm-e2e-tests.sh
- $ # Remember to kill the npm build watch process i.e.: pkill -f "ng build"
+
+Shutdown the cluster by running:
+
+ $ kcli delete plan -y ceph
+ $ # In development mode, also kill the npm build watch process (e.g., pkill -f "ng build")
Other running options
.....................
cephadm bootstrap --mon-ip $mon_ip --initial-dashboard-password {{ admin_password }} --allow-fqdn-hostname --dashboard-password-noupdate --shared_ceph_folder /mnt/{{ ceph_dev_folder }}
+fsid=$(cat /etc/ceph/ceph.conf | grep fsid | awk '{ print $3}')
+
{% for number in range(1, nodes) %}
ssh-copy-id -f -i /etc/ceph/ceph.pub -o StrictHostKeyChecking=no root@{{ prefix }}-node-0{{ number }}.{{ domain }}
+ {% if expanded_cluster is defined %}
+ cephadm shell --fsid $fsid -c /etc/ceph/ceph.conf -k /etc/ceph/ceph.client.admin.keyring ceph orch host add {{ prefix }}-node-0{{ number }}.{{ domain }}
+ {% endif %}
{% endfor %}
+
+{% if expanded_cluster is defined %}
+ cephadm shell --fsid $fsid -c /etc/ceph/ceph.conf -k /etc/ceph/ceph.client.admin.keyring ceph orch apply osd --all-available-devices
+{% endif %}
echo -n $ip
}
-if [[ -z "${CYPRESS_BASE_URL}" ]]; then
- CEPH_NODE_00_IP="$(get_vm_ip ceph-node-00)"
- if [[ -z "${CEPH_NODE_00_IP}" ]]; then
- . "$(dirname $0)"/start-cluster.sh
- fi
+if [[ -n "${JENKINS_HOME}" || (-z "${CYPRESS_BASE_URL}" && -z "$(get_vm_ip ceph-node-00)") ]]; then
+ . "$(dirname $0)"/start-cluster.sh
+
CYPRESS_BASE_URL="https://$(get_vm_ip ceph-node-00):${DASHBOARD_PORT}"
fi
local specs="$1"
local timeout="$2"
local override_config="ignoreTestFiles=*.po.ts,retries=0,testFiles=${specs}"
-
if [[ -n "$timeout" ]]; then
override_config="${override_config},defaultCommandTimeout=${timeout}"
fi
- npx cypress run ${CYPRESS_ARGS} --browser chrome --headless --config "$override_config"
+
+ rm -f cypress/reports/results-*.xml || true
+
+ npx --no-install cypress run ${CYPRESS_ARGS} --browser chrome --headless --config "$override_config"
}
: ${CEPH_DEV_FOLDER:=${PWD}}
if [ "$1" != "0" ]; then
printf "\n\nERROR $1 thrown on line $2\n\n"
printf "\n\nCollecting info...\n\n"
+ printf "\n\nDisplaying MGR logs:\n\n"
+ kcli ssh -u root -- ceph-node-00 'cephadm logs -n $(cephadm ls | grep -Eo "mgr\.ceph[0-9a-z.-]+" | head -n 1)'
for vm_id in 0 1 2
do
local vm="ceph-node-0${vm_id}"
for arg in "$@"; do
shift
case "$arg" in
- "--dev-mode") DEV_MODE='true'; EXTRA_PARAMS="-P dev_mode=${DEV_MODE}" ;;
+ "--dev-mode") DEV_MODE='true'; EXTRA_PARAMS+=" -P dev_mode=${DEV_MODE}" ;;
+ "--expanded") EXTRA_PARAMS+=" -P expanded_cluster=true" ;;
esac
done
# Build dashboard frontend (required to start the module).
cd ${CEPH_DEV_FOLDER}/src/pybind/mgr/dashboard/frontend
-NG_CLI_ANALYTICS=false npm ci
+export NG_CLI_ANALYTICS=false
+if [[ -n "$JENKINS_HOME" ]]; then
+ npm cache clean --force
+fi
+npm ci
FRONTEND_BUILD_OPTS='-- --prod'
if [[ -n "${DEV_MODE}" ]]; then
FRONTEND_BUILD_OPTS+=' --deleteOutputPath=false --watch'
def list(self, stats=False):
# type: (bool) -> List[Any]
- query_params = '?stats' if stats else ''
+ query_params = '?stats' if str_to_bool(stats) else ''
result = self.proxy('GET', 'bucket{}'.format(query_params))
if stats:
for daemon, counters in six.iteritems(self.get_all_perf_counters()):
svc_type, svc_id = daemon.split(".", 1)
metadata = self.get_metadata(svc_type, svc_id)
+ if metadata is not None:
+ hostname = metadata['hostname']
+ else:
+ hostname = 'N/A'
for path, counter_info in counters.items():
if counter_info['type'] & self.PERFCOUNTER_HISTOGRAM:
"tags": {
"ceph_daemon": daemon,
"type_instance": path,
- "host": metadata['hostname'],
+ "host": hostname,
"fsid": self.get_fsid()
},
"time": now,
# health history tracking
self._pending_health = []
self._health_slot = None
+ self._store = {}
+
+ # The following three functions, get_store, set_store, and get_store_prefix
+ # mask the functions defined in the parent to avoid storing large keys
+ # persistently to disk as that was proving problematic. Long term we may
+ # implement a different mechanism to make these persistent. When that day
+ # comes it should just be a matter of deleting these three functions.
+ def get_store(self, key):
+ return self._store.get(key)
+
+ def set_store(self, key, value):
+ if value is None:
+ if key in self._store:
+ del self._store[key]
+ else:
+ self._store[key] = value
+
+ def get_store_prefix(self, prefix):
+ return { k: v for k, v in self._store.items() if k.startswith(prefix) }
+
def notify(self, ttype, ident):
"""Queue updates for processing"""
for state in DF_POOL:
path = 'pool_{}'.format(state)
metrics[path] = Metric(
- 'gauge',
+ 'counter' if state in ('rd', 'rd_bytes', 'wr', 'wr_bytes') else 'gauge',
path,
'DF pool {}'.format(state),
('pool_id',)
else:
self._store[k] = value
- def mock_store_preifx(self, kind, prefix):
+ def mock_store_prefix(self, kind, prefix):
if not hasattr(self, '_store'):
self._store = {}
full_prefix = f'mock_store/{kind}/{prefix}'
self.mock_store_set('store', k, v)
def _ceph_get_store_prefix(self, prefix):
- return self.mock_store_preifx('store', prefix)
+ return self.mock_store_prefix('store', prefix)
def _ceph_get_module_option(self, module, key, localized_prefix= None):
try:
def config_dump():
r = []
- for prefix, value in self.mock_store_preifx('config', '').items():
+ for prefix, value in self.mock_store_prefix('config', '').items():
section, name = prefix.split('/', 1)
r.append({
'name': name,
local_status = site_status
else:
site_status['mirror_uuid'] = mirror_uuid
- site_statuses += site_status
+ site_statuses.append(site_status)
status = {
'name' : decode_cstr(self.images[i].name),
'info' : {
'global_id' : decode_cstr(self.images[i].info.global_id),
'state' : self.images[i].info.state,
+ # primary isn't added here because it is unknown (always
+ # false, see XXX in Mirror::image_global_status_list())
},
'remote_statuses': site_statuses,
}
if mirror_uuid == '':
local_status = site_status
else:
- site_statuses['mirror_uuid'] = mirror_uuid
- site_statuses += site_status
+ site_status['mirror_uuid'] = mirror_uuid
+ site_statuses.append(site_status)
status = {
'name': decode_cstr(c_status.name),
'id' : self.id(),
if (op_ret < 0)
return;
+ if (!s->bucket_exists) {
+ op_ret = -ERR_NO_SUCH_BUCKET;
+ return;
+ }
+
if (!store->svc()->zone->is_meta_master()) {
op_ret = forward_request_to_master(s, NULL, store, in_data, nullptr);
if (op_ret < 0) {
void RGWDeleteBucketWebsite::execute()
{
+ if (!s->bucket_exists) {
+ op_ret = -ERR_NO_SUCH_BUCKET;
+ return;
+ }
if (!store->svc()->zone->is_meta_master()) {
bufferlist in_data;
async_refcount->put_wait(); /* wait for all pending async requests to complete */
}
- int get_stats(const rgw_user& user, const rgw_bucket& bucket, RGWStorageStats& stats, RGWQuotaInfo& quota);
+ int get_stats(const rgw_user& user, const rgw_bucket& bucket, RGWStorageStats& stats);
void adjust_stats(const rgw_user& user, rgw_bucket& bucket, int objs_delta, uint64_t added_bytes, uint64_t removed_bytes);
- virtual bool can_use_cached_stats(RGWQuotaInfo& quota, RGWStorageStats& stats);
-
void set_stats(const rgw_user& user, const rgw_bucket& bucket, RGWQuotaCacheStats& qs, RGWStorageStats& stats);
int async_refresh(const rgw_user& user, const rgw_bucket& bucket, RGWQuotaCacheStats& qs);
void async_refresh_response(const rgw_user& user, rgw_bucket& bucket, RGWStorageStats& stats);
virtual AsyncRefreshHandler *allocate_refresh_handler(const rgw_user& user, const rgw_bucket& bucket) = 0;
};
-template<class T>
-bool RGWQuotaCache<T>::can_use_cached_stats(RGWQuotaInfo& quota, RGWStorageStats& cached_stats)
-{
- if (quota.max_size >= 0) {
- if (quota.max_size_soft_threshold < 0) {
- quota.max_size_soft_threshold = quota.max_size * store->ctx()->_conf->rgw_bucket_quota_soft_threshold;
- }
-
- if (cached_stats.size_rounded >= (uint64_t)quota.max_size_soft_threshold) {
- ldout(store->ctx(), 20) << "quota: can't use cached stats, exceeded soft threshold (size): "
- << cached_stats.size_rounded << " >= " << quota.max_size_soft_threshold << dendl;
- return false;
- }
- }
-
- if (quota.max_objects >= 0) {
- if (quota.max_objs_soft_threshold < 0) {
- quota.max_objs_soft_threshold = quota.max_objects * store->ctx()->_conf->rgw_bucket_quota_soft_threshold;
- }
-
- if (cached_stats.num_objects >= (uint64_t)quota.max_objs_soft_threshold) {
- ldout(store->ctx(), 20) << "quota: can't use cached stats, exceeded soft threshold (num objs): "
- << cached_stats.num_objects << " >= " << quota.max_objs_soft_threshold << dendl;
- return false;
- }
- }
-
- return true;
-}
-
template<class T>
int RGWQuotaCache<T>::async_refresh(const rgw_user& user, const rgw_bucket& bucket, RGWQuotaCacheStats& qs)
{
}
template<class T>
-int RGWQuotaCache<T>::get_stats(const rgw_user& user, const rgw_bucket& bucket, RGWStorageStats& stats, RGWQuotaInfo& quota) {
+int RGWQuotaCache<T>::get_stats(const rgw_user& user, const rgw_bucket& bucket, RGWStorageStats& stats) {
RGWQuotaCacheStats qs;
utime_t now = ceph_clock_now();
if (map_find(user, bucket, qs)) {
}
}
- if (can_use_cached_stats(quota, qs.stats) && qs.expiration >
- ceph_clock_now()) {
+ if (qs.expiration > ceph_clock_now()) {
stats = qs.stats;
return 0;
}
return new UserAsyncRefreshHandler(store, this, user, bucket);
}
- bool can_use_cached_stats(RGWQuotaInfo& quota, RGWStorageStats& stats) override {
- /* in the user case, the cached stats may contain a better estimation of the totals, as
- * the backend is only periodically getting updated.
- */
- return true;
- }
-
bool going_down() {
return down_flag;
}
if (bucket_quota.enabled) {
RGWStorageStats bucket_stats;
- int ret = bucket_stats_cache.get_stats(user, bucket, bucket_stats,
- bucket_quota);
+ int ret = bucket_stats_cache.get_stats(user, bucket, bucket_stats);
if (ret < 0) {
return ret;
}
if (user_quota.enabled) {
RGWStorageStats user_stats;
- int ret = user_stats_cache.get_stats(user, bucket, user_stats, user_quota);
+ int ret = user_stats_cache.get_stats(user, bucket, user_stats);
if (ret < 0) {
return ret;
}
struct RGWQuotaInfo {
template<class T> friend class RGWQuotaCache;
-protected:
- /* The quota thresholds after which comparing against cached storage stats
- * is disallowed. Those fields may be accessed only by the RGWQuotaCache.
- * They are not intended as tunables but rather as a mean to store results
- * of repeating calculations in the quota cache subsystem. */
- int64_t max_size_soft_threshold;
- int64_t max_objs_soft_threshold;
-
public:
int64_t max_size;
int64_t max_objects;
bool check_on_raw;
RGWQuotaInfo()
- : max_size_soft_threshold(-1),
- max_objs_soft_threshold(-1),
- max_size(-1),
+ : max_size(-1),
max_objects(-1),
enabled(false),
check_on_raw(false) {
}
}
-int RGWRados::apply_olh_log(RGWObjectCtx& obj_ctx, RGWObjState& state, const RGWBucketInfo& bucket_info, const rgw_obj& obj,
- bufferlist& olh_tag, map<uint64_t, vector<rgw_bucket_olh_log_entry> >& log,
- uint64_t *plast_ver, rgw_zone_set* zones_trace)
+int RGWRados::apply_olh_log(RGWObjectCtx& obj_ctx,
+ RGWObjState& state,
+ const RGWBucketInfo& bucket_info,
+ const rgw_obj& obj,
+ bufferlist& olh_tag,
+ std::map<uint64_t, std::vector<rgw_bucket_olh_log_entry> >& log,
+ uint64_t *plast_ver,
+ rgw_zone_set* zones_trace)
{
if (log.empty()) {
return 0;
}
+// A helper function to retrieve the hash source from an incomplete multipart entry
+// by removing everything from the second last dot to the end.
+static int parse_index_hash_source(const std::string& oid_wo_ns, std::string *index_hash_source) {
+ std::size_t found = oid_wo_ns.rfind('.');
+ if (found == std::string::npos || found < 1) {
+ return -EINVAL;
+ }
+ found = oid_wo_ns.rfind('.', found - 1);
+ if (found == std::string::npos || found < 1) {
+ return -EINVAL;
+ }
+ *index_hash_source = oid_wo_ns.substr(0, found);
+ return 0;
+}
+
+
int RGWRados::cls_bucket_list_unordered(RGWBucketInfo& bucket_info,
int shard_id,
const rgw_obj_index_key& start_after,
// in it, so we need to get to the bucket shard index, so we can
// start reading from there
- std::string key;
- // test whether object name is a multipart meta name
- if(! multipart_meta_filter.filter(start_after.name, key)) {
- // if multipart_meta_filter fails, must be "regular" (i.e.,
- // unadorned) and the name is the key
- key = start_after.name;
- }
// now convert the key (oid) to an rgw_obj_key since that will
// separate out the namespace, name, and instance
rgw_obj_key obj_key;
- bool parsed = rgw_obj_key::parse_raw_oid(key, &obj_key);
+ bool parsed = rgw_obj_key::parse_raw_oid(start_after.name, &obj_key);
if (!parsed) {
ldout(cct, 0) <<
"ERROR: RGWRados::cls_bucket_list_unordered received an invalid "
} else {
// so now we have the key used to compute the bucket index shard
// and can extract the specific shard from it
- current_shard = svc.bi_rados->bucket_shard_index(obj_key.name, num_shards);
+ if (obj_key.ns == RGW_OBJ_NS_MULTIPART) {
+ // Use obj_key.ns == RGW_OBJ_NS_MULTIPART instead of
+ // the implementation relying on MultipartMetaFilter
+ // because MultipartMetaFilter only checks .meta suffix, which may
+ // exclude data multiparts but include some regular objects with .meta suffix
+ // by mistake.
+ string index_hash_source;
+ r = parse_index_hash_source(obj_key.name, &index_hash_source);
+ if (r < 0) {
+ return r;
+ }
+ current_shard = svc.bi_rados->bucket_shard_index(index_hash_source, num_shards);
+ } else {
+ current_shard = svc.bi_rados->bucket_shard_index(obj_key.name, num_shards);
+ }
}
}
if (op_ret < 0) {
return;
}
+ std::string user_tenant = s->user->get_tenant();
RGWRole role(s->cct, store->getRados()->pctl, role_name, role_path, trust_policy,
- s->user->get_tenant(), max_session_duration);
+ user_tenant, max_session_duration);
+ if (!user_tenant.empty() && role.get_tenant() != user_tenant) {
+ ldpp_dout(this, 20) << "ERROR: the tenant provided in the role name does not match with the tenant of the user creating the role"
+ << dendl;
+ op_ret = -EINVAL;
+ return;
+ }
op_ret = role.create(true);
if (op_ret == -EEXIST) {
return -EINVAL;
}
string error;
- auto* keyhandler = cryptohandler->get_key_handler(secret, error);
+ std::unique_ptr<CryptoKeyHandler> keyhandler(cryptohandler->get_key_handler(secret, error));
if (! keyhandler) {
return -EINVAL;
}
return ret;
}
string error;
- auto* keyhandler = cryptohandler->get_key_handler(secret, error);
+ std::unique_ptr<CryptoKeyHandler> keyhandler(cryptohandler->get_key_handler(secret, error));
if (! keyhandler) {
return -EINVAL;
}
cls_lock
ceph_test_objectstore
ceph_erasure_code_non_regression
- ceph_erasure_code
cython_modules)
if (WITH_CEPHFS)
add_dependencies(tests ceph-mds)
EXPECT_TRUE(bl.is_aligned(SIMD_ALIGN));
EXPECT_TRUE(bl.is_n_align_sized(BUFFER_SIZE));
EXPECT_EQ(3U, bl.get_num_buffers());
+
+ {
+ /* bug replicator, to test rebuild_aligned_size_and_memory() in the
+ * scenario where the first bptr is both size and memory aligned and
+ * the second is 0-length */
+ bl.clear();
+ bufferptr ptr1(buffer::create_aligned(4096, 4096));
+ bl.append(ptr1);
+ bufferptr ptr(10);
+ /* bl.back().length() must be 0 */
+ bl.append(ptr, 0, 0);
+ EXPECT_EQ(bl.get_num_buffers(), 2);
+ EXPECT_EQ(bl.back().length(), 0);
+ /* rebuild_aligned() calls rebuild_aligned_size_and_memory() */
+ bl.rebuild_aligned(4096);
+ EXPECT_EQ(bl.get_num_buffers(), 1);
+ }
}
TEST(BufferList, is_zero) {
--- /dev/null
+#!/bin/sh -ex
+
+TMPDIR=/tmp/test_ceph-erasure-code-tool.$$
+mkdir $TMPDIR
+trap "rm -fr $TMPDIR" 0
+
+ceph-erasure-code-tool test-plugin-exists INVALID_PLUGIN && exit 1
+ceph-erasure-code-tool test-plugin-exists jerasure
+
+ceph-erasure-code-tool validate-profile \
+ plugin=jerasure,technique=reed_sol_van,k=2,m=1
+
+test "$(ceph-erasure-code-tool validate-profile \
+ plugin=jerasure,technique=reed_sol_van,k=2,m=1 chunk_count)" = 3
+
+test "$(ceph-erasure-code-tool calc-chunk-size \
+ plugin=jerasure,technique=reed_sol_van,k=2,m=1 4194304)" = 2097152
+
+dd if="$(which ceph-erasure-code-tool)" of=$TMPDIR/data bs=770808 count=1
+cp $TMPDIR/data $TMPDIR/data.orig
+
+ceph-erasure-code-tool encode \
+ plugin=jerasure,technique=reed_sol_van,k=2,m=1 \
+ 4096 \
+ 0,1,2 \
+ $TMPDIR/data
+test -f $TMPDIR/data.0
+test -f $TMPDIR/data.1
+test -f $TMPDIR/data.2
+
+rm $TMPDIR/data
+
+ceph-erasure-code-tool decode \
+ plugin=jerasure,technique=reed_sol_van,k=2,m=1 \
+ 4096 \
+ 0,2 \
+ $TMPDIR/data
+
+size=$(stat -c '%s' $TMPDIR/data.orig)
+truncate -s "${size}" $TMPDIR/data # remove stripe width padding
+cmp $TMPDIR/data.orig $TMPDIR/data
+
+echo OK
$ rbd device list
+Odd names
+=========
+
+ $ ceph osd pool create foo\* >/dev/null 2>&1
+ $ rbd pool init foo\*
+ $ rbd create --size 1 foo\*/[0.0.0.0]
+ $ rbd snap create foo\*/[0.0.0.0]@\?bar
+ $ sudo rbd device map foo\*/[0.0.0.0]@\?bar
+ /dev/rbd? (glob)
+ $ rbd device list
+ id pool namespace image snap device
+ ? foo* [0.0.0.0] ?bar /dev/rbd? (glob)
+ $ sudo rbd device unmap foo\*/[0.0.0.0]@\?bar
+ $ rbd device list
+ $ ceph osd pool delete foo\* foo\* --yes-i-really-really-mean-it >/dev/null 2>&1
+
+
Teardown
========
add_executable(ceph_erasure_code_non_regression ceph_erasure_code_non_regression.cc)
target_link_libraries(ceph_erasure_code_non_regression ceph-common Boost::program_options global ${CMAKE_DL_LIBS})
-add_executable(ceph_erasure_code ceph_erasure_code.cc)
-target_link_libraries(ceph_erasure_code ceph-common Boost::program_options global ${CMAKE_DL_LIBS})
-install(TARGETS ceph_erasure_code
- DESTINATION bin)
-
add_library(ec_example SHARED
ErasureCodePluginExample.cc
$<TARGET_OBJECTS:erasure_code_objs>)
+++ /dev/null
-// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
-// vim: ts=8 sw=2 smarttab
-/*
- * Ceph distributed storage system
- *
- * Copyright (C) 2014 Cloudwatt <libre.licensing@cloudwatt.com>
- * Copyright (C) 2014 Red Hat <contact@redhat.com>
- *
- * Author: Loic Dachary <loic@dachary.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- */
-
-#include <boost/scoped_ptr.hpp>
-#include <boost/lexical_cast.hpp>
-#include <boost/program_options/option.hpp>
-#include <boost/program_options/options_description.hpp>
-#include <boost/program_options/variables_map.hpp>
-#include <boost/program_options/cmdline.hpp>
-#include <boost/program_options/parsers.hpp>
-#include <boost/algorithm/string.hpp>
-
-#include "global/global_context.h"
-#include "global/global_init.h"
-#include "common/ceph_argparse.h"
-#include "common/ceph_context.h"
-#include "common/config.h"
-#include "common/Clock.h"
-#include "include/utime.h"
-#include "erasure-code/ErasureCodePlugin.h"
-
-namespace po = boost::program_options;
-
-class ErasureCodeCommand {
- po::variables_map vm;
- ErasureCodeProfile profile;
- boost::intrusive_ptr<CephContext> cct;
-public:
- int setup(int argc, char** argv);
- int run();
- int plugin_exists();
- int display_information();
-};
-
-int ErasureCodeCommand::setup(int argc, char** argv) {
-
- po::options_description desc("Allowed options");
- desc.add_options()
- ("help,h", "produce help message")
- ("all", "implies "
- "--get_chunk_size 1024 "
- "--get_data_chunk_count "
- "--get_coding_chunk_count "
- "--get_chunk_count ")
- ("get_chunk_size", po::value<unsigned int>(),
- "display get_chunk_size(<object size>)")
- ("get_data_chunk_count", "display get_data_chunk_count()")
- ("get_coding_chunk_count", "display get_coding_chunk_count()")
- ("get_chunk_count", "display get_chunk_count()")
- ("parameter,P", po::value<vector<string> >(),
- "parameters")
- ("plugin_exists", po::value<string>(),
- "succeeds if the plugin given in argument exists and can be loaded")
- ;
-
- po::parsed_options parsed =
- po::command_line_parser(argc, argv).options(desc).allow_unregistered().run();
- po::store(
- parsed,
- vm);
- po::notify(vm);
-
- vector<const char *> ceph_options;
- vector<string> ceph_option_strings = po::collect_unrecognized(
- parsed.options, po::include_positional);
- ceph_options.reserve(ceph_option_strings.size());
- for (vector<string>::iterator i = ceph_option_strings.begin();
- i != ceph_option_strings.end();
- ++i) {
- ceph_options.push_back(i->c_str());
- }
-
- cct = global_init(
- NULL, ceph_options, CEPH_ENTITY_TYPE_CLIENT,
- CODE_ENVIRONMENT_UTILITY,
- CINIT_FLAG_NO_MON_CONFIG);
- common_init_finish(g_ceph_context);
- g_ceph_context->_conf.apply_changes(nullptr);
-
- if (vm.count("help")) {
- cout << desc << std::endl;
- return 1;
- }
-
- if (vm.count("parameter")) {
- const vector<string> &p = vm["parameter"].as< vector<string> >();
- for (vector<string>::const_iterator i = p.begin();
- i != p.end();
- ++i) {
- std::vector<std::string> strs;
- boost::split(strs, *i, boost::is_any_of("="));
- if (strs.size() != 2) {
- cerr << "--parameter " << *i
- << " ignored because it does not contain exactly one =" << endl;
- } else {
- profile[strs[0]] = strs[1];
- }
- }
- }
-
- return 0;
-}
-
-int ErasureCodeCommand::run() {
- if (vm.count("plugin_exists"))
- return plugin_exists();
- else
- return display_information();
-}
-
-int ErasureCodeCommand::plugin_exists() {
- ErasureCodePluginRegistry &instance = ErasureCodePluginRegistry::instance();
- ErasureCodePlugin *plugin = 0;
- std::lock_guard l{instance.lock};
- stringstream ss;
- int code = instance.load(vm["plugin_exists"].as<string>(),
- g_conf().get_val<std::string>("erasure_code_dir"), &plugin, &ss);
- if (code)
- cerr << ss.str() << endl;
- return code;
-}
-
-int ErasureCodeCommand::display_information() {
- ErasureCodePluginRegistry &instance = ErasureCodePluginRegistry::instance();
- ErasureCodeInterfaceRef erasure_code;
-
- if (profile.count("plugin") == 0) {
- cerr << "--parameter plugin=<plugin> is mandatory" << endl;
- return 1;
- }
-
- int code = instance.factory(profile["plugin"],
- g_conf().get_val<std::string>("erasure_code_dir"),
- profile,
- &erasure_code, &cerr);
- if (code)
- return code;
-
- if (vm.count("all") || vm.count("get_chunk_size")) {
- unsigned int object_size = 1024;
- if (vm.count("get_chunk_size"))
- object_size = vm["get_chunk_size"].as<unsigned int>();
- cout << "get_chunk_size(" << object_size << ")\t"
- << erasure_code->get_chunk_size(object_size) << endl;
- }
- if (vm.count("all") || vm.count("get_data_chunk_count"))
- cout << "get_data_chunk_count\t"
- << erasure_code->get_data_chunk_count() << endl;
- if (vm.count("all") || vm.count("get_coding_chunk_count"))
- cout << "get_coding_chunk_count\t"
- << erasure_code->get_coding_chunk_count() << endl;
- if (vm.count("all") || vm.count("get_chunk_count"))
- cout << "get_chunk_count\t"
- << erasure_code->get_chunk_count() << endl;
- return 0;
-}
-
-int main(int argc, char** argv) {
- ErasureCodeCommand eccommand;
- try {
- int err = eccommand.setup(argc, argv);
- if (err)
- return err;
- return eccommand.run();
- } catch(po::error &e) {
- cerr << e.what() << endl;
- return 1;
- }
-}
-
-/*
- * Local Variables:
- * compile-command: "cd ../.. ; make -j4 &&
- * make -j4 ceph_erasure_code &&
- * libtool --mode=execute valgrind --tool=memcheck --leak-check=full \
- * ./ceph_erasure_code \
- * --parameter plugin=jerasure \
- * --parameter technique=reed_sol_van \
- * --parameter k=2 \
- * --parameter m=2 \
- * --get_chunk_size 1024 \
- * --get_data_chunk_count \
- * --get_coding_chunk_count \
- * --get_chunk_count \
- * "
- * End:
- */
pool.quota_max_bytes = 2000 * 1024 * 1024;
pool.size = 2;
pool.type = pg_pool_t::TYPE_REPLICATED;
+ pool.tier_of = 0;
PGMap::dump_object_stat_sum(tbl, nullptr, pool_stat, avail,
- pool.get_size(), verbose, true, true, &pool);
+ pool.get_size(), verbose, true, true, &pool);
float copies_rate =
(static_cast<float>(sum.num_object_copies - sum.num_objects_degraded) /
sum.num_object_copies) * pool.get_size();
pool.quota_max_bytes = 2000 * 1024 * 1024;
pool.size = 2;
pool.type = pg_pool_t::TYPE_REPLICATED;
+ pool.tier_of = 0;
PGMap::dump_object_stat_sum(tbl, nullptr, pool_stat, avail,
- pool.get_size(), verbose, true, true, &pool);
+ pool.get_size(), verbose, true, true, &pool);
unsigned col = 0;
ASSERT_EQ(stringify(byte_u_t(0)), tbl.get(0, col++));
ASSERT_EQ(stringify(byte_u_t(0)), tbl.get(0, col++));
mock_remote_image_ctx));
}
+TEST_F(TestMockImageReplayerSnapshotReplayer, ImageNameUpdated) {
+ librbd::MockTestImageCtx mock_local_image_ctx{*m_local_image_ctx};
+ librbd::MockTestImageCtx mock_remote_image_ctx{*m_remote_image_ctx};
+
+ MockThreads mock_threads(m_threads);
+ expect_work_queue_repeatedly(mock_threads);
+
+ MockReplayerListener mock_replayer_listener;
+ expect_notification(mock_threads, mock_replayer_listener);
+
+ InSequence seq;
+
+ MockInstanceWatcher mock_instance_watcher;
+ MockImageMeta mock_image_meta;
+ MockStateBuilder mock_state_builder(mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_image_meta);
+ MockReplayer mock_replayer{&mock_threads, &mock_instance_watcher,
+ "local mirror uuid", &m_pool_meta_cache,
+ &mock_state_builder, &mock_replayer_listener};
+ m_pool_meta_cache.set_remote_pool_meta(
+ m_remote_io_ctx.get_id(),
+ {"remote mirror uuid", "remote mirror peer uuid"});
+
+ librbd::UpdateWatchCtx* update_watch_ctx = nullptr;
+ ASSERT_EQ(0, init_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx,
+ mock_replayer_listener,
+ mock_image_meta,
+ &update_watch_ctx));
+
+ // change the name of the image
+ mock_local_image_ctx.name = "NEW NAME";
+
+ // idle
+ expect_load_image_meta(mock_image_meta, true, 0);
+
+ // wake-up replayer
+ update_watch_ctx->handle_notify();
+
+ // wait for sync to complete and expect replay complete
+ ASSERT_EQ(0, wait_for_notification(2));
+ auto image_spec = image_replayer::util::compute_image_spec(m_local_io_ctx,
+ "NEW NAME");
+ ASSERT_EQ(image_spec, mock_replayer.get_image_spec());
+ ASSERT_FALSE(mock_replayer.is_replaying());
+
+ // shut down
+ ASSERT_EQ(0, shut_down_entry_replayer(mock_replayer, mock_threads,
+ mock_local_image_ctx,
+ mock_remote_image_ctx));
+}
+
} // namespace snapshot
} // namespace image_replayer
} // namespace mirror
#include <iostream>
#include <string>
-PerfCounters *g_perf_counters = nullptr;
+PerfCounters *g_journal_perf_counters = nullptr;
+PerfCounters *g_snapshot_perf_counters = nullptr;
extern void register_test_cluster_watcher();
extern void register_test_image_policy();
add_subdirectory(immutable_object_cache)
add_subdirectory(ceph-dencoder)
+add_subdirectory(erasure-code)
static int update_paxos(MonitorDBStore& st)
{
+ const string prefix("paxos");
+ // a large enough version greater than the maximum possible `last_committed`
+ // that could be replied by the peons when the leader is collecting paxos
+ // transactions during recovery
+ constexpr version_t first_committed = 0x42;
+ constexpr version_t last_committed = first_committed;
+ for (version_t v = first_committed; v < last_committed + 1; v++) {
+ auto t = make_shared<MonitorDBStore::Transaction>();
+ if (v == first_committed) {
+ t->put(prefix, "first_committed", v);
+ }
+ bufferlist proposal;
+ MonitorDBStore::Transaction empty_txn;
+ empty_txn.encode(proposal);
+ t->put(prefix, v, proposal);
+ t->put(prefix, "last_committed", v);
+ st.apply_transaction(t);
+ }
// build a pending paxos proposal from all non-permanent k/v pairs. once the
// proposal is committed, it will gets applied. on the sync provider side, it
// will be a no-op, but on its peers, the paxos commit will help to build up
}
t.encode(pending_proposal);
}
- const string prefix("paxos");
+ auto pending_v = last_committed + 1;
auto t = make_shared<MonitorDBStore::Transaction>();
- t->put(prefix, "first_committed", 0);
- t->put(prefix, "last_committed", 0);
- auto pending_v = 1;
t->put(prefix, pending_v, pending_proposal);
t->put(prefix, "pending_v", pending_v);
t->put(prefix, "pending_pn", 400);
--- /dev/null
+add_executable(ceph-erasure-code-tool
+ ${PROJECT_SOURCE_DIR}/src/osd/ECUtil.cc
+ ceph-erasure-code-tool.cc)
+target_link_libraries(ceph-erasure-code-tool global ceph-common)
+install(TARGETS ceph-erasure-code-tool DESTINATION bin)
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/buffer.h"
+#include "include/stringify.h"
+#include "common/ceph_argparse.h"
+#include "common/config_proxy.h"
+#include "common/errno.h"
+#include "erasure-code/ErasureCode.h"
+#include "erasure-code/ErasureCodePlugin.h"
+#include "global/global_context.h"
+#include "global/global_init.h"
+#include "osd/ECUtil.h"
+
+#include <iostream>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <boost/algorithm/string.hpp>
+
+std::vector<std::string> display_params = {
+ "chunk_count", "data_chunk_count", "coding_chunk_count"
+};
+
+void usage(const std::string message, ostream &out) {
+ if (!message.empty()) {
+ out << message << std::endl;
+ out << "" << std::endl;
+ }
+ out << "usage: ceph-erasure-code-tool test-plugin-exists <plugin>" << std::endl;
+ out << " ceph-erasure-code-tool validate-profile <profile> [<display-param> ...]" << std::endl;
+ out << " ceph-erasure-code-tool calc-chunk-size <profile> <object_size>" << std::endl;
+ out << " ceph-erasure-code-tool encode <profile> <stripe_unit> <want_to_encode> <fname>" << std::endl;
+ out << " ceph-erasure-code-tool decode <profile> <stripe_unit> <want_to_decode> <fname>" << std::endl;
+ out << "" << std::endl;
+ out << " plugin - plugin name" << std::endl;
+ out << " profile - comma separated list of erasure-code profile settings" << std::endl;
+ out << " example: plugin=jerasure,technique=reed_sol_van,k=3,m=2" << std::endl;
+ out << " display-param - parameter to display (display all if empty)" << std::endl;
+ out << " may be: " << display_params << std::endl;
+ out << " object_size - object size" << std::endl;
+ out << " stripe_unit - stripe unit" << std::endl;
+ out << " want_to_encode - comma separated list of shards to encode" << std::endl;
+ out << " want_to_decode - comma separated list of shards to decode" << std::endl;
+ out << " fname - name for input/output files" << std::endl;
+ out << " when encoding input is read form {fname} file," << std::endl;
+ out << " result is stored in {fname}.{shard} files" << std::endl;
+ out << " when decoding input is read form {fname}.{shard} files," << std::endl;
+ out << " result is stored in {fname} file" << std::endl;
+}
+
+int ec_init(const std::string &profile_str,
+ const std::string &stripe_unit_str,
+ ceph::ErasureCodeInterfaceRef *ec_impl,
+ std::unique_ptr<ECUtil::stripe_info_t> *sinfo) {
+ ceph::ErasureCodeProfile profile;
+ std::vector<std::string> opts;
+ boost::split(opts, profile_str, boost::is_any_of(", "));
+ for (auto &opt_str : opts) {
+ std::vector<std::string> opt;
+ boost::split(opt, opt_str, boost::is_any_of("="));
+ if (opt.size() <= 1) {
+ usage("invalid profile", std::cerr);
+ return 1;
+ }
+ profile[opt[0]] = opt[1];
+ }
+ auto plugin = profile.find("plugin");
+ if (plugin == profile.end()) {
+ usage("invalid profile: plugin not specified", std::cerr);
+ return 1;
+ }
+
+ stringstream ss;
+ ceph::ErasureCodePluginRegistry::instance().factory(
+ plugin->second, g_conf().get_val<std::string>("erasure_code_dir"),
+ profile, ec_impl, &ss);
+ if (!*ec_impl) {
+ usage("invalid profile: " + ss.str(), std::cerr);
+ return 1;
+ }
+
+ if (sinfo == nullptr) {
+ return 0;
+ }
+
+ uint64_t stripe_unit = atoi(stripe_unit_str.c_str());
+ if (stripe_unit <= 0) {
+ usage("invalid stripe unit", std::cerr);
+ return 1;
+ }
+
+ uint64_t stripe_size = atoi(profile["k"].c_str());
+ ceph_assert(stripe_size > 0);
+ uint64_t stripe_width = stripe_size * stripe_unit;
+ sinfo->reset(new ECUtil::stripe_info_t(stripe_size, stripe_width));
+
+ return 0;
+}
+
+int do_test_plugin_exists(const std::vector<const char*> &args) {
+ if (args.size() < 1) {
+ usage("not enought arguments", std::cerr);
+ return 1;
+ }
+
+ ErasureCodePluginRegistry &instance = ErasureCodePluginRegistry::instance();
+ ErasureCodePlugin *plugin;
+ stringstream ss;
+
+ std::lock_guard l{instance.lock};
+ int r = instance.load(
+ args[0], g_conf().get_val<std::string>("erasure_code_dir"), &plugin, &ss);
+ std::cerr << ss.str() << endl;
+ return r;
+}
+
+int do_validate_profile(const std::vector<const char*> &args) {
+ if (args.size() < 1) {
+ usage("not enought arguments", std::cerr);
+ return 1;
+ }
+
+ ceph::ErasureCodeInterfaceRef ec_impl;
+ int r = ec_init(args[0], {}, &ec_impl, nullptr);
+ if (r < 0) {
+ return r;
+ }
+
+ if (args.size() > 1) {
+ std::set<std::string> valid_params(display_params.begin(),
+ display_params.end());
+ display_params.clear();
+ for (size_t i = 1; i < args.size(); i++) {
+ if (!valid_params.count(args[i])) {
+ usage("invalid display param: " + std::string(args[i]), std::cerr);
+ return 1;
+ }
+ display_params.push_back(args[i]);
+ }
+ }
+
+ for (auto ¶m : display_params) {
+ if (display_params.size() > 1) {
+ std::cout << param << ": ";
+ }
+ if (param == "chunk_count") {
+ std::cout << ec_impl->get_chunk_count() << std::endl;
+ } else if (param == "data_chunk_count") {
+ std::cout << ec_impl->get_data_chunk_count() << std::endl;
+ } else if (param == "coding_chunk_count") {
+ std::cout << ec_impl->get_coding_chunk_count() << std::endl;
+ } else {
+ ceph_abort_msgf("unknown display_param: %s", param.c_str());
+ }
+ }
+
+ return 0;
+}
+
+int do_calc_chunk_size(const std::vector<const char*> &args) {
+ if (args.size() < 2) {
+ usage("not enought arguments", std::cerr);
+ return 1;
+ }
+
+ ceph::ErasureCodeInterfaceRef ec_impl;
+ int r = ec_init(args[0], {}, &ec_impl, nullptr);
+ if (r < 0) {
+ return r;
+ }
+
+ uint64_t object_size = atoi(args[1]);
+ if (object_size <= 0) {
+ usage("invalid object size", std::cerr);
+ return 1;
+ }
+
+ std::cout << ec_impl->get_chunk_size(object_size) << std::endl;
+ return 0;
+}
+
+int do_encode(const std::vector<const char*> &args) {
+ if (args.size() < 4) {
+ usage("not enought arguments", std::cerr);
+ return 1;
+ }
+
+ ceph::ErasureCodeInterfaceRef ec_impl;
+ std::unique_ptr<ECUtil::stripe_info_t> sinfo;
+ int r = ec_init(args[0], args[1], &ec_impl, &sinfo);
+ if (r < 0) {
+ return r;
+ }
+
+ std::set<int> want;
+ std::vector<std::string> shards;
+ boost::split(shards, args[2], boost::is_any_of(","));
+ for (auto &shard : shards) {
+ want.insert(atoi(shard.c_str()));
+ }
+ ceph::bufferlist decoded_data;
+ std::string fname = args[3];
+
+ std::string error;
+ r = decoded_data.read_file(fname.c_str(), &error);
+ if (r < 0) {
+ std::cerr << "failed to read " << fname << ": " << error << std::endl;
+ return 1;
+ }
+
+ uint64_t stripe_width = sinfo->get_stripe_width();
+ if (decoded_data.length() % stripe_width != 0) {
+ uint64_t pad = stripe_width - decoded_data.length() % stripe_width;
+ decoded_data.append_zero(pad);
+ }
+
+ std::map<int, ceph::bufferlist> encoded_data;
+ r = ECUtil::encode(*sinfo, ec_impl, decoded_data, want, &encoded_data);
+ if (r < 0) {
+ std::cerr << "failed to encode: " << cpp_strerror(r) << std::endl;
+ return 1;
+ }
+
+ for (auto &[shard, bl] : encoded_data) {
+ std::string name = fname + "." + stringify(shard);
+ r = bl.write_file(name.c_str());
+ if (r < 0) {
+ std::cerr << "failed to write " << name << ": " << cpp_strerror(r)
+ << std::endl;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int do_decode(const std::vector<const char*> &args) {
+ if (args.size() < 4) {
+ usage("not enought arguments", std::cerr);
+ return 1;
+ }
+
+ ceph::ErasureCodeInterfaceRef ec_impl;
+ std::unique_ptr<ECUtil::stripe_info_t> sinfo;
+ int r = ec_init(args[0], args[1], &ec_impl, &sinfo);
+ if (r < 0) {
+ return r;
+ }
+
+ std::map<int, ceph::bufferlist> encoded_data;
+ std::vector<std::string> shards;
+ boost::split(shards, args[2], boost::is_any_of(","));
+ for (auto &shard : shards) {
+ encoded_data[atoi(shard.c_str())] = {};
+ }
+ ceph::bufferlist decoded_data;
+ std::string fname = args[3];
+
+ for (auto &[shard, bl] : encoded_data) {
+ std::string name = fname + "." + stringify(shard);
+ std::string error;
+ r = bl.read_file(name.c_str(), &error);
+ if (r < 0) {
+ std::cerr << "failed to read " << name << ": " << error << std::endl;
+ return 1;
+ }
+ }
+
+ r = ECUtil::decode(*sinfo, ec_impl, encoded_data, &decoded_data);
+ if (r < 0) {
+ std::cerr << "failed to decode: " << cpp_strerror(r) << std::endl;
+ return 1;
+ }
+
+ r = decoded_data.write_file(fname.c_str());
+ if (r < 0) {
+ std::cerr << "failed to write " << fname << ": " << cpp_strerror(r)
+ << std::endl;
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int argc, const char **argv) {
+ std::vector<const char*> args;
+ argv_to_vec(argc, argv, args);
+ auto cct = global_init(nullptr, args, CEPH_ENTITY_TYPE_CLIENT,
+ CODE_ENVIRONMENT_UTILITY,
+ CINIT_FLAG_NO_MON_CONFIG);
+
+ if (args.empty() || args[0] == std::string("-h") ||
+ args[0] == std::string("--help")) {
+ usage("", std::cout);
+ return 0;
+ }
+
+ if (args.size() < 1) {
+ usage("not enought arguments", std::cerr);
+ return 1;
+ }
+
+ std::string cmd = args[0];
+ std::vector<const char*> cmd_args(args.begin() + 1, args.end());
+
+ if (cmd == "test-plugin-exists") {
+ return do_test_plugin_exists(cmd_args);
+ } else if (cmd == "validate-profile") {
+ return do_validate_profile(cmd_args);
+ } else if (cmd == "calc-chunk-size") {
+ return do_calc_chunk_size(cmd_args);
+ } else if (cmd == "encode") {
+ return do_encode(cmd_args);
+ } else if (cmd == "decode") {
+ return do_decode(cmd_args);
+ }
+
+ usage("invalid command: " + cmd, std::cerr);
+ return 1;
+}
#define dout_prefix *_dout << "rbd::mirror::" << *this << " " \
<< __func__ << ": "
-extern PerfCounters *g_perf_counters;
-
namespace rbd {
namespace mirror {
std::lock_guard locker{m_lock};
if (m_on_shut_down != nullptr) {
+ m_async_op_tracker.finish_op();
return;
}
#define dout_context g_ceph_context
#define dout_subsys ceph_subsys_rbd_mirror
#undef dout_prefix
-#define dout_prefix *_dout << "rbd::mirror::RemotePollPoller: " << this << " " \
+#define dout_prefix *_dout << "rbd::mirror::RemotePoolPoller: " << this << " " \
<< __func__ << ": "
namespace rbd {
cls::rbd::MirrorPeer* matched_peer = nullptr;
for (auto& peer : peers) {
- if (peer.mirror_peer_direction == cls::rbd::MIRROR_PEER_DIRECTION_TX) {
+ if (peer.mirror_peer_direction == cls::rbd::MIRROR_PEER_DIRECTION_RX) {
continue;
}
std::ostream& operator<<(std::ostream& lhs,
const RemotePoolMeta& rhs) {
return lhs << "mirror_uuid=" << rhs.mirror_uuid << ", "
- "mirror_pool_uuid=" << rhs.mirror_peer_uuid;
+ "mirror_peer_uuid=" << rhs.mirror_peer_uuid;
}
std::ostream& operator<<(std::ostream& lhs, const PeerSpec &peer) {
// Performance counters
enum {
- l_rbd_mirror_first = 27000,
+ l_rbd_mirror_journal_first = 27000,
l_rbd_mirror_replay,
l_rbd_mirror_replay_bytes,
l_rbd_mirror_replay_latency,
- l_rbd_mirror_last,
+ l_rbd_mirror_journal_last,
+ l_rbd_mirror_snapshot_first,
+ l_rbd_mirror_snapshot_replay_snapshots,
+ l_rbd_mirror_snapshot_replay_snapshots_time,
+ l_rbd_mirror_snapshot_replay_bytes,
+ l_rbd_mirror_snapshot_last,
};
typedef std::shared_ptr<librados::Rados> RadosRef;
#define dout_prefix *_dout << "rbd::mirror::image_replayer::journal::" \
<< "Replayer: " << this << " " << __func__ << ": "
-extern PerfCounters *g_perf_counters;
+extern PerfCounters *g_journal_perf_counters;
namespace rbd {
namespace mirror {
}
auto latency = ceph_clock_now() - replay_start_time;
- if (g_perf_counters) {
- g_perf_counters->inc(l_rbd_mirror_replay);
- g_perf_counters->inc(l_rbd_mirror_replay_bytes, replay_bytes);
- g_perf_counters->tinc(l_rbd_mirror_replay_latency, latency);
+ if (g_journal_perf_counters) {
+ g_journal_perf_counters->inc(l_rbd_mirror_replay);
+ g_journal_perf_counters->inc(l_rbd_mirror_replay_bytes, replay_bytes);
+ g_journal_perf_counters->tinc(l_rbd_mirror_replay_latency, latency);
}
auto ctx = new LambdaContext(
auto cct = static_cast<CephContext *>(m_state_builder->local_image_ctx->cct);
auto prio = cct->_conf.get_val<int64_t>("rbd_mirror_image_perf_stats_prio");
PerfCountersBuilder plb(g_ceph_context, "rbd_mirror_image_" + m_image_spec,
- l_rbd_mirror_first, l_rbd_mirror_last);
+ l_rbd_mirror_journal_first, l_rbd_mirror_journal_last);
plb.add_u64_counter(l_rbd_mirror_replay, "replay", "Replays", "r", prio);
plb.add_u64_counter(l_rbd_mirror_replay_bytes, "replay_bytes",
"Replayed data", "rb", prio, unit_t(UNIT_BYTES));
#define dout_prefix *_dout << "rbd::mirror::image_replayer::snapshot::" \
<< "Replayer: " << this << " " << __func__ << ": "
-extern PerfCounters *g_perf_counters;
+extern PerfCounters *g_snapshot_perf_counters;
namespace rbd {
namespace mirror {
template <typename I>
Replayer<I>::~Replayer() {
dout(10) << dendl;
+
+ {
+ std::unique_lock locker{m_lock};
+ unregister_perf_counters();
+ }
+
ceph_assert(m_state == STATE_COMPLETE);
ceph_assert(m_update_watch_ctx == nullptr);
ceph_assert(m_deep_copy_handler == nullptr);
m_remote_mirror_peer_uuid = remote_pool_meta.mirror_peer_uuid;
dout(10) << "remote_mirror_peer_uuid=" << m_remote_mirror_peer_uuid << dendl;
+ {
+ auto local_image_ctx = m_state_builder->local_image_ctx;
+ std::shared_lock image_locker{local_image_ctx->image_lock};
+ m_image_spec = image_replayer::util::compute_image_spec(
+ local_image_ctx->md_ctx, local_image_ctx->name);
+ }
+
+ {
+ std::unique_lock locker{m_lock};
+ register_perf_counters();
+ }
+
ceph_assert(m_on_init_shutdown == nullptr);
m_on_init_shutdown = on_finish;
m_image_updated = false;
}
+ bool update_status = false;
+ {
+ auto local_image_ctx = m_state_builder->local_image_ctx;
+ std::shared_lock image_locker{local_image_ctx->image_lock};
+ auto image_spec = image_replayer::util::compute_image_spec(
+ local_image_ctx->md_ctx, local_image_ctx->name);
+ if (m_image_spec != image_spec) {
+ m_image_spec = image_spec;
+ update_status = true;
+ }
+ }
+ if (update_status) {
+ std::unique_lock locker{m_lock};
+ unregister_perf_counters();
+ register_perf_counters();
+ notify_status_updated();
+ }
+
ceph_assert(m_state_builder->local_image_meta != nullptr);
auto ctx = create_context_callback<
Replayer<I>, &Replayer<I>::handle_load_local_image_meta>(this);
<< "snap_seqs=" << m_local_mirror_snap_ns.snap_seqs << dendl;
m_snapshot_bytes = 0;
+ m_snapshot_replay_start = ceph_clock_now();
m_deep_copy_handler = new DeepCopyHandler(this);
auto ctx = create_context_callback<
Replayer<I>, &Replayer<I>::handle_copy_image>(this);
{
std::unique_lock locker{m_lock};
m_bytes_per_snapshot(m_snapshot_bytes);
+ auto time = ceph_clock_now() - m_snapshot_replay_start;
+ if (g_snapshot_perf_counters) {
+ g_snapshot_perf_counters->inc(l_rbd_mirror_snapshot_replay_bytes,
+ m_snapshot_bytes);
+ g_snapshot_perf_counters->inc(l_rbd_mirror_snapshot_replay_snapshots);
+ g_snapshot_perf_counters->tinc(
+ l_rbd_mirror_snapshot_replay_snapshots_time, time);
+ }
+ if (m_perf_counters) {
+ m_perf_counters->inc(l_rbd_mirror_snapshot_replay_bytes, m_snapshot_bytes);
+ m_perf_counters->inc(l_rbd_mirror_snapshot_replay_snapshots);
+ m_perf_counters->tinc(l_rbd_mirror_snapshot_replay_snapshots_time, time);
+ }
m_snapshot_bytes = 0;
}
return false;
}
+template <typename I>
+void Replayer<I>::register_perf_counters() {
+ dout(5) << dendl;
+
+ ceph_assert(ceph_mutex_is_locked_by_me(m_lock));
+ ceph_assert(m_perf_counters == nullptr);
+
+ auto cct = static_cast<CephContext *>(m_state_builder->local_image_ctx->cct);
+ auto prio = cct->_conf.get_val<int64_t>("rbd_mirror_image_perf_stats_prio");
+ PerfCountersBuilder plb(g_ceph_context,
+ "rbd_mirror_snapshot_image_" + m_image_spec,
+ l_rbd_mirror_snapshot_first,
+ l_rbd_mirror_snapshot_last);
+ plb.add_u64_counter(l_rbd_mirror_snapshot_replay_snapshots,
+ "snapshots", "Snapshots", "r", prio);
+ plb.add_time_avg(l_rbd_mirror_snapshot_replay_snapshots_time,
+ "snapshots_time", "Snapshots time", "rl", prio);
+ plb.add_u64_counter(l_rbd_mirror_snapshot_replay_bytes, "replay_bytes",
+ "Replayed data", "rb", prio, unit_t(UNIT_BYTES));
+ m_perf_counters = plb.create_perf_counters();
+ g_ceph_context->get_perfcounters_collection()->add(m_perf_counters);
+}
+
+template <typename I>
+void Replayer<I>::unregister_perf_counters() {
+ dout(5) << dendl;
+ ceph_assert(ceph_mutex_is_locked_by_me(m_lock));
+
+ PerfCounters *perf_counters = nullptr;
+ std::swap(perf_counters, m_perf_counters);
+
+ if (perf_counters != nullptr) {
+ g_ceph_context->get_perfcounters_collection()->remove(perf_counters);
+ delete perf_counters;
+ }
+}
+
} // namespace snapshot
} // namespace image_replayer
} // namespace mirror
return m_error_description;
}
+ std::string get_image_spec() const {
+ std::unique_lock locker(m_lock);
+ return m_image_spec;
+ }
+
private:
/**
* @verbatim
State m_state = STATE_INIT;
+ std::string m_image_spec;
Context* m_on_init_shutdown = nullptr;
bool m_resync_requested = false;
uint64_t, boost::accumulators::stats<
boost::accumulators::tag::rolling_mean>> m_bytes_per_snapshot{
boost::accumulators::tag::rolling_window::window_size = 2};
+ utime_t m_snapshot_replay_start;
uint32_t m_pending_snapshots = 0;
bool m_updating_sync_point = false;
bool m_sync_in_progress = false;
+ PerfCounters *m_perf_counters = nullptr;
+
void load_local_image_meta();
void handle_load_local_image_meta(int r);
bool is_replay_interrupted();
bool is_replay_interrupted(std::unique_lock<ceph::mutex>* lock);
+ void register_perf_counters();
+ void unregister_perf_counters();
};
} // namespace snapshot
#include <vector>
rbd::mirror::Mirror *mirror = nullptr;
-PerfCounters *g_perf_counters = nullptr;
+PerfCounters *g_journal_perf_counters = nullptr;
+PerfCounters *g_snapshot_perf_counters = nullptr;
void usage() {
std::cout << "usage: rbd-mirror [options...]" << std::endl;
auto prio =
g_ceph_context->_conf.get_val<int64_t>("rbd_mirror_perf_stats_prio");
- PerfCountersBuilder plb(g_ceph_context, "rbd_mirror",
- rbd::mirror::l_rbd_mirror_first,
- rbd::mirror::l_rbd_mirror_last);
- plb.add_u64_counter(rbd::mirror::l_rbd_mirror_replay, "replay", "Replays",
- "r", prio);
- plb.add_u64_counter(rbd::mirror::l_rbd_mirror_replay_bytes, "replay_bytes",
- "Replayed data", "rb", prio, unit_t(UNIT_BYTES));
- plb.add_time_avg(rbd::mirror::l_rbd_mirror_replay_latency, "replay_latency",
- "Replay latency", "rl", prio);
- g_perf_counters = plb.create_perf_counters();
- g_ceph_context->get_perfcounters_collection()->add(g_perf_counters);
+ {
+ PerfCountersBuilder plb(g_ceph_context, "rbd_mirror",
+ rbd::mirror::l_rbd_mirror_journal_first,
+ rbd::mirror::l_rbd_mirror_journal_last);
+ plb.add_u64_counter(rbd::mirror::l_rbd_mirror_replay, "replay", "Replays",
+ "r", prio);
+ plb.add_u64_counter(rbd::mirror::l_rbd_mirror_replay_bytes, "replay_bytes",
+ "Replayed data", "rb", prio, unit_t(UNIT_BYTES));
+ plb.add_time_avg(rbd::mirror::l_rbd_mirror_replay_latency, "replay_latency",
+ "Replay latency", "rl", prio);
+ g_journal_perf_counters = plb.create_perf_counters();
+ }
+ {
+ PerfCountersBuilder plb(g_ceph_context, "rbd_mirror_snapshot",
+ rbd::mirror::l_rbd_mirror_snapshot_first,
+ rbd::mirror::l_rbd_mirror_snapshot_last);
+ plb.add_u64_counter(rbd::mirror::l_rbd_mirror_snapshot_replay_snapshots,
+ "snapshots", "Snapshots", "r", prio);
+ plb.add_time_avg(rbd::mirror::l_rbd_mirror_snapshot_replay_snapshots_time,
+ "snapshots_time", "Snapshots time", "rl", prio);
+ plb.add_u64_counter(rbd::mirror::l_rbd_mirror_snapshot_replay_bytes,
+ "replay_bytes", "Replayed data", "rb", prio,
+ unit_t(UNIT_BYTES));
+ g_snapshot_perf_counters = plb.create_perf_counters();
+ }
+ g_ceph_context->get_perfcounters_collection()->add(g_journal_perf_counters);
+ g_ceph_context->get_perfcounters_collection()->add(g_snapshot_perf_counters);
mirror = new rbd::mirror::Mirror(g_ceph_context, cmd_args);
int r = mirror->init();
unregister_async_signal_handler(SIGTERM, handle_signal);
shutdown_async_signal_handler();
- g_ceph_context->get_perfcounters_collection()->remove(g_perf_counters);
+ g_ceph_context->get_perfcounters_collection()->remove(g_journal_perf_counters);
+ g_ceph_context->get_perfcounters_collection()->remove(g_snapshot_perf_counters);
delete mirror;
- delete g_perf_counters;
+ delete g_journal_perf_counters;
+ delete g_snapshot_perf_counters;
return r < 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}