diff --git a/ceph.spec.in b/ceph.spec.in index 14c1420af40..a82758bf458 100644 --- a/ceph.spec.in +++ b/ceph.spec.in @@ -336,7 +336,6 @@ BuildRequires: hostname BuildRequires: jq BuildRequires: libuuid-devel BuildRequires: python%{python3_pkgversion}-bcrypt -BuildRequires: python%{python3_pkgversion}-pecan BuildRequires: python%{python3_pkgversion}-requests BuildRequires: python%{python3_pkgversion}-dateutil BuildRequires: python%{python3_pkgversion}-coverage @@ -469,7 +468,6 @@ BuildRequires: xmlsec1-openssl-devel BuildRequires: python%{python3_pkgversion}-cherrypy BuildRequires: python%{python3_pkgversion}-routes BuildRequires: python%{python3_pkgversion}-scipy -BuildRequires: python%{python3_pkgversion}-werkzeug BuildRequires: python%{python3_pkgversion}-pyOpenSSL %endif BuildRequires: jsonnet @@ -480,7 +478,6 @@ BuildRequires: libxmlsec1-nss1 BuildRequires: libxmlsec1-openssl1 BuildRequires: python%{python3_pkgversion}-CherryPy BuildRequires: python%{python3_pkgversion}-Routes -BuildRequires: python%{python3_pkgversion}-Werkzeug BuildRequires: python%{python3_pkgversion}-numpy-devel BuildRequires: xmlsec1-devel BuildRequires: xmlsec1-openssl-devel @@ -705,7 +702,6 @@ Recommends: python%{python3_pkgversion}-grpcio-tools %if 0%{?suse_version} Requires: python%{python3_pkgversion}-CherryPy Requires: python%{python3_pkgversion}-Routes -Requires: python%{python3_pkgversion}-Werkzeug Recommends: python%{python3_pkgversion}-python3-saml %endif %description mgr-dashboard @@ -738,7 +734,6 @@ Group: System/Filesystems %endif Requires: python%{python3_pkgversion}-bcrypt Requires: python%{python3_pkgversion}-packaging -Requires: python%{python3_pkgversion}-pecan Requires: python%{python3_pkgversion}-pyOpenSSL Requires: python%{python3_pkgversion}-requests Requires: python%{python3_pkgversion}-dateutil @@ -746,12 +741,10 @@ Requires: python%{python3_pkgversion}-setuptools %if 0%{?fedora} || 0%{?rhel} >= 8 || 0%{?openEuler} Requires: python%{python3_pkgversion}-cherrypy Requires: python%{python3_pkgversion}-pyyaml -Requires: python%{python3_pkgversion}-werkzeug %endif %if 0%{?suse_version} Requires: python%{python3_pkgversion}-CherryPy Requires: python%{python3_pkgversion}-PyYAML -Requires: python%{python3_pkgversion}-Werkzeug %endif # RHEL8 has python 3.6 and that lacks dataclasses in the stdlib, so pull in the # backport dataclasses module instead. @@ -1996,7 +1989,6 @@ fi %{_datadir}/ceph/mgr/progress %{_datadir}/ceph/mgr/prometheus %{_datadir}/ceph/mgr/rbd_support -%{_datadir}/ceph/mgr/restful %{_datadir}/ceph/mgr/rgw %{_datadir}/ceph/mgr/selftest %{_datadir}/ceph/mgr/smb diff --git a/debian/ceph-mgr-modules-core.install b/debian/ceph-mgr-modules-core.install index f2ad7452ac3..40fbd6f0bf5 100644 --- a/debian/ceph-mgr-modules-core.install +++ b/debian/ceph-mgr-modules-core.install @@ -15,8 +15,6 @@ usr/share/ceph/mgr/pg_autoscaler usr/share/ceph/mgr/progress usr/share/ceph/mgr/prometheus usr/share/ceph/mgr/rbd_support -usr/share/ceph/mgr/rgw -usr/share/ceph/mgr/restful usr/share/ceph/mgr/selftest usr/share/ceph/mgr/snap_schedule usr/share/ceph/mgr/stats diff --git a/debian/ceph-mgr-modules-core.requires b/debian/ceph-mgr-modules-core.requires index eeeca20bcff..07769e866f8 100644 --- a/debian/ceph-mgr-modules-core.requires +++ b/debian/ceph-mgr-modules-core.requires @@ -1,7 +1,5 @@ natsort CherryPy packaging -pecan -werkzeug requests python-dateutil diff --git a/debian/control b/debian/control index 36da5efd69a..d31a82bbc75 100644 --- a/debian/control +++ b/debian/control @@ -94,7 +94,6 @@ Build-Depends: automake, python3-all-dev, python3-cherrypy3, python3-natsort, - python3-pecan , python3-bcrypt , tox , python3-coverage , @@ -107,7 +106,6 @@ Build-Depends: automake, python3-setuptools, python3-sphinx, python3-venv, - python3-werkzeug , python3-yaml, ragel , socat , diff --git a/doc/_ext/ceph_commands.py b/doc/_ext/ceph_commands.py index 3822a879293..0697c71f0e1 100644 --- a/doc/_ext/ceph_commands.py +++ b/doc/_ext/ceph_commands.py @@ -285,12 +285,6 @@ class CephMgrCommands(Directive): # make diskprediction_local happy mock_imports += ['numpy', 'scipy'] - # make restful happy - mock_imports += ['pecan', - 'pecan.rest', - 'pecan.hooks', - 'werkzeug', - 'werkzeug.serving'] for m in mock_imports: args = {} diff --git a/doc/_ext/ceph_confval.py b/doc/_ext/ceph_confval.py index cde538b45c9..da93d1b415c 100644 --- a/doc/_ext/ceph_confval.py +++ b/doc/_ext/ceph_confval.py @@ -289,12 +289,6 @@ class CephOption(ObjectDescription): # make diskprediction_local happy mock_imports += ['numpy', 'scipy'] - # make restful happy - mock_imports += ['pecan', - 'pecan.rest', - 'pecan.hooks', - 'werkzeug', - 'werkzeug.serving'] for m in mock_imports: args = {} diff --git a/doc/dev/developer_guide/dash-devel.rst b/doc/dev/developer_guide/dash-devel.rst index 2285690e383..e65e846cb5d 100644 --- a/doc/dev/developer_guide/dash-devel.rst +++ b/doc/dev/developer_guide/dash-devel.rst @@ -161,8 +161,6 @@ dashboard and its URLs:: dashboard urls: https://192.168.178.84:41259, https://192.168.178.84:43259, https://192.168.178.84:45259 w/ user/pass: admin / admin - restful urls: https://192.168.178.84:42259, https://192.168.178.84:44259, https://192.168.178.84:46259 - w/ user/pass: admin / 598da51f-8cd1-4161-a970-b2944d5ad200 During development (especially in backend development), you also want to check on occasions if the dashboard manager module is still running. To do so @@ -175,8 +173,7 @@ should look similar to the following output: $ ./bin/ceph mgr services { - "dashboard": "https://home:41931/", - "restful": "https://home:42931/" + "dashboard": "https://home:41931/" } By default, this environment uses a randomly chosen port for Ceph Dashboard diff --git a/doc/mgr/administrator.rst b/doc/mgr/administrator.rst index d59b013aa57..18f3f0f2190 100644 --- a/doc/mgr/administrator.rst +++ b/doc/mgr/administrator.rst @@ -100,7 +100,6 @@ Here is an example of enabling the :term:`Dashboard` module: $ ceph mgr module ls { "enabled_modules": [ - "restful", "status" ], "disabled_modules": [ @@ -112,7 +111,6 @@ Here is an example of enabling the :term:`Dashboard` module: $ ceph mgr module ls { "enabled_modules": [ - "restful", "status", "dashboard" ], @@ -122,8 +120,7 @@ Here is an example of enabling the :term:`Dashboard` module: $ ceph mgr services { - "dashboard": "http://myserver.com:7789/", - "restful": "https://myserver.com:8789/" + "dashboard": "http://myserver.com:7789/" } diff --git a/doc/mgr/index.rst b/doc/mgr/index.rst index e74145d4f8e..1c47e3a8efc 100644 --- a/doc/mgr/index.rst +++ b/doc/mgr/index.rst @@ -33,7 +33,6 @@ sensible. Alerts module DiskPrediction module Local pool module - RESTful module Zabbix module Prometheus module Influx module diff --git a/doc/mgr/restful.rst b/doc/mgr/restful.rst deleted file mode 100644 index c36f2cd0dc5..00000000000 --- a/doc/mgr/restful.rst +++ /dev/null @@ -1,202 +0,0 @@ -Restful Module -============== - -RESTful module offers the REST API access to the status of the cluster -over an SSL-secured connection. - -Enabling --------- - -The *restful* module is enabled with:: - - ceph mgr module enable restful - -You will also need to configure an SSL certificate below before the -API endpoint is available. By default the module will accept HTTPS -requests on port ``8003`` on all IPv4 and IPv6 addresses on the host. - -Securing --------- - -All connections to *restful* are secured with SSL. You can generate a -self-signed certificate with the command:: - - ceph restful create-self-signed-cert - -Note that with a self-signed certificate most clients will need a flag -to allow a connection and/or suppress warning messages. For example, -if the ``ceph-mgr`` daemon is on the same host,:: - - curl -k https://localhost:8003/ - -To properly secure a deployment, a certificate that is signed by the -organization's certificate authority should be used. For example, a key pair -can be generated with a command similar to:: - - openssl req -new -nodes -x509 \ - -subj "/O=IT/CN=ceph-mgr-restful" \ - -days 3650 -keyout restful.key -out restful.crt -extensions v3_ca - -The ``restful.crt`` should then be signed by your organization's CA -(certificate authority). Once that is done, you can set it with:: - - ceph config-key set mgr/restful/$name/crt -i restful.crt - ceph config-key set mgr/restful/$name/key -i restful.key - -where ``$name`` is the name of the ``ceph-mgr`` instance (usually the -hostname). If all manager instances are to share the same certificate, -you can leave off the ``$name`` portion:: - - ceph config-key set mgr/restful/crt -i restful.crt - ceph config-key set mgr/restful/key -i restful.key - - -Configuring IP and port ------------------------ - -Like any other RESTful API endpoint, *restful* binds to an IP and -port. By default, the currently active ``ceph-mgr`` daemon will bind -to port 8003 and any available IPv4 or IPv6 address on the host. - -Since each ``ceph-mgr`` hosts its own instance of *restful*, it may -also be necessary to configure them separately. The IP and port -can be changed via the configuration key facility:: - - ceph config set mgr mgr/restful/$name/server_addr $IP - ceph config set mgr mgr/restful/$name/server_port $PORT - -where ``$name`` is the ID of the ceph-mgr daemon (usually the hostname). - -These settings can also be configured cluster-wide and not manager -specific. For example,:: - - ceph config set mgr mgr/restful/server_addr $IP - ceph config set mgr mgr/restful/server_port $PORT - -If the port is not configured, *restful* will bind to port ``8003``. -If the address it not configured, the *restful* will bind to ``::``, -which corresponds to all available IPv4 and IPv6 addresses. - -Configuring max_request ---------------------------- - -The maximum request size can be configured via a central configuration -option:: - - ceph config set mgr mgr/restful/$name/max_requests $NUM - -where ``$name`` is the ID of the ceph-mgr daemon (usually the hostname). - -.. mgr_module:: restful -.. confval:: max_requests - -.. _creating-an-api-user: - -Creating an API User ------------------------ - -To create an API user, please run the following command:: - - ceph restful create-key - -Replace ```` with the desired name of the user. For example, to create a user named -``api``:: - - $ ceph restful create-key api - 52dffd92-a103-4a10-bfce-5b60f48f764e - -The UUID generated from ``ceph restful create-key api`` acts as the key for the user. - -To list all of your API keys, please run the following command:: - - ceph restful list-keys - -The ``ceph restful list-keys`` command will output in JSON:: - - { - "api": "52dffd92-a103-4a10-bfce-5b60f48f764e" - } - -You can use ``curl`` in order to test your user with the API. Here is an example:: - - curl -k https://api:52dffd92-a103-4a10-bfce-5b60f48f764e@:/server - -In the case above, we are using ``GET`` to fetch information from the ``server`` endpoint. - -Load balancer -------------- - -Please note that *restful* will *only* start on the manager which -is active at that moment. Query the Ceph cluster status to see which -manager is active (e.g., ``ceph mgr dump``). In order to make the -API available via a consistent URL regardless of which manager -daemon is currently active, you may want to set up a load balancer -front-end to direct traffic to whichever manager endpoint is -available. - -Available methods ------------------ - -You can navigate to the ``/doc`` endpoint for full list of available -endpoints and HTTP methods implemented for each endpoint. - -For example, if you want to use the PATCH method of the ``/osd/`` -endpoint to set the state ``up`` of the OSD id ``1``, you can use the -following curl command:: - - echo -En '{"up": true}' | curl --request PATCH --data @- --silent --insecure --user 'https://:/osd/1' - -or you can use python to do so:: - - $ python - >> import requests - >> result = requests.patch( - 'https://:/osd/1', - json={"up": True}, - auth=("", "") - ) - >> print result.json() - -Some of the other endpoints implemented in the *restful* module include - -* ``/config/cluster``: **GET** -* ``/config/osd``: **GET**, **PATCH** -* ``/crush/rule``: **GET** -* ``/mon``: **GET** -* ``/osd``: **GET** -* ``/pool``: **GET**, **POST** -* ``/pool/``: **DELETE**, **GET**, **PATCH** -* ``/request``: **DELETE**, **GET**, **POST** -* ``/request/``: **DELETE**, **GET** -* ``/server``: **GET** - -The ``/request`` endpoint -------------------------- - -You can use the ``/request`` endpoint to poll the state of a request -you scheduled with any **DELETE**, **POST** or **PATCH** method. These -methods are by default asynchronous since it may take longer for them -to finish execution. You can modify this behaviour by appending -``?wait=1`` to the request url. The returned request will then always -be completed. - -The **POST** method of the ``/request`` method provides a passthrough -for the ceph mon commands as defined in ``src/mon/MonCommands.h``. -Let's consider the following command:: - - COMMAND("osd ls " \ - "name=epoch,type=CephInt,range=0,req=false", \ - "show all OSD ids", "osd", "r", "cli,rest") - -The **prefix** is **osd ls**. The optional argument's name is **epoch** -and it is of type ``CephInt``, i.e. ``integer``. This means that you -need to do the following **POST** request to schedule the command:: - - $ python - >> import requests - >> result = requests.post( - 'https://:/request', - json={'prefix': 'osd ls', 'epoch': 0}, - auth=("", "") - ) - >> print result.json() diff --git a/install-deps.sh b/install-deps.sh index d5eafba48cc..909e41957a9 100755 --- a/install-deps.sh +++ b/install-deps.sh @@ -374,10 +374,6 @@ if [ x$(uname)x = xFreeBSDx ]; then security/oath-toolkit \ sysutils/flock \ sysutils/fusefs-libs \ - - # Now use pip to install some extra python modules - pip install pecan - exit else [ $WITH_SEASTAR ] && with_seastar=true || with_seastar=false diff --git a/qa/suites/ceph-ansible/smoke/basic/2-ceph/ceph_ansible.yaml b/qa/suites/ceph-ansible/smoke/basic/2-ceph/ceph_ansible.yaml index 7e7ede3e334..5be06bc6732 100644 --- a/qa/suites/ceph-ansible/smoke/basic/2-ceph/ceph_ansible.yaml +++ b/qa/suites/ceph-ansible/smoke/basic/2-ceph/ceph_ansible.yaml @@ -21,7 +21,6 @@ overrides: ceph_repository: dev ceph_mgr_modules: - status - - restful cephfs_pools: - name: "cephfs_data" pg_num: "64" diff --git a/qa/suites/ceph-ansible/smoke/basic/4-tasks/rest.yaml b/qa/suites/ceph-ansible/smoke/basic/4-tasks/rest.yaml deleted file mode 100644 index 8e389134b92..00000000000 --- a/qa/suites/ceph-ansible/smoke/basic/4-tasks/rest.yaml +++ /dev/null @@ -1,15 +0,0 @@ -tasks: -- exec: - mgr.x: - - systemctl stop ceph-mgr.target - - sleep 5 - - ceph -s -- exec: - mon.a: - - ceph restful create-key admin - - ceph restful create-self-signed-cert - - ceph restful restart -- workunit: - clients: - client.0: - - rest/test-restful.sh diff --git a/qa/suites/cephmetrics/2-ceph/ceph_ansible.yaml b/qa/suites/cephmetrics/2-ceph/ceph_ansible.yaml index 309f5060045..53e2b7fdbc8 100644 --- a/qa/suites/cephmetrics/2-ceph/ceph_ansible.yaml +++ b/qa/suites/cephmetrics/2-ceph/ceph_ansible.yaml @@ -20,7 +20,6 @@ overrides: ceph_repository: dev ceph_mgr_modules: - status - - restful cephfs_pools: - name: "cephfs_data" pg_num: "64" diff --git a/qa/suites/rados/rest/% b/qa/suites/rados/rest/% deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/qa/suites/rados/rest/.qa b/qa/suites/rados/rest/.qa deleted file mode 120000 index a602a0353e7..00000000000 --- a/qa/suites/rados/rest/.qa +++ /dev/null @@ -1 +0,0 @@ -../.qa/ \ No newline at end of file diff --git a/qa/suites/rados/rest/mgr-restful.yaml b/qa/suites/rados/rest/mgr-restful.yaml deleted file mode 100644 index 4901f401d30..00000000000 --- a/qa/suites/rados/rest/mgr-restful.yaml +++ /dev/null @@ -1,31 +0,0 @@ -openstack: -- volumes: # attached to each instance - count: 3 - size: 10 # GB -roles: -- [mon.a, mgr.x, osd.0, osd.1, osd.2, mds.a, client.a] -tasks: -- install: -- ceph: - log-ignorelist: - - overall HEALTH_ - - \(MGR_DOWN\) - - \(PG_ - - \(OSD_ - - \(OBJECT_ - - \(OSDMAP_FLAGS\) - - \(POOL_APP_NOT_ENABLED\) -- exec: - mon.a: - - ceph restful create-key admin - - ceph restful create-self-signed-cert - - ceph restful restart -- workunit: - clients: - client.a: - - rest/test-restful.sh -- exec: - mon.a: - - ceph restful delete-key admin - - ceph restful list-keys | jq ".admin" | grep null - diff --git a/qa/suites/rados/rest/supported-random-distro$ b/qa/suites/rados/rest/supported-random-distro$ deleted file mode 120000 index 7cef21eeffd..00000000000 --- a/qa/suites/rados/rest/supported-random-distro$ +++ /dev/null @@ -1 +0,0 @@ -../basic/supported-random-distro$ \ No newline at end of file diff --git a/qa/tasks/mgr/mgr_test_case.py b/qa/tasks/mgr/mgr_test_case.py index 74b1e9d850c..9032e0e2658 100644 --- a/qa/tasks/mgr/mgr_test_case.py +++ b/qa/tasks/mgr/mgr_test_case.py @@ -108,7 +108,7 @@ class MgrTestCase(CephTestCase): # Unload all non-default plugins loaded = json.loads(cls.mgr_cluster.mon_manager.raw_cluster_cmd( "mgr", "module", "ls", "--format=json-pretty"))['enabled_modules'] - unload_modules = set(loaded) - {"cephadm", "restful"} + unload_modules = set(loaded) - {"cephadm"} for m in unload_modules: cls.mgr_cluster.mon_manager.raw_cluster_cmd( @@ -137,7 +137,7 @@ class MgrTestCase(CephTestCase): raise SkipTest( "Only have {0} manager daemons, {1} are required".format( len(cls.mgr_cluster.mgr_ids), cls.MGRS_REQUIRED)) - + # We expect laggy OSDs in this testing environment so turn off this warning. # See https://tracker.ceph.com/issues/61907 cls.mgr_cluster.mon_manager.raw_cluster_cmd('config', 'set', 'mds', diff --git a/qa/workunits/cephtool/test.sh b/qa/workunits/cephtool/test.sh index cdfff17d837..ad5950367e9 100755 --- a/qa/workunits/cephtool/test.sh +++ b/qa/workunits/cephtool/test.sh @@ -63,7 +63,7 @@ function retry_eagain() for count in $(seq 1 $max) ; do status=0 "$@" > $tmpfile 2>&1 || status=$? - if test $status = 0 || + if test $status = 0 || ! grep --quiet EAGAIN $tmpfile ; then break fi @@ -108,7 +108,7 @@ function check_response() exit 1 fi - if ! grep --quiet -- "$expected_string" $TMPFILE ; then + if ! grep --quiet -- "$expected_string" $TMPFILE ; then echo "Didn't find $expected_string in output" >&2 cat $TMPFILE >&2 exit 1 @@ -696,7 +696,7 @@ function test_auth_profiles() ceph -n client.xx-profile-rd -k client.xx.keyring auth del client.xx-profile-ro ceph -n client.xx-profile-rd -k client.xx.keyring auth del client.xx-profile-rw - + # add a new role-definer with the existing role-definer ceph -n client.xx-profile-rd -k client.xx.keyring \ auth add client.xx-profile-rd2 mon 'allow profile role-definer' @@ -730,7 +730,7 @@ function test_mon_caps() ceph-authtool -n client.bug --cap mon '' $TEMP_DIR/ceph.client.bug.keyring ceph auth add client.bug -i $TEMP_DIR/ceph.client.bug.keyring rados lspools --no-mon-config --keyring $TEMP_DIR/ceph.client.bug.keyring -n client.bug >& $TMPFILE || true - check_response "Permission denied" + check_response "Permission denied" } function test_mon_misc() @@ -780,7 +780,6 @@ function test_mon_misc() ceph mgr dump ceph mgr dump | jq -e '.active_clients[0].name' ceph mgr module ls - ceph mgr module enable restful expect_false ceph mgr module enable foodne ceph mgr module enable foodne --force ceph mgr module disable foodne @@ -1650,7 +1649,7 @@ function test_mon_osd() dump_json=$(ceph osd dump --format=json | \ jq -cM '.osds[] | select(.osd == 0)') [[ "${info_json}" == "${dump_json}" ]] - + info_plain="$(ceph osd info)" dump_plain="$(ceph osd dump | grep '^osd')" [[ "${info_plain}" == "${dump_plain}" ]] @@ -2244,7 +2243,7 @@ function test_mon_pg() # tell osd version # ceph tell osd.0 version - expect_false ceph tell osd.9999 version + expect_false ceph tell osd.9999 version expect_false ceph tell osd.foo version # back to pg stuff @@ -2336,7 +2335,7 @@ function test_mon_osd_pool_set() ceph osd pool get $TEST_POOL_GETSET deep_scrub_interval | expect_false grep '.' ceph osd pool get $TEST_POOL_GETSET recovery_priority | expect_false grep '.' - ceph osd pool set $TEST_POOL_GETSET recovery_priority 5 + ceph osd pool set $TEST_POOL_GETSET recovery_priority 5 ceph osd pool get $TEST_POOL_GETSET recovery_priority | grep 'recovery_priority: 5' ceph osd pool set $TEST_POOL_GETSET recovery_priority -5 ceph osd pool get $TEST_POOL_GETSET recovery_priority | grep 'recovery_priority: -5' @@ -2346,13 +2345,13 @@ function test_mon_osd_pool_set() expect_false ceph osd pool set $TEST_POOL_GETSET recovery_priority 11 ceph osd pool get $TEST_POOL_GETSET recovery_op_priority | expect_false grep '.' - ceph osd pool set $TEST_POOL_GETSET recovery_op_priority 5 + ceph osd pool set $TEST_POOL_GETSET recovery_op_priority 5 ceph osd pool get $TEST_POOL_GETSET recovery_op_priority | grep 'recovery_op_priority: 5' ceph osd pool set $TEST_POOL_GETSET recovery_op_priority 0 ceph osd pool get $TEST_POOL_GETSET recovery_op_priority | expect_false grep '.' ceph osd pool get $TEST_POOL_GETSET scrub_priority | expect_false grep '.' - ceph osd pool set $TEST_POOL_GETSET scrub_priority 5 + ceph osd pool set $TEST_POOL_GETSET scrub_priority 5 ceph osd pool get $TEST_POOL_GETSET scrub_priority | grep 'scrub_priority: 5' ceph osd pool set $TEST_POOL_GETSET scrub_priority 0 ceph osd pool get $TEST_POOL_GETSET scrub_priority | expect_false grep '.' @@ -2386,10 +2385,10 @@ function test_mon_osd_pool_set() ceph osd pool set $TEST_POOL_GETSET size 2 wait_for_clean ceph osd pool set $TEST_POOL_GETSET min_size 2 - + expect_false ceph osd pool set $TEST_POOL_GETSET hashpspool 0 ceph osd pool set $TEST_POOL_GETSET hashpspool 0 --yes-i-really-mean-it - + expect_false ceph osd pool set $TEST_POOL_GETSET hashpspool 1 ceph osd pool set $TEST_POOL_GETSET hashpspool 1 --yes-i-really-mean-it @@ -2587,7 +2586,7 @@ function test_mon_osd_misc() ceph osd map 2>$TMPFILE; check_response 'pool' $? 22 # expect error about unused argument foo - ceph osd ls foo 2>$TMPFILE; check_response 'unused' $? 22 + ceph osd ls foo 2>$TMPFILE; check_response 'unused' $? 22 # expect "not in range" for invalid overload percentage ceph osd reweight-by-utilization 80 2>$TMPFILE; check_response 'higher than 100' $? 22 diff --git a/qa/workunits/rest/test-restful.sh b/qa/workunits/rest/test-restful.sh deleted file mode 100755 index fde0d107a0b..00000000000 --- a/qa/workunits/rest/test-restful.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -ex - -mydir=`dirname $0` - -secret=`ceph config-key get mgr/restful/keys/admin` -url=$(ceph mgr dump|jq -r .services.restful|sed -e 's/\/$//') -echo "url $url secret $secret" -$mydir/test_mgr_rest_api.py $url $secret - -echo $0 OK diff --git a/src/common/options/mgr.yaml.in b/src/common/options/mgr.yaml.in index 773b0d36591..5095710afdf 100644 --- a/src/common/options/mgr.yaml.in +++ b/src/common/options/mgr.yaml.in @@ -152,7 +152,7 @@ options: first started after installation, to populate the list of enabled manager modules. Subsequent updates are done using the 'mgr module [enable|disable]' commands. List may be comma or space separated. - default: restful iostat nfs + default: iostat nfs services: - mon - common diff --git a/src/pybind/mgr/CMakeLists.txt b/src/pybind/mgr/CMakeLists.txt index b2a8ac9a325..f95f10d1b2a 100644 --- a/src/pybind/mgr/CMakeLists.txt +++ b/src/pybind/mgr/CMakeLists.txt @@ -42,7 +42,6 @@ set(mgr_modules progress prometheus rbd_support - restful rgw # rook (optional) selftest diff --git a/src/pybind/mgr/restful/__init__.py b/src/pybind/mgr/restful/__init__.py deleted file mode 100644 index 8f210ac9247..00000000000 --- a/src/pybind/mgr/restful/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .module import Module diff --git a/src/pybind/mgr/restful/api/__init__.py b/src/pybind/mgr/restful/api/__init__.py deleted file mode 100644 index a105dfe87f8..00000000000 --- a/src/pybind/mgr/restful/api/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -from pecan import expose -from pecan.rest import RestController - -from .config import Config -from .crush import Crush -from .doc import Doc -from .mon import Mon -from .osd import Osd -from .pool import Pool -from .perf import Perf -from .request import Request -from .server import Server - - -class Root(RestController): - config = Config() - crush = Crush() - doc = Doc() - mon = Mon() - osd = Osd() - perf = Perf() - pool = Pool() - request = Request() - server = Server() - - @expose(template='json') - def get(self, **kwargs): - """ - Show the basic information for the REST API - This includes values like api version or auth method - """ - return { - 'api_version': 1, - 'auth': - 'Use "ceph restful create-key " to create a key pair, ' - 'pass it as HTTP Basic auth to authenticate', - 'doc': 'See /doc endpoint', - 'info': "Ceph Manager RESTful API server", - } diff --git a/src/pybind/mgr/restful/api/config.py b/src/pybind/mgr/restful/api/config.py deleted file mode 100644 index 5b0e0af96c2..00000000000 --- a/src/pybind/mgr/restful/api/config.py +++ /dev/null @@ -1,86 +0,0 @@ -from pecan import expose, request -from pecan.rest import RestController - -from restful import common, context -from restful.decorators import auth - - -class ConfigOsd(RestController): - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show OSD configuration options - """ - flags = context.instance.get("osd_map")['flags'] - - # pause is a valid osd config command that sets pauserd,pausewr - flags = flags.replace('pauserd,pausewr', 'pause') - - return flags.split(',') - - - @expose(template='json') - @auth - def patch(self, **kwargs): - """ - Modify OSD configuration options - """ - args = request.json - - commands = [] - - valid_flags = set(args.keys()) & set(common.OSD_FLAGS) - invalid_flags = list(set(args.keys()) - valid_flags) - if invalid_flags: - context.instance.log.warning("%s not valid to set/unset", invalid_flags) - - for flag in list(valid_flags): - if args[flag]: - mode = 'set' - else: - mode = 'unset' - - commands.append({ - 'prefix': 'osd ' + mode, - 'key': flag, - }) - - return context.instance.submit_request([commands], **kwargs) - - - -class ConfigClusterKey(RestController): - def __init__(self, key): - self.key = key - - - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show specific configuration option - """ - return context.instance.get("config").get(self.key, None) - - - -class ConfigCluster(RestController): - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show all cluster configuration options - """ - return context.instance.get("config") - - - @expose() - def _lookup(self, key, *remainder): - return ConfigClusterKey(key), remainder - - - -class Config(RestController): - cluster = ConfigCluster() - osd = ConfigOsd() diff --git a/src/pybind/mgr/restful/api/crush.py b/src/pybind/mgr/restful/api/crush.py deleted file mode 100644 index 79f9007b6fd..00000000000 --- a/src/pybind/mgr/restful/api/crush.py +++ /dev/null @@ -1,25 +0,0 @@ -from pecan import expose -from pecan.rest import RestController - -from restful import common, context - -from restful.decorators import auth - - -class CrushRule(RestController): - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show crush rules - """ - crush = context.instance.get('osd_map_crush') - rules = crush['rules'] - - for rule in rules: - rule['osd_count'] = len(common.crush_rule_osds(crush['buckets'], rule)) - - return rules - -class Crush(RestController): - rule = CrushRule() diff --git a/src/pybind/mgr/restful/api/doc.py b/src/pybind/mgr/restful/api/doc.py deleted file mode 100644 index f1038c21b16..00000000000 --- a/src/pybind/mgr/restful/api/doc.py +++ /dev/null @@ -1,15 +0,0 @@ -from pecan import expose -from pecan.rest import RestController - -from restful import context - -import restful - - -class Doc(RestController): - @expose(template='json') - def get(self, **kwargs): - """ - Show documentation information - """ - return context.instance.get_doc_api(restful.api.Root) diff --git a/src/pybind/mgr/restful/api/mon.py b/src/pybind/mgr/restful/api/mon.py deleted file mode 100644 index 20d0336059a..00000000000 --- a/src/pybind/mgr/restful/api/mon.py +++ /dev/null @@ -1,40 +0,0 @@ -from pecan import expose, response -from pecan.rest import RestController - -from restful import context -from restful.decorators import auth - - -class MonName(RestController): - def __init__(self, name): - self.name = name - - - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show the information for the monitor name - """ - mon = [x for x in context.instance.get_mons() - if x['name'] == self.name] - if len(mon) != 1: - response.status = 500 - return {'message': 'Failed to identify the monitor node "{}"'.format(self.name)} - return mon[0] - - - -class Mon(RestController): - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show the information for all the monitors - """ - return context.instance.get_mons() - - - @expose() - def _lookup(self, name, *remainder): - return MonName(name), remainder diff --git a/src/pybind/mgr/restful/api/osd.py b/src/pybind/mgr/restful/api/osd.py deleted file mode 100644 index 8577fae98eb..00000000000 --- a/src/pybind/mgr/restful/api/osd.py +++ /dev/null @@ -1,135 +0,0 @@ -from pecan import expose, request, response -from pecan.rest import RestController - -from restful import common, context -from restful.decorators import auth - - -class OsdIdCommand(RestController): - def __init__(self, osd_id): - self.osd_id = osd_id - - - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show implemented commands for the OSD id - """ - osd = context.instance.get_osd_by_id(self.osd_id) - - if not osd: - response.status = 500 - return {'message': 'Failed to identify the OSD id "{}"'.format(self.osd_id)} - - if osd['up']: - return common.OSD_IMPLEMENTED_COMMANDS - else: - return [] - - - @expose(template='json') - @auth - def post(self, **kwargs): - """ - Run the implemented command for the OSD id - """ - command = request.json.get('command', None) - - osd = context.instance.get_osd_by_id(self.osd_id) - - if not osd: - response.status = 500 - return {'message': 'Failed to identify the OSD id "{}"'.format(self.osd_id)} - - if not osd['up'] or command not in common.OSD_IMPLEMENTED_COMMANDS: - response.status = 500 - return {'message': 'Command "{}" not available'.format(command)} - - return context.instance.submit_request([[{ - 'prefix': 'osd ' + command, - 'who': str(self.osd_id) - }]], **kwargs) - - - -class OsdId(RestController): - def __init__(self, osd_id): - self.osd_id = osd_id - self.command = OsdIdCommand(osd_id) - - - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show the information for the OSD id - """ - osd = context.instance.get_osds(ids=[str(self.osd_id)]) - if len(osd) != 1: - response.status = 500 - return {'message': 'Failed to identify the OSD id "{}"'.format(self.osd_id)} - - return osd[0] - - - @expose(template='json') - @auth - def patch(self, **kwargs): - """ - Modify the state (up, in) of the OSD id or reweight it - """ - args = request.json - - commands = [] - - if 'in' in args: - if args['in']: - commands.append({ - 'prefix': 'osd in', - 'ids': [str(self.osd_id)] - }) - else: - commands.append({ - 'prefix': 'osd out', - 'ids': [str(self.osd_id)] - }) - - if 'up' in args: - if args['up']: - response.status = 500 - return {'message': "It is not valid to set a down OSD to be up"} - else: - commands.append({ - 'prefix': 'osd down', - 'ids': [str(self.osd_id)] - }) - - if 'reweight' in args: - commands.append({ - 'prefix': 'osd reweight', - 'id': self.osd_id, - 'weight': args['reweight'] - }) - - return context.instance.submit_request([commands], **kwargs) - - - -class Osd(RestController): - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show the information for all the OSDs - """ - # Parse request args - # TODO Filter by ids - pool_id = kwargs.get('pool', None) - - return context.instance.get_osds(pool_id) - - - @expose() - def _lookup(self, osd_id, *remainder): - return OsdId(int(osd_id)), remainder diff --git a/src/pybind/mgr/restful/api/perf.py b/src/pybind/mgr/restful/api/perf.py deleted file mode 100644 index c484ac55e44..00000000000 --- a/src/pybind/mgr/restful/api/perf.py +++ /dev/null @@ -1,27 +0,0 @@ -from pecan import expose, request, response -from pecan.rest import RestController - -from restful import context -from restful.decorators import auth, lock, paginate - -import re - -class Perf(RestController): - @expose(template='json') - @paginate - @auth - def get(self, **kwargs): - """ - List all the available performance counters - - Options: - - 'daemon' -- filter by daemon, accepts Python regexp - """ - - counters = context.instance.get_unlabeled_perf_counters() - - if 'daemon' in kwargs: - _re = re.compile(kwargs['daemon']) - counters = {k: v for k, v in counters.items() if _re.match(k)} - - return counters diff --git a/src/pybind/mgr/restful/api/pool.py b/src/pybind/mgr/restful/api/pool.py deleted file mode 100644 index 40de54eb957..00000000000 --- a/src/pybind/mgr/restful/api/pool.py +++ /dev/null @@ -1,140 +0,0 @@ -from pecan import expose, request, response -from pecan.rest import RestController - -from restful import common, context -from restful.decorators import auth - - -class PoolId(RestController): - def __init__(self, pool_id): - self.pool_id = pool_id - - - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show the information for the pool id - """ - pool = context.instance.get_pool_by_id(self.pool_id) - - if not pool: - response.status = 500 - return {'message': 'Failed to identify the pool id "{}"'.format(self.pool_id)} - - # pgp_num is called pg_placement_num, deal with that - if 'pg_placement_num' in pool: - pool['pgp_num'] = pool.pop('pg_placement_num') - return pool - - - @expose(template='json') - @auth - def patch(self, **kwargs): - """ - Modify the information for the pool id - """ - try: - args = request.json - except ValueError: - response.status = 400 - return {'message': 'Bad request: malformed JSON or wrong Content-Type'} - - # Get the pool info for its name - pool = context.instance.get_pool_by_id(self.pool_id) - if not pool: - response.status = 500 - return {'message': 'Failed to identify the pool id "{}"'.format(self.pool_id)} - - # Check for invalid pool args - invalid = common.invalid_pool_args(args) - if invalid: - response.status = 500 - return {'message': 'Invalid arguments found: "{}"'.format(invalid)} - - # Schedule the update request - return context.instance.submit_request(common.pool_update_commands(pool['pool_name'], args), **kwargs) - - - @expose(template='json') - @auth - def delete(self, **kwargs): - """ - Remove the pool data for the pool id - """ - pool = context.instance.get_pool_by_id(self.pool_id) - - if not pool: - response.status = 500 - return {'message': 'Failed to identify the pool id "{}"'.format(self.pool_id)} - - return context.instance.submit_request([[{ - 'prefix': 'osd pool delete', - 'pool': pool['pool_name'], - 'pool2': pool['pool_name'], - 'yes_i_really_really_mean_it': True - }]], **kwargs) - - - -class Pool(RestController): - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show the information for all the pools - """ - pools = context.instance.get('osd_map')['pools'] - - # pgp_num is called pg_placement_num, deal with that - for pool in pools: - if 'pg_placement_num' in pool: - pool['pgp_num'] = pool.pop('pg_placement_num') - - return pools - - - @expose(template='json') - @auth - def post(self, **kwargs): - """ - Create a new pool - Requires name and pg_num dict arguments - """ - args = request.json - - # Check for the required arguments - pool_name = args.pop('name', None) - if pool_name is None: - response.status = 500 - return {'message': 'You need to specify the pool "name" argument'} - - pg_num = args.pop('pg_num', None) - if pg_num is None: - response.status = 500 - return {'message': 'You need to specify the "pg_num" argument'} - - # Run the pool create command first - create_command = { - 'prefix': 'osd pool create', - 'pool': pool_name, - 'pg_num': pg_num - } - - # Check for invalid pool args - invalid = common.invalid_pool_args(args) - if invalid: - response.status = 500 - return {'message': 'Invalid arguments found: "{}"'.format(invalid)} - - # Schedule the creation and update requests - return context.instance.submit_request( - [[create_command]] + - common.pool_update_commands(pool_name, args), - **kwargs - ) - - - @expose() - def _lookup(self, pool_id, *remainder): - return PoolId(int(pool_id)), remainder diff --git a/src/pybind/mgr/restful/api/request.py b/src/pybind/mgr/restful/api/request.py deleted file mode 100644 index 67143ef508d..00000000000 --- a/src/pybind/mgr/restful/api/request.py +++ /dev/null @@ -1,93 +0,0 @@ -from pecan import expose, request, response -from pecan.rest import RestController - -from restful import context -from restful.decorators import auth, lock, paginate - - -class RequestId(RestController): - def __init__(self, request_id): - self.request_id = request_id - - - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show the information for the request id - """ - request = [x for x in context.instance.requests - if x.id == self.request_id] - if len(request) != 1: - response.status = 500 - return {'message': 'Unknown request id "{}"'.format(self.request_id)} - return request[0] - - - @expose(template='json') - @auth - @lock - def delete(self, **kwargs): - """ - Remove the request id from the database - """ - for index in range(len(context.instance.requests)): - if context.instance.requests[index].id == self.request_id: - return context.instance.requests.pop(index) - - # Failed to find the job to cancel - response.status = 500 - return {'message': 'No such request id'} - - - -class Request(RestController): - @expose(template='json') - @paginate - @auth - def get(self, **kwargs): - """ - List all the available requests - """ - return context.instance.requests - - - @expose(template='json') - @auth - @lock - def delete(self, **kwargs): - """ - Remove all the finished requests - """ - num_requests = len(context.instance.requests) - - context.instance.requests = [x for x in context.instance.requests - if not x.is_finished()] - remaining = len(context.instance.requests) - # Return the job statistics - return { - 'cleaned': num_requests - remaining, - 'remaining': remaining, - } - - - @expose(template='json') - @auth - def post(self, **kwargs): - """ - Pass through method to create any request - """ - if isinstance(request.json, list): - if all(isinstance(element, list) for element in request.json): - return context.instance.submit_request(request.json, **kwargs) - - # The request.json has wrong format - response.status = 500 - return {'message': 'The request format should be [[{c1},{c2}]]'} - - return context.instance.submit_request([[request.json]], **kwargs) - - - @expose() - def _lookup(self, request_id, *remainder): - return RequestId(request_id), remainder diff --git a/src/pybind/mgr/restful/api/server.py b/src/pybind/mgr/restful/api/server.py deleted file mode 100644 index 8ce63493754..00000000000 --- a/src/pybind/mgr/restful/api/server.py +++ /dev/null @@ -1,35 +0,0 @@ -from pecan import expose -from pecan.rest import RestController - -from restful import context -from restful.decorators import auth - - -class ServerFqdn(RestController): - def __init__(self, fqdn): - self.fqdn = fqdn - - - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show the information for the server fqdn - """ - return context.instance.get_server(self.fqdn) - - - -class Server(RestController): - @expose(template='json') - @auth - def get(self, **kwargs): - """ - Show the information for all the servers - """ - return context.instance.list_servers() - - - @expose() - def _lookup(self, fqdn, *remainder): - return ServerFqdn(fqdn), remainder diff --git a/src/pybind/mgr/restful/common.py b/src/pybind/mgr/restful/common.py deleted file mode 100644 index 1b957d6b5ec..00000000000 --- a/src/pybind/mgr/restful/common.py +++ /dev/null @@ -1,156 +0,0 @@ -# List of valid osd flags -OSD_FLAGS = [ - 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', - 'norecover', 'noscrub', 'nodeep-scrub', -] - -# Implemented osd commands -OSD_IMPLEMENTED_COMMANDS = [ - 'scrub', 'deep-scrub', 'repair' -] - -# Valid values for the 'var' argument to 'ceph osd pool set' -POOL_PROPERTIES_1 = [ - 'size', 'min_size', 'pg_num', - 'crush_rule', 'hashpspool', -] - -POOL_PROPERTIES_2 = [ - 'pgp_num' -] - -POOL_PROPERTIES = POOL_PROPERTIES_1 + POOL_PROPERTIES_2 - -# Valid values for the 'ceph osd pool set-quota' command -POOL_QUOTA_PROPERTIES = [ - ('quota_max_bytes', 'max_bytes'), - ('quota_max_objects', 'max_objects'), -] - -POOL_ARGS = POOL_PROPERTIES + [x for x,_ in POOL_QUOTA_PROPERTIES] - - -# Transform command to a human readable form -def humanify_command(command): - out = [command['prefix']] - - for arg, val in command.items(): - if arg != 'prefix': - out.append("%s=%s" % (str(arg), str(val))) - - return " ".join(out) - - -def invalid_pool_args(args): - invalid = [] - for arg in args: - if arg not in POOL_ARGS: - invalid.append(arg) - - return invalid - - -def pool_update_commands(pool_name, args): - commands = [[], []] - - # We should increase pgp_num when we are re-setting pg_num - if 'pg_num' in args and 'pgp_num' not in args: - args['pgp_num'] = args['pg_num'] - - # Run the first pool set and quota properties in parallel - for var in POOL_PROPERTIES_1: - if var in args: - commands[0].append({ - 'prefix': 'osd pool set', - 'pool': pool_name, - 'var': var, - 'val': args[var], - }) - - for (var, field) in POOL_QUOTA_PROPERTIES: - if var in args: - commands[0].append({ - 'prefix': 'osd pool set-quota', - 'pool': pool_name, - 'field': field, - 'val': str(args[var]), - }) - - # The second pool set properties need to be run after the first wave - for var in POOL_PROPERTIES_2: - if var in args: - commands[1].append({ - 'prefix': 'osd pool set', - 'pool': pool_name, - 'var': var, - 'val': args[var], - }) - - return commands - -def crush_rule_osds(node_buckets, rule): - nodes_by_id = dict((b['id'], b) for b in node_buckets) - - def _gather_leaf_ids(node_id): - if node_id >= 0: - return set([node_id]) - - result = set() - for item in nodes_by_id[node_id]['items']: - result |= _gather_leaf_ids(item['id']) - - return result - - def _gather_descendent_ids(node, typ): - result = set() - for item in node['items']: - if item['id'] >= 0: - if typ == "osd": - result.add(item['id']) - else: - child_node = nodes_by_id[item['id']] - if child_node['type_name'] == typ: - result.add(child_node['id']) - elif 'items' in child_node: - result |= _gather_descendent_ids(child_node, typ) - - return result - - def _gather_osds(root, steps): - if root['id'] >= 0: - return set([root['id']]) - - osds = set() - step = steps[0] - if step['op'] == 'choose_firstn': - # Choose all descendents of the current node of type 'type' - descendent_ids = _gather_descendent_ids(root, step['type']) - for node_id in descendent_ids: - if node_id >= 0: - osds.add(node_id) - else: - osds |= _gather_osds(nodes_by_id[node_id], steps[1:]) - elif step['op'] == 'chooseleaf_firstn': - # Choose all descendents of the current node of type 'type', - # and select all leaves beneath those - descendent_ids = _gather_descendent_ids(root, step['type']) - for node_id in descendent_ids: - if node_id >= 0: - osds.add(node_id) - else: - for desc_node in nodes_by_id[node_id]['items']: - # Short circuit another iteration to find the emit - # and assume anything we've done a chooseleaf on - # is going to be part of the selected set of osds - osds |= _gather_leaf_ids(desc_node['id']) - elif step['op'] == 'emit': - if root['id'] >= 0: - osds |= root['id'] - - return osds - - osds = set() - for i, step in enumerate(rule['steps']): - if step['op'] == 'take': - osds |= _gather_osds(nodes_by_id[step['item']], rule['steps'][i + 1:]) - return osds diff --git a/src/pybind/mgr/restful/context.py b/src/pybind/mgr/restful/context.py deleted file mode 100644 index a05ea8548df..00000000000 --- a/src/pybind/mgr/restful/context.py +++ /dev/null @@ -1,2 +0,0 @@ -# Global instance to share -instance = None diff --git a/src/pybind/mgr/restful/decorators.py b/src/pybind/mgr/restful/decorators.py deleted file mode 100644 index 11840a9913a..00000000000 --- a/src/pybind/mgr/restful/decorators.py +++ /dev/null @@ -1,81 +0,0 @@ - -from pecan import request, response -from base64 import b64decode -from functools import wraps - -import traceback - -from . import context - - -# Handle authorization -def auth(f): - @wraps(f) - def decorated(*args, **kwargs): - if not context.instance.enable_auth: - return f(*args, **kwargs) - - if not request.authorization: - response.status = 401 - response.headers['WWW-Authenticate'] = 'Basic realm="Login Required"' - return {'message': 'auth: No HTTP username/password'} - - username, password = b64decode(request.authorization[1]).decode('utf-8').split(':') - - # Check that the username exists - if username not in context.instance.keys: - response.status = 401 - response.headers['WWW-Authenticate'] = 'Basic realm="Login Required"' - return {'message': 'auth: No such user'} - - # Check the password - if context.instance.keys[username] != password: - response.status = 401 - response.headers['WWW-Authenticate'] = 'Basic realm="Login Required"' - return {'message': 'auth: Incorrect password'} - - return f(*args, **kwargs) - return decorated - - -# Helper function to lock the function -def lock(f): - @wraps(f) - def decorated(*args, **kwargs): - with context.instance.requests_lock: - return f(*args, **kwargs) - return decorated - - -# Support ?page=N argument -def paginate(f): - @wraps(f) - def decorated(*args, **kwargs): - _out = f(*args, **kwargs) - - # Do not modify anything without a specific request - if not 'page' in kwargs: - return _out - - # A pass-through for errors, etc - if not isinstance(_out, list): - return _out - - # Parse the page argument - _page = kwargs['page'] - try: - _page = int(_page) - except ValueError: - response.status = 500 - return {'message': 'The requested page is not an integer'} - - # Raise _page so that 0 is the first page and -1 is the last - _page += 1 - - if _page > 0: - _page *= 100 - else: - _page = len(_out) - (_page*100) - - return _out[_page - 100: _page] - return decorated diff --git a/src/pybind/mgr/restful/hooks.py b/src/pybind/mgr/restful/hooks.py deleted file mode 100644 index c57cbcd404d..00000000000 --- a/src/pybind/mgr/restful/hooks.py +++ /dev/null @@ -1,10 +0,0 @@ - -from pecan.hooks import PecanHook - -import traceback - -from . import context - -class ErrorHook(PecanHook): - def on_error(self, stat, exc): - context.instance.log.error(str(traceback.format_exc())) diff --git a/src/pybind/mgr/restful/module.py b/src/pybind/mgr/restful/module.py deleted file mode 100644 index ad76473afd0..00000000000 --- a/src/pybind/mgr/restful/module.py +++ /dev/null @@ -1,633 +0,0 @@ -""" -A RESTful API for Ceph -""" - -import os -import json -import time -import errno -import inspect -import tempfile -import threading -import traceback -import socket -import fcntl -from typing import cast - -from . import common -from . import context - -from uuid import uuid4 -from pecan import jsonify, make_app -from OpenSSL import crypto -from pecan.rest import RestController -from werkzeug.serving import make_server, make_ssl_devcert - -from .hooks import ErrorHook -from mgr_module import MgrModule, CommandResult, NotifyType, Option -from mgr_util import build_url - - -class CannotServe(Exception): - pass - - -class CommandsRequest(object): - """ - This class handles parallel as well as sequential execution of - commands. The class accept a list of iterables that should be - executed sequentially. Each iterable can contain several commands - that can be executed in parallel. - - Example: - [[c1,c2],[c3,c4]] - - run c1 and c2 in parallel - - wait for them to finish - - run c3 and c4 in parallel - - wait for them to finish - """ - - - def __init__(self, commands_arrays): - self.id = str(id(self)) - - # Filter out empty sub-requests - commands_arrays = [x for x in commands_arrays - if len(x) != 0] - - self.running = [] - self.waiting = commands_arrays[1:] - self.finished = [] - self.failed = [] - - self.lock = threading.RLock() - if not len(commands_arrays): - # Nothing to run - return - - # Process first iteration of commands_arrays in parallel - results = self.run(commands_arrays[0]) - - self.running.extend(results) - - - def run(self, commands): - """ - A static method that will execute the given list of commands in - parallel and will return the list of command results. - """ - - # Gather the results (in parallel) - results = [] - for index, command in enumerate(commands): - tag = '%s:%s:%d' % (__name__, self.id, index) - - # Store the result - result = CommandResult(tag) - result.command = common.humanify_command(command) - results.append(result) - - # Run the command - context.instance.send_command(result, 'mon', '', json.dumps(command), tag) - - return results - - - def next(self): - with self.lock: - if not self.waiting: - # Nothing to run - return - - # Run a next iteration of commands - commands = self.waiting[0] - self.waiting = self.waiting[1:] - - self.running.extend(self.run(commands)) - - - def finish(self, tag): - with self.lock: - for index in range(len(self.running)): - if self.running[index].tag == tag: - if self.running[index].r == 0: - self.finished.append(self.running.pop(index)) - else: - self.failed.append(self.running.pop(index)) - return True - - # No such tag found - return False - - - def is_running(self, tag): - for result in self.running: - if result.tag == tag: - return True - return False - - - def is_ready(self): - with self.lock: - return not self.running and self.waiting - - - def is_waiting(self): - return bool(self.waiting) - - - def is_finished(self): - with self.lock: - return not self.running and not self.waiting - - - def has_failed(self): - return bool(self.failed) - - - def get_state(self): - with self.lock: - if not self.is_finished(): - return "pending" - - if self.has_failed(): - return "failed" - - return "success" - - - def __json__(self): - return { - 'id': self.id, - 'running': [ - { - 'command': x.command, - 'outs': x.outs, - 'outb': x.outb, - } for x in self.running - ], - 'finished': [ - { - 'command': x.command, - 'outs': x.outs, - 'outb': x.outb, - } for x in self.finished - ], - 'waiting': [ - [common.humanify_command(y) for y in x] - for x in self.waiting - ], - 'failed': [ - { - 'command': x.command, - 'outs': x.outs, - 'outb': x.outb, - } for x in self.failed - ], - 'is_waiting': self.is_waiting(), - 'is_finished': self.is_finished(), - 'has_failed': self.has_failed(), - 'state': self.get_state(), - } - - - -class Module(MgrModule): - MODULE_OPTIONS = [ - Option(name='server_addr'), - Option(name='server_port'), - Option(name='key_file'), - Option(name='enable_auth', - type='bool', - default=True), - Option(name='max_requests', - type='int', - default=500, - desc='Maximum number of requests to keep in memory. ' - ' When new request comes in, the oldest request will be removed if the number of requests exceeds the max request number.' - 'if un-finished request is removed, error message will be logged in the ceph-mgr log.'), - ] - - COMMANDS = [ - { - "cmd": "restful create-key name=key_name,type=CephString", - "desc": "Create an API key with this name", - "perm": "rw" - }, - { - "cmd": "restful delete-key name=key_name,type=CephString", - "desc": "Delete an API key with this name", - "perm": "rw" - }, - { - "cmd": "restful list-keys", - "desc": "List all API keys", - "perm": "r" - }, - { - "cmd": "restful create-self-signed-cert", - "desc": "Create localized self signed certificate", - "perm": "rw" - }, - { - "cmd": "restful restart", - "desc": "Restart API server", - "perm": "rw" - }, - ] - - NOTIFY_TYPES = [NotifyType.command] - - def __init__(self, *args, **kwargs): - super(Module, self).__init__(*args, **kwargs) - context.instance = self - - self.requests = [] - self.requests_lock = threading.RLock() - - self.keys = {} - self.enable_auth = True - - self.server = None - - self.stop_server = False - self.serve_event = threading.Event() - self.max_requests = cast(int, self.get_localized_module_option('max_requests', 500)) - - - def serve(self): - self.log.debug('serve enter') - while not self.stop_server: - try: - self._serve() - self.server.socket.close() - except CannotServe as cs: - self.log.warning("server not running: %s", cs) - except: - self.log.error(str(traceback.format_exc())) - - # Wait and clear the threading event - self.serve_event.wait() - self.serve_event.clear() - self.log.debug('serve exit') - - def refresh_keys(self): - self.keys = {} - rawkeys = self.get_store_prefix('keys/') or {} - for k, v in rawkeys.items(): - self.keys[k[5:]] = v # strip of keys/ prefix - - def _serve(self): - # Load stored authentication keys - self.refresh_keys() - - jsonify._instance = jsonify.GenericJSON( - sort_keys=True, - indent=4, - separators=(',', ': '), - ) - - server_addr = self.get_localized_module_option('server_addr', '::') - if server_addr is None: - raise CannotServe('no server_addr configured; try "ceph config-key set mgr/restful/server_addr "') - - server_port = int(self.get_localized_module_option('server_port', '8003')) - self.log.info('server_addr: %s server_port: %d', - server_addr, server_port) - - cert = self.get_localized_store("crt") - if cert is not None: - cert_tmp = tempfile.NamedTemporaryFile() - cert_tmp.write(cert.encode('utf-8')) - cert_tmp.flush() - cert_fname = cert_tmp.name - else: - cert_fname = self.get_localized_store('crt_file') - - pkey = self.get_localized_store("key") - if pkey is not None: - pkey_tmp = tempfile.NamedTemporaryFile() - pkey_tmp.write(pkey.encode('utf-8')) - pkey_tmp.flush() - pkey_fname = pkey_tmp.name - else: - pkey_fname = self.get_localized_module_option('key_file') - - self.enable_auth = self.get_localized_module_option('enable_auth', True) - - if not cert_fname or not pkey_fname: - raise CannotServe('no certificate configured') - if not os.path.isfile(cert_fname): - raise CannotServe('certificate %s does not exist' % cert_fname) - if not os.path.isfile(pkey_fname): - raise CannotServe('private key %s does not exist' % pkey_fname) - - # Publish the URI that others may use to access the service we're - # about to start serving - addr = self.get_mgr_ip() if server_addr == "::" else server_addr - self.set_uri(build_url(scheme='https', host=addr, port=server_port, path='/')) - - # Create the HTTPS werkzeug server serving pecan app - self.server = make_server( - host=server_addr, - port=server_port, - app=make_app( - root='restful.api.Root', - hooks = [ErrorHook()], # use a callable if pecan >= 0.3.2 - ), - ssl_context=(cert_fname, pkey_fname), - ) - sock_fd_flag = fcntl.fcntl(self.server.socket.fileno(), fcntl.F_GETFD) - if not (sock_fd_flag & fcntl.FD_CLOEXEC): - self.log.debug("set server socket close-on-exec") - fcntl.fcntl(self.server.socket.fileno(), fcntl.F_SETFD, sock_fd_flag | fcntl.FD_CLOEXEC) - if self.stop_server: - self.log.debug('made server, but stop flag set') - else: - self.log.debug('made server, serving forever') - self.server.serve_forever() - - - def shutdown(self): - self.log.debug('shutdown enter') - try: - self.stop_server = True - if self.server: - self.log.debug('calling server.shutdown') - self.server.shutdown() - self.log.debug('called server.shutdown') - self.serve_event.set() - except: - self.log.error(str(traceback.format_exc())) - raise - self.log.debug('shutdown exit') - - - def restart(self): - try: - if self.server: - self.server.shutdown() - self.serve_event.set() - except: - self.log.error(str(traceback.format_exc())) - - - def notify(self, notify_type: NotifyType, tag: str): - try: - self._notify(notify_type, tag) - except: - self.log.error(str(traceback.format_exc())) - - - def _notify(self, notify_type: NotifyType, tag): - if notify_type != NotifyType.command: - self.log.debug("Unhandled notification type '%s'", notify_type) - return - # we can safely skip all the sequential commands - if tag == 'seq': - return - try: - with self.requests_lock: - request = next(x for x in self.requests if x.is_running(tag)) - request.finish(tag) - if request.is_ready(): - request.next() - except StopIteration: - # the command was not issued by me - pass - - def config_notify(self): - self.enable_auth = self.get_localized_module_option('enable_auth', True) - - - def create_self_signed_cert(self): - # create a key pair - pkey = crypto.PKey() - pkey.generate_key(crypto.TYPE_RSA, 2048) - - # create a self-signed cert - cert = crypto.X509() - cert.get_subject().O = "IT" - cert.get_subject().CN = "ceph-restful" - cert.set_serial_number(int(uuid4())) - cert.gmtime_adj_notBefore(0) - cert.gmtime_adj_notAfter(10*365*24*60*60) - cert.set_issuer(cert.get_subject()) - cert.set_pubkey(pkey) - cert.sign(pkey, 'sha512') - - return ( - crypto.dump_certificate(crypto.FILETYPE_PEM, cert), - crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey) - ) - - - def handle_command(self, inbuf, command): - self.log.warning("Handling command: '%s'" % str(command)) - if command['prefix'] == "restful create-key": - if command['key_name'] in self.keys: - return 0, self.keys[command['key_name']], "" - - else: - key = str(uuid4()) - self.keys[command['key_name']] = key - self.set_store('keys/' + command['key_name'], key) - - return ( - 0, - self.keys[command['key_name']], - "", - ) - - elif command['prefix'] == "restful delete-key": - if command['key_name'] in self.keys: - del self.keys[command['key_name']] - self.set_store('keys/' + command['key_name'], None) - - return ( - 0, - "", - "", - ) - - elif command['prefix'] == "restful list-keys": - self.refresh_keys() - return ( - 0, - json.dumps(self.keys, indent=4, sort_keys=True), - "", - ) - - elif command['prefix'] == "restful create-self-signed-cert": - cert, pkey = self.create_self_signed_cert() - self.set_store(self.get_mgr_id() + '/crt', cert.decode('utf-8')) - self.set_store(self.get_mgr_id() + '/key', pkey.decode('utf-8')) - - self.restart() - return ( - 0, - "Restarting RESTful API server...", - "" - ) - - elif command['prefix'] == 'restful restart': - self.restart(); - return ( - 0, - "Restarting RESTful API server...", - "" - ) - - else: - return ( - -errno.EINVAL, - "", - "Command not found '{0}'".format(command['prefix']) - ) - - - def get_doc_api(self, root, prefix=''): - doc = {} - for _obj in dir(root): - obj = getattr(root, _obj) - - if isinstance(obj, RestController): - doc.update(self.get_doc_api(obj, prefix + '/' + _obj)) - - if getattr(root, '_lookup', None) and isinstance(root._lookup('0')[0], RestController): - doc.update(self.get_doc_api(root._lookup('0')[0], prefix + '/')) - - prefix = prefix or '/' - - doc[prefix] = {} - for method in 'get', 'post', 'patch', 'delete': - if getattr(root, method, None): - doc[prefix][method.upper()] = inspect.getdoc(getattr(root, method)).split('\n') - - if len(doc[prefix]) == 0: - del doc[prefix] - - return doc - - - def get_mons(self): - mon_map_mons = self.get('mon_map')['mons'] - mon_status = json.loads(self.get('mon_status')['json']) - - # Add more information - for mon in mon_map_mons: - mon['in_quorum'] = mon['rank'] in mon_status['quorum'] - mon['server'] = self.get_metadata("mon", mon['name'])['hostname'] - mon['leader'] = mon['rank'] == mon_status['quorum'][0] - - return mon_map_mons - - - def get_osd_pools(self): - osds = dict(map(lambda x: (x['osd'], []), self.get('osd_map')['osds'])) - pools = dict(map(lambda x: (x['pool'], x), self.get('osd_map')['pools'])) - crush = self.get('osd_map_crush') - crush_rules = crush['rules'] - - osds_by_pool = {} - for pool_id, pool in pools.items(): - pool_osds = None - for rule in [r for r in crush_rules if r['rule_id'] == pool['crush_rule']]: - pool_osds = common.crush_rule_osds(crush['buckets'], rule) - - osds_by_pool[pool_id] = pool_osds - - for pool_id in pools.keys(): - for in_pool_id in osds_by_pool[pool_id]: - osds[in_pool_id].append(pool_id) - - return osds - - - def get_osds(self, pool_id=None, ids=None): - # Get data - osd_map = self.get('osd_map') - osd_metadata = self.get('osd_metadata') - - # Update the data with the additional info from the osd map - osds = osd_map['osds'] - - # Filter by osd ids - if ids is not None: - osds = [x for x in osds if str(x['osd']) in ids] - - # Get list of pools per osd node - pools_map = self.get_osd_pools() - - # map osd IDs to reweight - reweight_map = dict([ - (x.get('id'), x.get('reweight', None)) - for x in self.get('osd_map_tree')['nodes'] - ]) - - # Build OSD data objects - for osd in osds: - osd['pools'] = pools_map[osd['osd']] - osd['server'] = osd_metadata.get(str(osd['osd']), {}).get('hostname', None) - - osd['reweight'] = reweight_map.get(osd['osd'], 0.0) - - if osd['up']: - osd['valid_commands'] = common.OSD_IMPLEMENTED_COMMANDS - else: - osd['valid_commands'] = [] - - # Filter by pool - if pool_id: - pool_id = int(pool_id) - osds = [x for x in osds if pool_id in x['pools']] - - return osds - - - def get_osd_by_id(self, osd_id): - osd = [x for x in self.get('osd_map')['osds'] - if x['osd'] == osd_id] - - if len(osd) != 1: - return None - - return osd[0] - - - def get_pool_by_id(self, pool_id): - pool = [x for x in self.get('osd_map')['pools'] - if x['pool'] == pool_id] - - if len(pool) != 1: - return None - - return pool[0] - - - def submit_request(self, _request, **kwargs): - with self.requests_lock: - request = CommandsRequest(_request) - self.requests.append(request) - if len(self.requests) > self.max_requests: - req_to_trim = 0 - for i, req in enumerate(self.requests): - if req.is_finished(): - self.log.error("Trimmed one finished request due to exceeded maximum requests limit") - req_to_trim = i - break - else: - self.log.error("Trimmed the oldest unfinished request due to exceeded maximum requests limit") - self.requests.pop(req_to_trim) - if kwargs.get('wait', 0): - while not request.is_finished(): - time.sleep(0.001) - return request - - - def run_command(self, command): - # tag with 'seq' so that we can ignore these in notify function - result = CommandResult('seq') - - self.send_command(result, 'mon', '', json.dumps(command), 'seq') - return result.wait() diff --git a/src/vstart.sh b/src/vstart.sh index 4b62db677d1..45d3ba9b070 100755 --- a/src/vstart.sh +++ b/src/vstart.sh @@ -192,7 +192,6 @@ if [[ "$(get_cmake_variable WITH_MGR_DASHBOARD_FRONTEND)" != "ON" ]] || debug echo "ceph-mgr dashboard not built - disabling." with_mgr_dashboard=false fi -with_mgr_restful=false kstore_path= declare -a block_devs @@ -205,7 +204,6 @@ VSTART_SEC="client.vstart.sh" MON_ADDR="" DASH_URLS="" -RESTFUL_URLS="" conf_fn="$CEPH_CONF_PATH/ceph.conf" keyring_fn="$CEPH_CONF_PATH/keyring" @@ -558,9 +556,6 @@ case $1 in --without-dashboard) with_mgr_dashboard=false ;; - --with-restful) - with_mgr_restful=true - ;; --seastore-device-size) seastore_size="$2" shift @@ -782,9 +777,6 @@ prepare_conf() { if $with_mgr_dashboard; then mgr_modules+=" dashboard" fi - if $with_mgr_restful; then - mgr_modules+=" restful" - fi local msgr_conf='' if [ $msgr -eq 21 ]; then @@ -1010,7 +1002,7 @@ EOF ; see src/vstart.sh for more info public bind addr = EOF - fi + fi } write_logrotate_conf() { @@ -1254,22 +1246,6 @@ EOF fi } -create_mgr_restful_secret() { - while ! ceph_adm -h | grep -c -q ^restful ; do - debug echo 'waiting for mgr restful module to start' - sleep 1 - done - local secret_file - if ceph_adm restful create-self-signed-cert > /dev/null; then - secret_file=`mktemp` - ceph_adm restful create-key admin -o $secret_file - RESTFUL_SECRET=`cat $secret_file` - rm $secret_file - else - debug echo MGR Restful is not working, perhaps the package is not installed? - fi -} - start_mgr() { local mgr=0 local ssl=${DASHBOARD_SSL:-1} @@ -1309,15 +1285,7 @@ EOF MGR_PORT=$(($MGR_PORT + 1000)) ceph_adm config set mgr mgr/prometheus/$name/server_port $PROMETHEUS_PORT --force PROMETHEUS_PORT=$(($PROMETHEUS_PORT + 1000)) - - ceph_adm config set mgr mgr/restful/$name/server_port $MGR_PORT --force - if [ $mgr -eq 1 ]; then - RESTFUL_URLS="https://$IP:$MGR_PORT" - else - RESTFUL_URLS+=", https://$IP:$MGR_PORT" - fi - MGR_PORT=$(($MGR_PORT + 1000)) - fi + fi debug echo "Starting mgr.${name}" run 'mgr' $name $CEPH_BIN/ceph-mgr -i $name $ARGS @@ -1327,7 +1295,7 @@ EOF debug echo 'waiting for mgr to become available' sleep 1 done - + if [ "$new" -eq 1 ]; then # setting login credentials for dashboard if $with_mgr_dashboard; then @@ -1353,9 +1321,6 @@ EOF ceph_adm dashboard nvmeof-gateway-add -i <(echo "${NVMEOF_GW}") "${NVMEOF_GW/:/_}" fi fi - if $with_mgr_restful; then - create_mgr_restful_secret - fi fi if [ "$cephadm" -eq 1 ]; then @@ -2044,12 +2009,6 @@ if [ "$new" -eq 1 ]; then cat <