From: Aashish Sharma Date: Wed, 29 Jul 2020 11:48:21 +0000 (+0530) Subject: mgr/dashboard: Placeholders for Documenting REST API X-Git-Tag: v16.1.0~1348^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=830f295ebb2dac60bfc0344d1697f1926933bc42;p=ceph.git mgr/dashboard: Placeholders for Documenting REST API Added summary/description and example value for one endpoint from each component in Rest API Documentation Fixes:https://tracker.ceph.com/issues/40767 Signed-off-by: Aashish Sharma --- diff --git a/src/pybind/mgr/dashboard/controllers/auth.py b/src/pybind/mgr/dashboard/controllers/auth.py index 68c3827b4e85..fb021be84201 100644 --- a/src/pybind/mgr/dashboard/controllers/auth.py +++ b/src/pybind/mgr/dashboard/controllers/auth.py @@ -4,7 +4,7 @@ from __future__ import absolute_import import logging import cherrypy -from . import ApiController, RESTController +from . import ApiController, RESTController, ControllerDoc, EndpointDoc from .. import mgr from ..exceptions import DashboardException from ..services.auth import AuthManager, JwtManager @@ -12,13 +12,22 @@ from ..services.auth import AuthManager, JwtManager logger = logging.getLogger('controllers.auth') +AUTH_CHECK_SCHEMA = { + "username": (str, "Username"), + "permissions": ({ + "cephfs": ([str], "") + }, "List of permissions acquired"), + "sso": (bool, "Uses single sign on?"), + "pwdUpdateRequired": (bool, "Is password update required?") +} + @ApiController('/auth', secure=False) +@ControllerDoc("Initiate a session with Ceph", "Auth") class Auth(RESTController): """ Provide authenticates and returns JWT token. """ - def create(self, username, password): user_data = AuthManager.authenticate(username, password) user_perms, pwd_expiration_date, pwd_update_required = None, None, None @@ -63,7 +72,10 @@ class Auth(RESTController): return 'auth/saml2/login' return '#/login' - @RESTController.Collection('POST') + @RESTController.Collection('POST', query_params=['token']) + @EndpointDoc("Check token Authentication", + parameters={'token': (str, 'Authentication Token')}, + responses={201: AUTH_CHECK_SCHEMA}) def check(self, token): if token: user = JwtManager.get_user(token) diff --git a/src/pybind/mgr/dashboard/controllers/cephfs.py b/src/pybind/mgr/dashboard/controllers/cephfs.py index 499fae94612c..e6eb9c83efe3 100644 --- a/src/pybind/mgr/dashboard/controllers/cephfs.py +++ b/src/pybind/mgr/dashboard/controllers/cephfs.py @@ -8,7 +8,8 @@ import os import cherrypy import cephfs -from . import ApiController, ControllerDoc, RESTController, UiApiController +from . import ApiController, ControllerDoc, RESTController, \ + UiApiController, EndpointDoc from .. import mgr from ..exceptions import DashboardException from ..security import Scope @@ -16,8 +17,14 @@ from ..services.cephfs import CephFS as CephFS_ from ..services.ceph_service import CephService from ..tools import ViewCache +GET_QUOTAS_SCHEMA = { + 'max_bytes': (int, ''), + 'max_files': (int, '') +} + @ApiController('/cephfs', Scope.CEPHFS) +@ControllerDoc("Cephfs Management API", "Cephfs") class CephFS(RESTController): def __init__(self): # pragma: no cover super(CephFS, self).__init__() @@ -431,6 +438,12 @@ class CephFS(RESTController): cfs.rm_snapshot(path, name) @RESTController.Resource('GET') + @EndpointDoc("Get Cephfs Quotas of the specified path", + parameters={ + 'fs_id': (str, 'File System Identifier'), + 'path': (str, 'File System Path'), + }, + responses={200: GET_QUOTAS_SCHEMA}) def get_quotas(self, fs_id, path): """ Get the quotas of the specified path. diff --git a/src/pybind/mgr/dashboard/controllers/cluster_configuration.py b/src/pybind/mgr/dashboard/controllers/cluster_configuration.py index 810adf333448..382e18eb01ac 100644 --- a/src/pybind/mgr/dashboard/controllers/cluster_configuration.py +++ b/src/pybind/mgr/dashboard/controllers/cluster_configuration.py @@ -3,14 +3,34 @@ from __future__ import absolute_import import cherrypy -from . import ApiController, RESTController +from . import ApiController, RESTController, ControllerDoc, EndpointDoc from .. import mgr from ..security import Scope from ..services.ceph_service import CephService from ..exceptions import DashboardException +FILTER_SCHEMA = [{ + "name": (str, 'Name of the config option'), + "type": (str, 'Config option type'), + "level": (str, 'Config option level'), + "desc": (str, 'Description of the configuration'), + "long_desc": (str, 'Elaborated description'), + "default": (str, 'Default value for the config option'), + "daemon_default": (str, 'Daemon specific default value'), + "tags": ([str], 'Tags associated with the cluster'), + "services": ([str], 'Services associated with the config option'), + "see_also": ([str], 'Related config options'), + "enum_values": ([str], 'List of enums allowed'), + "min": (str, 'Minimum value'), + "max": (str, 'Maximum value'), + "can_update_at_runtime": (bool, 'Check if can update at runtime'), + "flags": ([str], 'List of flags associated') +}] + + @ApiController('/cluster_conf', Scope.CONFIG_OPT) +@ControllerDoc("Manage Cluster Configurations", "ClusterConfiguration") class ClusterConfiguration(RESTController): def _append_config_option_values(self, options): @@ -39,6 +59,11 @@ class ClusterConfiguration(RESTController): return self._get_config_option(name) @RESTController.Collection('GET', query_params=['name']) + @EndpointDoc("Get Cluster Configuration by name", + parameters={ + 'names': (str, 'Config option names'), + }, + responses={200: FILTER_SCHEMA}) def filter(self, names=None): config_options = [] diff --git a/src/pybind/mgr/dashboard/controllers/crush_rule.py b/src/pybind/mgr/dashboard/controllers/crush_rule.py index 3aeef258aa3b..fe62f090f61d 100644 --- a/src/pybind/mgr/dashboard/controllers/crush_rule.py +++ b/src/pybind/mgr/dashboard/controllers/crush_rule.py @@ -4,14 +4,28 @@ from __future__ import absolute_import from cherrypy import NotFound from . import ApiController, ControllerDoc, RESTController, Endpoint, ReadPermission, \ - UiApiController + UiApiController, EndpointDoc from ..security import Scope from ..services.ceph_service import CephService from .. import mgr +LIST_SCHEMA = { + "rule_id": (int, 'Rule ID'), + "rule_name": (str, 'Rule Name'), + "ruleset": (int, 'RuleSet related to the rule'), + "type": (int, 'Type of Rule'), + "min_size": (int, 'Minimum size of Rule'), + "max_size": (int, 'Maximum size of Rule'), + 'steps': ([{str}], 'Steps included in the rule') +} + + @ApiController('/crush_rule', Scope.POOL) +@ControllerDoc("Crush Rule Management API", "CrushRule") class CrushRule(RESTController): + @EndpointDoc("List Crush Rule Configuration", + responses={200: LIST_SCHEMA}) def list(self): return mgr.get('osd_map_crush')['rules'] diff --git a/src/pybind/mgr/dashboard/controllers/docs.py b/src/pybind/mgr/dashboard/controllers/docs.py index 84de247010c1..fce5341193c3 100644 --- a/src/pybind/mgr/dashboard/controllers/docs.py +++ b/src/pybind/mgr/dashboard/controllers/docs.py @@ -254,6 +254,7 @@ class Docs(BaseController): @classmethod def _gen_paths(cls, all_endpoints): + # pylint: disable=R0912 method_order = ['get', 'post', 'put', 'delete'] paths = {} for path, endpoints in sorted(list(ENDPOINT_MAP.items()), @@ -305,6 +306,13 @@ class Docs(BaseController): 'application/json': { 'schema': cls._gen_schema_for_content(body_params)}}} + if endpoint.query_params: + query_params = cls._add_param_info(endpoint.query_params, p_info) + methods[method.lower()]['requestBody'] = { + 'content': { + 'application/json': { + 'schema': cls._gen_schema_for_content(query_params)}}} + if endpoint.is_secure: methods[method.lower()]['security'] = [{'jwt': []}] diff --git a/src/pybind/mgr/dashboard/controllers/erasure_code_profile.py b/src/pybind/mgr/dashboard/controllers/erasure_code_profile.py index 3c8ba61f9f8a..e9dc01e7e8a3 100644 --- a/src/pybind/mgr/dashboard/controllers/erasure_code_profile.py +++ b/src/pybind/mgr/dashboard/controllers/erasure_code_profile.py @@ -4,19 +4,30 @@ from __future__ import absolute_import from cherrypy import NotFound from . import ApiController, ControllerDoc, RESTController, Endpoint, ReadPermission, \ - UiApiController + UiApiController, EndpointDoc from ..security import Scope from ..services.ceph_service import CephService from .. import mgr +LIST_CODE__SCHEMA = { + "crush-failure-domain": (str, ''), + "k": (int, 'Number of data chunks'), + "m": (int, 'Number of coding chunks'), + "plugin": (str, 'Plugin Info'), + "technique": (str, ''), + "name": (str, 'Name of the profile') +} + @ApiController('/erasure_code_profile', Scope.POOL) +@ControllerDoc("Erasure Code Profile Management API", "ErasureCodeProfile") class ErasureCodeProfile(RESTController): """ create() supports additional key-value arguments that are passed to the ECP plugin. """ - + @EndpointDoc("List Erasure Code Profile Information", + responses={'200': [LIST_CODE__SCHEMA]}) def list(self): return CephService.get_erasure_code_profiles() diff --git a/src/pybind/mgr/dashboard/controllers/grafana.py b/src/pybind/mgr/dashboard/controllers/grafana.py index 0d4331ff2463..9639d0260f13 100644 --- a/src/pybind/mgr/dashboard/controllers/grafana.py +++ b/src/pybind/mgr/dashboard/controllers/grafana.py @@ -3,17 +3,26 @@ from __future__ import absolute_import from . import (ApiController, BaseController, Endpoint, ReadPermission, UpdatePermission) +from . import ControllerDoc, EndpointDoc from ..exceptions import DashboardException from ..grafana import GrafanaRestClient, push_local_dashboards from ..security import Scope from ..settings import Settings +URL_SCHEMA = { + "instance": (str, "grafana instance") +} + + @ApiController('/grafana', Scope.GRAFANA) +@ControllerDoc("Grafana Management API", "Grafana") class Grafana(BaseController): @Endpoint() @ReadPermission + @EndpointDoc("List Grafana URL Instance", + responses={200: URL_SCHEMA}) def url(self): response = {'instance': Settings.GRAFANA_API_URL} return response diff --git a/src/pybind/mgr/dashboard/controllers/health.py b/src/pybind/mgr/dashboard/controllers/health.py index 35e7b07b216e..b8f91ca8f514 100644 --- a/src/pybind/mgr/dashboard/controllers/health.py +++ b/src/pybind/mgr/dashboard/controllers/health.py @@ -3,7 +3,7 @@ from __future__ import absolute_import import json -from . import ApiController, Endpoint, BaseController +from . import ApiController, Endpoint, BaseController, ControllerDoc, EndpointDoc from .. import mgr from ..rest_client import RequestException @@ -14,6 +14,102 @@ from ..services.iscsi_client import IscsiClient from ..tools import partial_dict from .host import get_hosts +HEALTH_MINIMAL_SCHEMA = ({ + 'client_perf': ({ + 'read_bytes_sec': (int, ''), + 'read_op_per_sec': (int, ''), + 'recovering_bytes_per_sec': (int, ''), + 'write_bytes_sec': (int, ''), + 'write_op_per_sec': (int, ''), + }, ''), + 'df': ({ + 'stats': ({ + 'total_avail_bytes': (int, ''), + 'total_bytes': (int, ''), + 'total_used_raw_bytes': (int, ''), + }, '') + }, ''), + 'fs_map': ({ + 'filesystems': ([{ + 'mdsmap': ({ + 'session_autoclose': (int, ''), + 'balancer': (str, ''), + 'up': (str, ''), + 'last_failure_osd_epoch': (int, ''), + 'in': ([int], ''), + 'last_failure': (int, ''), + 'max_file_size': (int, ''), + 'explicitly_allowed_features': (int, ''), + 'damaged': ([int], ''), + 'tableserver': (int, ''), + 'failed': ([int], ''), + 'metadata_pool': (int, ''), + 'epoch': (int, ''), + 'stopped': ([int], ''), + 'max_mds': (int, ''), + 'compat': ({ + 'compat': (str, ''), + 'ro_compat': (str, ''), + 'incompat': (str, ''), + }, ''), + 'required_client_features': (str, ''), + 'data_pools': ([int], ''), + 'info': (str, ''), + 'fs_name': (str, ''), + 'created': (str, ''), + 'standby_count_wanted': (int, ''), + 'enabled': (bool, ''), + 'modified': (str, ''), + 'session_timeout': (int, ''), + 'flags': (int, ''), + 'ever_allowed_features': (int, ''), + 'root': (int, ''), + }, ''), + 'standbys': (str, ''), + }], ''), + }, ''), + 'health': ({ + 'checks': (str, ''), + 'mutes': (str, ''), + 'status': (str, ''), + }, ''), + 'hosts': (int, ''), + 'iscsi_daemons': ({ + 'up': (int, ''), + 'down': (int, '') + }, ''), + 'mgr_map': ({ + 'active_name': (str, ''), + 'standbys': (str, '') + }, ''), + 'mon_status': ({ + 'monmap': ({ + 'mons': (str, ''), + }, ''), + 'quorum': ([int], '') + }, ''), + 'osd_map': ({ + 'osds': ([{ + 'in': (int, ''), + 'up': (int, ''), + }], '') + }, ''), + 'pg_info': ({ + 'object_stats': ({ + 'num_objects': (int, ''), + 'num_object_copies': (int, ''), + 'num_objects_degraded': (int, ''), + 'num_objects_misplaced': (int, ''), + 'num_objects_unfound': (int, ''), + }, ''), + 'pgs_per_osd': (int, ''), + 'statuses': (str, '') + }, ''), + 'pools': (str, ''), + 'rgw': (int, ''), + 'scrub_status': (str, '') +}) + class HealthData(object): """ @@ -181,6 +277,7 @@ class HealthData(object): @ApiController('/health') +@ControllerDoc("Display Detailed Cluster health Status", "Health") class Health(BaseController): def __init__(self): super(Health, self).__init__() @@ -192,5 +289,7 @@ class Health(BaseController): return self.health_full.all_health() @Endpoint() + @EndpointDoc("Get Cluster's minimal health report", + responses={200: HEALTH_MINIMAL_SCHEMA}) def minimal(self): return self.health_minimal.all_health() diff --git a/src/pybind/mgr/dashboard/controllers/host.py b/src/pybind/mgr/dashboard/controllers/host.py index db498c8aaad8..21c9bb1b6b91 100644 --- a/src/pybind/mgr/dashboard/controllers/host.py +++ b/src/pybind/mgr/dashboard/controllers/host.py @@ -10,7 +10,7 @@ import cherrypy from mgr_util import merge_dicts from orchestrator import HostSpec from . import ApiController, RESTController, Task, Endpoint, ReadPermission, \ - UiApiController, BaseController + UiApiController, BaseController, EndpointDoc, ControllerDoc from .orchestrator import raise_if_no_orchestrator from .. import mgr from ..exceptions import DashboardException @@ -19,6 +19,23 @@ from ..services.orchestrator import OrchClient from ..services.ceph_service import CephService from ..services.exception import handle_orchestrator_error +LIST_HOST_SCHEMA = { + "hostname": (str, "Hostname"), + "services": ([{ + "type": (str, "type of service"), + "id": (str, "Service Id"), + }], "Services related to the host"), + "ceph_version": (str, "Ceph version"), + "addr": (str, "Host address"), + "labels": ([str], "Labels related to the host"), + "service_type": (str, ""), + "sources": ({ + "ceph": (bool, ""), + "orchestrator": (bool, "") + }, "Host Sources"), + "status": (str, "") +} + def host_task(name, metadata, wait_for=10.0): return Task("host/{}".format(name), metadata, wait_for) @@ -105,7 +122,13 @@ def get_host(hostname: str) -> Dict: @ApiController('/host', Scope.HOSTS) +@ControllerDoc("Get Host Details", "Host") class Host(RESTController): + @EndpointDoc("List Host Specifications", + parameters={ + 'sources': (str, 'Host Sources'), + }, + responses={200: LIST_HOST_SCHEMA}) def list(self, sources=None): if sources is None: return get_hosts() diff --git a/src/pybind/mgr/dashboard/controllers/iscsi.py b/src/pybind/mgr/dashboard/controllers/iscsi.py index 81a54ce61e77..cb5e657cbb5e 100644 --- a/src/pybind/mgr/dashboard/controllers/iscsi.py +++ b/src/pybind/mgr/dashboard/controllers/iscsi.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# pylint: disable=C0302 # pylint: disable=too-many-branches # pylint: disable=too-many-lines from __future__ import absolute_import @@ -12,7 +13,7 @@ import rados import rbd from . import ApiController, UiApiController, RESTController, BaseController, Endpoint,\ - ReadPermission, UpdatePermission, Task + ReadPermission, UpdatePermission, Task, ControllerDoc, EndpointDoc from .. import mgr from ..rest_client import RequestException from ..security import Scope @@ -29,6 +30,13 @@ try: except ImportError: no_type_check = object() # Just for type checking +ISCSI_SCHEMA = { + 'user': (str, 'username'), + 'password': (str, 'password'), + 'mutual_user': (str, ''), + 'mutual_password': (str, '') +} + @UiApiController('/iscsi', Scope.ISCSI) class IscsiUi(BaseController): @@ -188,16 +196,26 @@ class IscsiUi(BaseController): @ApiController('/iscsi', Scope.ISCSI) +@ControllerDoc("Iscsi Management API", "Iscsi") class Iscsi(BaseController): - @Endpoint('GET', 'discoveryauth') @ReadPermission + @EndpointDoc("Get Iscsi discoveryauth Details", + responses={'200': [ISCSI_SCHEMA]}) def get_discoveryauth(self): gateway = get_available_gateway() return self._get_discoveryauth(gateway) - @Endpoint('PUT', 'discoveryauth') + @Endpoint('PUT', 'discoveryauth', + query_params=['user', 'password', 'mutual_user', 'mutual_password']) @UpdatePermission + @EndpointDoc("Set Iscsi discoveryauth", + parameters={ + 'user': (str, 'Username'), + 'password': (str, 'Password'), + 'mutual_user': (str, 'Mutual UserName'), + 'mutual_password': (str, 'Mutual Password'), + }) def set_discoveryauth(self, user, password, mutual_user, mutual_password): validate_auth({ 'user': user, @@ -235,6 +253,7 @@ def iscsi_target_task(name, metadata, wait_for=2.0): @ApiController('/iscsi/target', Scope.ISCSI) +@ControllerDoc("Get Iscsi Target Details", "IscsiTarget") class IscsiTarget(RESTController): def list(self): diff --git a/src/pybind/mgr/dashboard/controllers/logs.py b/src/pybind/mgr/dashboard/controllers/logs.py index 9dc5286f3bd4..63f7bfdb414e 100644 --- a/src/pybind/mgr/dashboard/controllers/logs.py +++ b/src/pybind/mgr/dashboard/controllers/logs.py @@ -3,7 +3,7 @@ from __future__ import absolute_import import collections -from . import ApiController, Endpoint, BaseController, ReadPermission +from . import ApiController, Endpoint, BaseController, ReadPermission, ControllerDoc, EndpointDoc from ..security import Scope from ..services.ceph_service import CephService from ..tools import NotificationQueue @@ -11,8 +11,29 @@ from ..tools import NotificationQueue LOG_BUFFER_SIZE = 30 +LOGS_SCHEMA = { + "clog": ([str], ""), + "audit_log": ([{ + "name": (str, ""), + "rank": (str, ""), + "addrs": ({ + "addrvec": ([{ + "type": (str, ""), + "addr": (str, "IP Address"), + "nonce": (int, ""), + }], ""), + }, ""), + "stamp": (str, ""), + "seq": (int, ""), + "channel": (str, ""), + "priority": (str, ""), + "message": (str, ""), + }], "Audit log") +} + @ApiController('/logs', Scope.LOG) +@ControllerDoc("Logs Management API", "Logs") class Logs(BaseController): def __init__(self): super(Logs, self).__init__() @@ -43,6 +64,8 @@ class Logs(BaseController): @Endpoint() @ReadPermission + @EndpointDoc("Display Logs Configuration", + responses={200: LOGS_SCHEMA}) def all(self): self.initialize_buffers() return dict( diff --git a/src/pybind/mgr/dashboard/controllers/mgr_modules.py b/src/pybind/mgr/dashboard/controllers/mgr_modules.py index 0d4cc5222560..ca834b96f893 100644 --- a/src/pybind/mgr/dashboard/controllers/mgr_modules.py +++ b/src/pybind/mgr/dashboard/controllers/mgr_modules.py @@ -1,18 +1,42 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -from . import ApiController, RESTController +from . import ApiController, RESTController, ControllerDoc, EndpointDoc from .. import mgr from ..security import Scope from ..services.ceph_service import CephService from ..services.exception import handle_send_command_error from ..tools import find_object_in_list, str_to_bool +MGR_MODULE_SCHEMA = ([{ + "name": (str, "Module Name"), + "enabled": (bool, "Is Module Enabled"), + "always_on": (bool, "Is it an always on module?"), + "options": ({ + "Option_name": ({ + "name": (str, "Name of the option"), + "type": (str, "Type of the option"), + "level": (str, "Option level"), + "flags": (int, "List of flags associated"), + "default_value": (int, "Default value for the option"), + "min": (str, "Minimum value"), + "max": (str, "Maximum value"), + "enum_allowed": ([str], ""), + "desc": (str, "Description of the option"), + "long_desc": (str, "Elaborated description"), + "tags": ([str], "Tags associated with the option"), + "see_also": ([str], "Related options") + }, "Options") + }, "Module Options") +}]) + @ApiController('/mgr/module', Scope.CONFIG_OPT) +@ControllerDoc("Get details of MGR Module", "MgrModule") class MgrModules(RESTController): ignore_modules = ['selftest'] - + @EndpointDoc("List Mgr modules", + responses={200: MGR_MODULE_SCHEMA}) def list(self): """ Get the list of managed modules. diff --git a/src/pybind/mgr/dashboard/controllers/monitor.py b/src/pybind/mgr/dashboard/controllers/monitor.py index d4512fcfe357..5b5a44fec747 100644 --- a/src/pybind/mgr/dashboard/controllers/monitor.py +++ b/src/pybind/mgr/dashboard/controllers/monitor.py @@ -3,15 +3,109 @@ from __future__ import absolute_import import json -from . import ApiController, Endpoint, BaseController, ReadPermission +from . import ApiController, Endpoint, BaseController, ReadPermission, ControllerDoc, EndpointDoc from .. import mgr from ..security import Scope +MONITOR_SCHEMA = { + "mon_status": ({ + "name": (str, ""), + "rank": (int, ""), + "state": (str, ""), + "election_epoch": (int, ""), + "quorum": ([int], ""), + "quorum_age": (int, ""), + "features": ({ + "required_con": (str, ""), + "required_mon": ([int], ""), + "quorum_con": (str, ""), + "quorum_mon": ([str], "") + }, ""), + "outside_quorum": ([str], ""), + "extra_probe_peers": ([str], ""), + "sync_provider": ([str], ""), + "monmap": ({ + "epoch": (int, ""), + "fsid": (str, ""), + "modified": (str, ""), + "created": (str, ""), + "min_mon_release": (int, ""), + "min_mon_release_name": (str, ""), + "features": ({ + "persistent": ([str], ""), + "optional": ([str], "") + }, ""), + "mons": ([{ + "rank": (int, ""), + "name": (str, ""), + "public_addrs": ({ + "addrvec": ([{ + "type": (str, ""), + "addr": (str, ""), + "nonce": (int, "") + }], "") + }, ""), + "addr": (str, ""), + "public_addr": (str, ""), + "priority": (int, ""), + "weight": (int, ""), + "stats": ({ + "num_sessions": ([int], ""), + }, "") + }], "") + }, ""), + "feature_map": ({ + "mon": ([{ + "features": (str, ""), + "release": (str, ""), + "num": (int, "") + }], ""), + "mds": ([{ + "features": (str, ""), + "release": (str, ""), + "num": (int, "") + }], ""), + "client": ([{ + "features": (str, ""), + "release": (str, ""), + "num": (int, "") + }], ""), + "mgr": ([{ + "features": (str, ""), + "release": (str, ""), + "num": (int, "") + }], ""), + }, "") + }, ""), + "in_quorum": ([{ + "rank": (int, ""), + "name": (str, ""), + "public_addrs": ({ + "addrvec": ([{ + "type": (str, ""), + "addr": (str, ""), + "nonce": (int, "") + }], "") + }, ""), + "addr": (str, ""), + "public_addr": (str, ""), + "priority": (int, ""), + "weight": (int, ""), + "stats": ({ + "num_sessions": ([int], "") + }, "") + }], ""), + "out_quorum": ([int], "") +} + @ApiController('/monitor', Scope.MONITOR) +@ControllerDoc("Get Monitor Details", "Monitor") class Monitor(BaseController): @Endpoint() @ReadPermission + @EndpointDoc("Get Monitor Details", + responses={200: MONITOR_SCHEMA}) def __call__(self): in_quorum, out_quorum = [], [] diff --git a/src/pybind/mgr/dashboard/controllers/orchestrator.py b/src/pybind/mgr/dashboard/controllers/orchestrator.py index 14ddd39298ae..8ef183285e72 100644 --- a/src/pybind/mgr/dashboard/controllers/orchestrator.py +++ b/src/pybind/mgr/dashboard/controllers/orchestrator.py @@ -5,7 +5,7 @@ import os.path import time from functools import wraps -from . import ApiController, Endpoint, ReadPermission, UpdatePermission +from . import ApiController, Endpoint, ReadPermission, UpdatePermission, ControllerDoc, EndpointDoc from . import RESTController, Task from .. import mgr from ..exceptions import DashboardException @@ -14,6 +14,11 @@ from ..services.exception import handle_orchestrator_error from ..services.orchestrator import OrchClient from ..tools import TaskManager +STATUS_SCHEMA = { + "available": (bool, "Orchestrator status"), + "description": (str, "Description") +} + def get_device_osd_map(): """Get mappings from inventory devices to OSD IDs. @@ -67,10 +72,13 @@ def raise_if_no_orchestrator(method): @ApiController('/orchestrator') +@ControllerDoc("Orchestrator Management API", "Orchestrator") class Orchestrator(RESTController): @Endpoint() @ReadPermission + @EndpointDoc("Display Orchestrator Status", + responses={200: STATUS_SCHEMA}) def status(self): return OrchClient.instance().status() @@ -100,6 +108,7 @@ class Orchestrator(RESTController): @ApiController('/orchestrator/inventory', Scope.HOSTS) +@ControllerDoc("Get Orchestrator Inventory Details", "OrchestratorInventory") class OrchestratorInventory(RESTController): @raise_if_no_orchestrator diff --git a/src/pybind/mgr/dashboard/controllers/osd.py b/src/pybind/mgr/dashboard/controllers/osd.py index caff46e5eaad..bc8b4bc172fc 100644 --- a/src/pybind/mgr/dashboard/controllers/osd.py +++ b/src/pybind/mgr/dashboard/controllers/osd.py @@ -7,7 +7,7 @@ import time from ceph.deployment.drive_group import DriveGroupSpec, DriveGroupValidationError from mgr_util import get_most_recent_rate -from . import ApiController, RESTController, Endpoint, Task +from . import ApiController, RESTController, Endpoint, Task, EndpointDoc, ControllerDoc from . import CreatePermission, ReadPermission, UpdatePermission, DeletePermission from .orchestrator import raise_if_no_orchestrator from .. import mgr @@ -25,12 +25,25 @@ except ImportError: # pragma: no cover logger = logging.getLogger('controllers.osd') +SAFE_TO_DESTROY_SCHEMA = { + "safe_to_destroy": ([str], "Is OSD safe to destroy?"), + "active": ([int], ""), + "missing_stats": ([str], ""), + "stored_pgs": ([str], "Stored Pool groups in Osd"), + "is_safe_to_destroy": (bool, "Is OSD safe to destroy?") +} + +EXPORT_FLAGS_SCHEMA = { + "list_of_flags": ([str], "") +} + def osd_task(name, metadata, wait_for=2.0): return Task("osd/{}".format(name), metadata, wait_for) @ApiController('/osd', Scope.OSD) +@ControllerDoc("Get OSD Details", "OSD") class Osd(RESTController): def list(self): osds = self.get_osd_map() @@ -301,6 +314,11 @@ class Osd(RESTController): @Endpoint('GET', query_params=['ids']) @ReadPermission + @EndpointDoc("Check If OSD is Safe to Destroy", + parameters={ + 'ids': (str, 'OSD Service Identifier'), + }, + responses={200: SAFE_TO_DESTROY_SCHEMA}) def safe_to_destroy(self, ids): """ :type ids: int|[int] @@ -345,6 +363,7 @@ class Osd(RESTController): @ApiController('/osd/flags', Scope.OSD) +@ControllerDoc("OSD Flags controller Management API", "OsdFlagsController") class OsdFlagsController(RESTController): @staticmethod def _osd_flags(): @@ -358,6 +377,8 @@ class OsdFlagsController(RESTController): set(enabled_flags) - {'pauserd', 'pausewr'} | {'pause'}) return sorted(enabled_flags) + @EndpointDoc("Display OSD Flags", + responses={200: EXPORT_FLAGS_SCHEMA}) def list(self): return self._osd_flags() diff --git a/src/pybind/mgr/dashboard/controllers/perf_counters.py b/src/pybind/mgr/dashboard/controllers/perf_counters.py index 158641ca1562..30d62358e37a 100644 --- a/src/pybind/mgr/dashboard/controllers/perf_counters.py +++ b/src/pybind/mgr/dashboard/controllers/perf_counters.py @@ -3,11 +3,24 @@ from __future__ import absolute_import import cherrypy -from . import ApiController, RESTController +from . import ApiController, RESTController, EndpointDoc, ControllerDoc from .. import mgr from ..security import Scope from ..services.ceph_service import CephService +PERF_SCHEMA = { + "mon.a": ({ + ".cache_bytes": ({ + "description": (str, ""), + "nick": (str, ""), + "type": (int, ""), + "priority": (int, ""), + "units": (int, ""), + "value": (int, "") + }, ""), + }, "Service ID"), +} + class PerfCounter(RESTController): service_type = None # type: str @@ -45,41 +58,51 @@ class PerfCounter(RESTController): @ApiController('perf_counters/mds', Scope.CEPHFS) +@ControllerDoc("Mds Perf Counters Management API", "MdsPerfCounter") class MdsPerfCounter(PerfCounter): service_type = 'mds' @ApiController('perf_counters/mon', Scope.MONITOR) +@ControllerDoc("Mon Perf Counters Management API", "MonPerfCounter") class MonPerfCounter(PerfCounter): service_type = 'mon' @ApiController('perf_counters/osd', Scope.OSD) +@ControllerDoc("OSD Perf Counters Management API", "OsdPerfCounter") class OsdPerfCounter(PerfCounter): service_type = 'osd' @ApiController('perf_counters/rgw', Scope.RGW) +@ControllerDoc("Rgw Perf Counters Management API", "RgwPerfCounter") class RgwPerfCounter(PerfCounter): service_type = 'rgw' @ApiController('perf_counters/rbd-mirror', Scope.RBD_MIRRORING) +@ControllerDoc("Rgw Mirroring Perf Counters Management API", "RgwMirrorPerfCounter") class RbdMirrorPerfCounter(PerfCounter): service_type = 'rbd-mirror' @ApiController('perf_counters/mgr', Scope.MANAGER) +@ControllerDoc("Mgr Perf Counters Management API", "MgrPerfCounter") class MgrPerfCounter(PerfCounter): service_type = 'mgr' @ApiController('perf_counters/tcmu-runner', Scope.ISCSI) +@ControllerDoc("Tcmu Runner Perf Counters Management API", "TcmuRunnerPerfCounter") class TcmuRunnerPerfCounter(PerfCounter): service_type = 'tcmu-runner' @ApiController('perf_counters') +@ControllerDoc("Perf Counters Management API", "PerfCounters") class PerfCounters(RESTController): + @EndpointDoc("Display Perf Counters", + responses={200: PERF_SCHEMA}) def list(self): return mgr.get_all_perf_counters() diff --git a/src/pybind/mgr/dashboard/controllers/pool.py b/src/pybind/mgr/dashboard/controllers/pool.py index a1f2e6d916ad..8e3294bfbbc7 100644 --- a/src/pybind/mgr/dashboard/controllers/pool.py +++ b/src/pybind/mgr/dashboard/controllers/pool.py @@ -6,7 +6,7 @@ import time import cherrypy from . import ApiController, ControllerDoc, RESTController, Endpoint, ReadPermission, Task, \ - UiApiController + UiApiController, EndpointDoc from .. import mgr from ..security import Scope from ..services.ceph_service import CephService @@ -14,12 +14,83 @@ from ..services.rbd import RbdConfiguration from ..services.exception import handle_send_command_error from ..tools import str_to_bool, TaskManager +POOL_SCHEMA = ([{ + "pool": (int, "pool id"), + "pool_name": (str, "pool name"), + "flags": (int, ""), + "flags_names": (str, "flags name"), + "type": (str, "type of pool"), + "size": (int, "pool size"), + "min_size": (int, ""), + "crush_rule": (str, ""), + "object_hash": (int, ""), + "pg_autoscale_mode": (str, ""), + "pg_num": (int, ""), + "pg_placement_num": (int, ""), + "pg_placement_num_target": (int, ""), + "pg_num_target": (int, ""), + "pg_num_pending": (int, ""), + "last_pg_merge_meta": ({ + "ready_epoch": (int, ""), + "last_epoch_started": (int, ""), + "last_epoch_clean": (int, ""), + "source_pgid": (str, ""), + "source_version": (str, ""), + "target_version": (str, ""), + }, ""), + "auid": (int, ""), + "snap_mode": (str, ""), + "snap_seq": (int, ""), + "snap_epoch": (int, ""), + "pool_snaps": ([str], ""), + "quota_max_bytes": (int, ""), + "quota_max_objects": (int, ""), + "tiers": ([str], ""), + "tier_of": (int, ""), + "read_tier": (int, ""), + "write_tier": (int, ""), + "cache_mode": (str, ""), + "target_max_bytes": (int, ""), + "target_max_objects": (int, ""), + "cache_target_dirty_ratio_micro": (int, ""), + "cache_target_dirty_high_ratio_micro": (int, ""), + "cache_target_full_ratio_micro": (int, ""), + "cache_min_flush_age": (int, ""), + "cache_min_evict_age": (int, ""), + "erasure_code_profile": (str, ""), + "hit_set_params": ({ + "type": (str, "") + }, ""), + "hit_set_period": (int, ""), + "hit_set_count": (int, ""), + "use_gmt_hitset": (bool, ""), + "min_read_recency_for_promote": (int, ""), + "min_write_recency_for_promote": (int, ""), + "hit_set_grade_decay_rate": (int, ""), + "hit_set_search_last_n": (int, ""), + "grade_table": ([str], ""), + "stripe_width": (int, ""), + "expected_num_objects": (int, ""), + "fast_read": (bool, ""), + "options": ({ + "pg_num_min": (int, "") + }, ""), + "application_metadata": ([str], ""), + "create_time": (str, ""), + "last_change": (str, ""), + "last_force_op_resend": (str, ""), + "last_force_op_resend_prenautilus": (str, ""), + "last_force_op_resend_preluminous": (str, ""), + "removed_snaps": ([str], "") +}]) + def pool_task(name, metadata, wait_for=2.0): return Task("pool/{}".format(name), metadata, wait_for) @ApiController('/pool', Scope.POOL) +@ControllerDoc("Get pool details by pool name", "Pool") class Pool(RESTController): @staticmethod @@ -58,6 +129,12 @@ class Pool(RESTController): return [cls._serialize_pool(pool, attrs) for pool in pools] + @EndpointDoc("Display Pool List", + parameters={ + 'attrs': (str, 'Pool Attributes'), + 'stats': (bool, 'Pool Stats') + }, + responses={200: POOL_SCHEMA}) def list(self, attrs=None, stats=False): return self._pool_list(attrs, stats) diff --git a/src/pybind/mgr/dashboard/controllers/prometheus.py b/src/pybind/mgr/dashboard/controllers/prometheus.py index 219adfa86f70..c13e6bd64dc7 100644 --- a/src/pybind/mgr/dashboard/controllers/prometheus.py +++ b/src/pybind/mgr/dashboard/controllers/prometheus.py @@ -5,7 +5,7 @@ from datetime import datetime import json import requests -from . import Controller, ApiController, BaseController, RESTController, Endpoint +from . import Controller, ApiController, BaseController, RESTController, Endpoint, ControllerDoc from ..security import Scope from ..settings import Settings from ..exceptions import DashboardException @@ -57,6 +57,7 @@ class PrometheusRESTController(RESTController): @ApiController('/prometheus', Scope.PROMETHEUS) +@ControllerDoc("Prometheus Management API", "Prometheus") class Prometheus(PrometheusRESTController): def list(self, **params): return self.alert_proxy('GET', '/alerts', params) @@ -79,6 +80,7 @@ class Prometheus(PrometheusRESTController): @ApiController('/prometheus/notifications', Scope.PROMETHEUS) +@ControllerDoc("Prometheus Notifications Management API", "PrometheusNotifications") class PrometheusNotifications(RESTController): def list(self, **params): diff --git a/src/pybind/mgr/dashboard/controllers/rbd.py b/src/pybind/mgr/dashboard/controllers/rbd.py index ec959949381f..a3ccf634276f 100644 --- a/src/pybind/mgr/dashboard/controllers/rbd.py +++ b/src/pybind/mgr/dashboard/controllers/rbd.py @@ -11,7 +11,7 @@ from datetime import datetime import rbd from . import ApiController, RESTController, Task, UpdatePermission, \ - DeletePermission, CreatePermission + DeletePermission, CreatePermission, EndpointDoc, ControllerDoc from .. import mgr from ..exceptions import DashboardException from ..security import Scope @@ -24,6 +24,18 @@ from ..services.exception import handle_rados_error, handle_rbd_error, \ logger = logging.getLogger(__name__) +RBD_SCHEMA = ([{ + "status": (int, 'Status of the image'), + "value": ([str], ''), + "pool_name": (str, 'pool name') +}]) + +RBD_TRASH_SCHEMA = [{ + "status": (int, ''), + "value": ([str], ''), + "pool_name": (str, 'pool name') +}] + # pylint: disable=not-callable def RbdTask(name, metadata, wait_for): # noqa: N802 @@ -55,6 +67,7 @@ def _sort_features(features, enable=True): @ApiController('/block/image', Scope.RBD_IMAGE) +@ControllerDoc("RBD Management API", "Rbd") class Rbd(RESTController): # set of image features that can be enable on existing images @@ -82,6 +95,11 @@ class Rbd(RESTController): @handle_rbd_error() @handle_rados_error('pool') + @EndpointDoc("Display Rbd Images", + parameters={ + 'pool_name': (str, 'Pool Name'), + }, + responses={200: RBD_SCHEMA}) def list(self, pool_name=None): return self._rbd_list(pool_name) @@ -229,6 +247,7 @@ class Rbd(RESTController): @ApiController('/block/image/{image_spec}/snap', Scope.RBD_IMAGE) +@ControllerDoc("RBD Snapshot Management API", "RbdSnapshot") class RbdSnapshot(RESTController): RESOURCE_ID = "snapshot_name" @@ -317,6 +336,7 @@ class RbdSnapshot(RESTController): @ApiController('/block/image/trash', Scope.RBD_IMAGE) +@ControllerDoc("RBD Trash Management API", "RbdTrash") class RbdTrash(RESTController): RESOURCE_ID = "image_id_spec" rbd_inst = rbd.RBD() @@ -355,6 +375,11 @@ class RbdTrash(RESTController): @handle_rbd_error() @handle_rados_error('pool') + @EndpointDoc("Get RBD Trash Details by pool name", + parameters={ + 'pool_name': (str, 'Name of the pool'), + }, + responses={200: RBD_TRASH_SCHEMA}) def list(self, pool_name=None): """List all entries from trash.""" return self._trash_list(pool_name) @@ -398,6 +423,7 @@ class RbdTrash(RESTController): @ApiController('/block/pool/{pool_name}/namespace', Scope.RBD_IMAGE) +@ControllerDoc("RBD Namespace Management API", "RbdNamespace") class RbdNamespace(RESTController): rbd_inst = rbd.RBD() diff --git a/src/pybind/mgr/dashboard/controllers/rbd_mirroring.py b/src/pybind/mgr/dashboard/controllers/rbd_mirroring.py index 027289030854..c089e0a72751 100644 --- a/src/pybind/mgr/dashboard/controllers/rbd_mirroring.py +++ b/src/pybind/mgr/dashboard/controllers/rbd_mirroring.py @@ -12,7 +12,7 @@ import cherrypy import rbd from . import ApiController, Endpoint, Task, BaseController, ReadPermission, \ - UpdatePermission, RESTController + UpdatePermission, RESTController, EndpointDoc, ControllerDoc from .. import mgr from ..security import Scope @@ -335,12 +335,42 @@ def _reset_view_cache(): _get_content_data.reset() +RBD_MIRROR_SCHEMA = { + "site_name": (str, "Site Name") +} + +RBDM_POOL_SCHEMA = { + "mirror_mode": (str, "Mirror Mode") +} + +RBDM_SUMMARY_SCHEMA = { + "site_name": (str, "site name"), + "status": (int, ""), + "content_data": ({ + "daemons": ([str], ""), + "pools": ([{ + "name": (str, "Pool name"), + "health_color": (str, ""), + "health": (str, "pool health"), + "mirror_mode": (str, "status"), + "peer_uuids": ([str], "") + }], "Pools"), + "image_error": ([str], ""), + "image_syncing": ([str], ""), + "image_ready": ([str], "") + }, "") +} + + @ApiController('/block/mirroring', Scope.RBD_MIRRORING) +@ControllerDoc("RBD Mirroring Management API", "RbdMirroring") class RbdMirroring(BaseController): @Endpoint(method='GET', path='site_name') @handle_rbd_mirror_error() @ReadPermission + @EndpointDoc("Display Rbd Mirroring sitename", + responses={200: RBD_MIRROR_SCHEMA}) def get(self): return self._get_site_name() @@ -356,11 +386,14 @@ class RbdMirroring(BaseController): @ApiController('/block/mirroring/summary', Scope.RBD_MIRRORING) +@ControllerDoc("RBD Mirroring Summary Management API", "RbdMirroringSummary") class RbdMirroringSummary(BaseController): @Endpoint() @handle_rbd_mirror_error() @ReadPermission + @EndpointDoc("Display Rbd Mirroring Summary", + responses={200: RBDM_SUMMARY_SCHEMA}) def __call__(self): site_name = rbd.RBD().mirror_site_name_get(mgr.rados) @@ -371,6 +404,7 @@ class RbdMirroringSummary(BaseController): @ApiController('/block/mirroring/pool', Scope.RBD_MIRRORING) +@ControllerDoc("RBD Mirroring Pool Mode Management API", "RbdMirroringPoolMode") class RbdMirroringPoolMode(RESTController): RESOURCE_ID = "pool_name" @@ -381,6 +415,11 @@ class RbdMirroringPoolMode(RESTController): } @handle_rbd_mirror_error() + @EndpointDoc("Display Rbd Mirroring Summary", + parameters={ + 'pool_name': (str, 'Pool Name'), + }, + responses={200: RBDM_POOL_SCHEMA}) def get(self, pool_name): ioctx = mgr.rados.open_ioctx(pool_name) mode = rbd.RBD().mirror_mode_get(ioctx) @@ -408,6 +447,7 @@ class RbdMirroringPoolMode(RESTController): @ApiController('/block/mirroring/pool/{pool_name}/bootstrap', Scope.RBD_MIRRORING) +@ControllerDoc("RBD Mirroring Pool Bootstrap Management API", "RbdMirroringPoolBootstrap") class RbdMirroringPoolBootstrap(BaseController): @Endpoint(method='POST', path='token') @@ -438,6 +478,7 @@ class RbdMirroringPoolBootstrap(BaseController): @ApiController('/block/mirroring/pool/{pool_name}/peer', Scope.RBD_MIRRORING) +@ControllerDoc("RBD Mirroring Pool Peer Management API", "RbdMirroringPoolPeer") class RbdMirroringPoolPeer(RESTController): RESOURCE_ID = "peer_uuid" diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index a0dc444dcabf..6f0ded0b513f 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -6,7 +6,7 @@ import json import cherrypy from . import ApiController, BaseController, RESTController, Endpoint, \ - ReadPermission + ReadPermission, ControllerDoc, EndpointDoc from ..exceptions import DashboardException from ..rest_client import RequestException from ..security import Scope, Permission @@ -20,13 +20,31 @@ try: except ImportError: # pragma: no cover pass # Just for type checking -logger = logging.getLogger('controllers.rgw') +logger = logging.getLogger("controllers.rgw") + +RGW_SCHEMA = { + "available": (bool, "Is RGW available?"), + "message": (str, "Descriptions") +} + +RGW_DAEMON_SCHEMA = { + "id": (str, "Daemon ID"), + "version": (str, "Ceph Version"), + "server_hostname": (str, "") +} + +RGW_USER_SCHEMA = { + "list_of_users": ([str], "list of rgw users") +} @ApiController('/rgw', Scope.RGW) +@ControllerDoc("RGW Management API", "Rgw") class Rgw(BaseController): @Endpoint() @ReadPermission + @EndpointDoc("Display RGW Status", + responses={200: RGW_SCHEMA}) def status(self): status = {'available': False, 'message': None} try: @@ -52,7 +70,10 @@ class Rgw(BaseController): @ApiController('/rgw/daemon', Scope.RGW) +@ControllerDoc("RGW Daemon Management API", "RgwDaemon") class RgwDaemon(RESTController): + @EndpointDoc("Display RGW Daemons", + responses={200: RGW_DAEMON_SCHEMA}) def list(self): # type: () -> List[dict] daemons = [] @@ -111,6 +132,7 @@ class RgwRESTController(RESTController): @ApiController('/rgw/site', Scope.RGW) +@ControllerDoc("RGW Site Management API", "RgwSite") class RgwSite(RgwRESTController): def list(self, query=None): if query == 'placement-targets': @@ -125,6 +147,7 @@ class RgwSite(RgwRESTController): @ApiController('/rgw/bucket', Scope.RGW) +@ControllerDoc("RGW Bucket Management API", "RgwBucket") class RgwBucket(RgwRESTController): def _append_bid(self, bucket): """ @@ -273,6 +296,7 @@ class RgwBucket(RgwRESTController): @ApiController('/rgw/user', Scope.RGW) +@ControllerDoc("RGW User Management API", "RgwUser") class RgwUser(RgwRESTController): def _append_uid(self, user): """ @@ -296,6 +320,8 @@ class RgwUser(RgwRESTController): return Scope.RGW in permissions and Permission.READ in permissions[Scope.RGW] \ and len(set(edit_permissions).intersection(set(permissions[Scope.RGW]))) > 0 + @EndpointDoc("Display RGW Users", + responses={200: RGW_USER_SCHEMA}) def list(self): # type: () -> List[str] users = [] # type: List[str] diff --git a/src/pybind/mgr/dashboard/controllers/role.py b/src/pybind/mgr/dashboard/controllers/role.py index 6bf616d08ef1..cfa28fa5c410 100644 --- a/src/pybind/mgr/dashboard/controllers/role.py +++ b/src/pybind/mgr/dashboard/controllers/role.py @@ -4,15 +4,25 @@ from __future__ import absolute_import import cherrypy from . import ApiController, RESTController, UiApiController,\ - CreatePermission + CreatePermission, ControllerDoc, EndpointDoc from .. import mgr from ..exceptions import RoleDoesNotExist, DashboardException,\ RoleIsAssociatedWithUser, RoleAlreadyExists from ..security import Scope as SecurityScope, Permission from ..services.access_control import SYSTEM_ROLES +ROLE_SCHEMA = [{ + "name": (str, "Role Name"), + "description": (str, "Role Descriptions"), + "scopes_permissions": ({ + "cephfs": ([str], "") + }, ""), + "system": (bool, "") +}] + @ApiController('/role', SecurityScope.USER) +@ControllerDoc("Role Management API", "Role") class Role(RESTController): @staticmethod def _role_to_dict(role): @@ -42,6 +52,8 @@ class Role(RESTController): if permissions: role.set_scope_permissions(scope, permissions) + @EndpointDoc("Display Role list", + responses={200: ROLE_SCHEMA}) def list(self): # type: () -> list roles = dict(mgr.ACCESS_CTRL_DB.roles) diff --git a/src/pybind/mgr/dashboard/controllers/service.py b/src/pybind/mgr/dashboard/controllers/service.py index baacff080ddb..0628e6d8e336 100644 --- a/src/pybind/mgr/dashboard/controllers/service.py +++ b/src/pybind/mgr/dashboard/controllers/service.py @@ -2,8 +2,8 @@ from typing import List, Optional, Dict import cherrypy from ceph.deployment.service_spec import ServiceSpec -from . import ApiController, RESTController, Task, Endpoint, ReadPermission -from . import CreatePermission, DeletePermission +from . import ApiController, ControllerDoc, RESTController, Task, Endpoint, ReadPermission, \ + CreatePermission, DeletePermission from .orchestrator import raise_if_no_orchestrator from ..exceptions import DashboardException from ..security import Scope @@ -16,6 +16,7 @@ def service_task(name, metadata, wait_for=2.0): @ApiController('/service', Scope.HOSTS) +@ControllerDoc("Service Management API", "Service") class Service(RESTController): @Endpoint() diff --git a/src/pybind/mgr/dashboard/controllers/settings.py b/src/pybind/mgr/dashboard/controllers/settings.py index 5235992a1741..dc35eec9561a 100644 --- a/src/pybind/mgr/dashboard/controllers/settings.py +++ b/src/pybind/mgr/dashboard/controllers/settings.py @@ -4,12 +4,20 @@ from contextlib import contextmanager import cherrypy -from . import ApiController, RESTController, UiApiController +from . import ApiController, RESTController, UiApiController, ControllerDoc, EndpointDoc from ..settings import Settings as SettingsModule, Options from ..security import Scope +SETTINGS_SCHEMA = [{ + "name": (str, 'Settings Name'), + "default": (bool, 'Default Settings'), + "type": (str, 'Type of Settings'), + "value": (bool, 'Settings Value') +}] + @ApiController('/settings', Scope.CONFIG_OPT) +@ControllerDoc("Settings Management API", "Settings") class Settings(RESTController): """ Enables to manage the settings of the dashboard (not the Ceph cluster). @@ -37,6 +45,11 @@ class Settings(RESTController): def _to_native(setting): return setting.upper().replace('-', '_') + @EndpointDoc("Display Settings Information", + parameters={ + 'names': (str, 'Name of Settings'), + }, + responses={200: SETTINGS_SCHEMA}) def list(self, names=None): """ Get the list of available options. diff --git a/src/pybind/mgr/dashboard/controllers/summary.py b/src/pybind/mgr/dashboard/controllers/summary.py index 3b181cb4d4e4..5febf4a6d83d 100644 --- a/src/pybind/mgr/dashboard/controllers/summary.py +++ b/src/pybind/mgr/dashboard/controllers/summary.py @@ -3,7 +3,7 @@ from __future__ import absolute_import import json -from . import ApiController, Endpoint, BaseController +from . import ApiController, Endpoint, BaseController, ControllerDoc, EndpointDoc from .. import mgr from ..security import Permission, Scope from ..controllers.rbd_mirroring import get_daemons_and_pools @@ -11,8 +11,35 @@ from ..exceptions import ViewCacheNoDataException from ..tools import TaskManager from ..services import progress +SUMMARY_SCHEMA = { + "health_status": (str, ""), + "mgr_id": (str, ""), + "mgr_host": (str, ""), + "have_mon_connection": (str, ""), + "executing_tasks": ([str], ""), + "finished_tasks": ([{ + "name": (str, ""), + "metadata": ({ + "pool": (int, ""), + }, ""), + "begin_time": (str, ""), + "end_time": (str, ""), + "duration": (int, ""), + "progress": (int, ""), + "success": (bool, ""), + "ret_value": (str, ""), + "exception": (str, ""), + }], ""), + "version": (str, ""), + "rbd_mirroring": ({ + "warnings": (int, ""), + "errors": (int, "") + }, "") +} + @ApiController('/summary') +@ControllerDoc("Get Ceph Summary Details", "Summary") class Summary(BaseController): def _health_status(self): health_data = mgr.get("health") @@ -69,6 +96,8 @@ class Summary(BaseController): return services['dashboard'] if 'dashboard' in services else '' @Endpoint() + @EndpointDoc("Display Summary", + responses={200: SUMMARY_SCHEMA}) def __call__(self): exe_t, fin_t = TaskManager.list_serializable() executing_tasks = [task for task in exe_t if self._task_permissions(task['name'])] diff --git a/src/pybind/mgr/dashboard/controllers/task.py b/src/pybind/mgr/dashboard/controllers/task.py index 8ac97fdb8f1e..1668f26af37e 100644 --- a/src/pybind/mgr/dashboard/controllers/task.py +++ b/src/pybind/mgr/dashboard/controllers/task.py @@ -1,13 +1,36 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -from . import ApiController, RESTController +from . import ApiController, RESTController, ControllerDoc, EndpointDoc from ..tools import TaskManager from ..services import progress +TASK_SCHEMA = { + "executing_tasks": (str, "ongoing executing tasks"), + "finished_tasks": ([{ + "name": (str, "finished tasks name"), + "metadata": ({ + "pool": (int, "") + }, ""), + "begin_time": (str, "Task begin time"), + "end_time": (str, "Task end time"), + "duration": (int, ""), + "progress": (int, "Progress of tasks"), + "success": (bool, ""), + "ret_value": (bool, ""), + "exception": (bool, "") + }], "") +} + @ApiController('/task') +@ControllerDoc("Task Management API", "Task") class Task(RESTController): + @EndpointDoc("Display Tasks", + parameters={ + 'name': (str, 'Task Name'), + }, + responses={200: TASK_SCHEMA}) def list(self, name=None): executing_t, finished_t = TaskManager.list_serializable(name) diff --git a/src/pybind/mgr/dashboard/controllers/telemetry.py b/src/pybind/mgr/dashboard/controllers/telemetry.py index c68432ece175..0478ba687552 100644 --- a/src/pybind/mgr/dashboard/controllers/telemetry.py +++ b/src/pybind/mgr/dashboard/controllers/telemetry.py @@ -1,16 +1,213 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -from . import ApiController, RESTController +from . import ApiController, RESTController, ControllerDoc, EndpointDoc from .. import mgr from ..exceptions import DashboardException from ..security import Scope +REPORT_SCHEMA = { + "report": ({ + "leaderboard": (bool, ""), + "report_version": (int, ""), + "report_timestamp": (str, ""), + "report_id": (str, ""), + "channels": ([str], ""), + "channels_available": ([str], ""), + "license": (str, ""), + "created": (str, ""), + "mon": ({ + "count": (int, ""), + "features": ({ + "persistent": ([str], ""), + "optional": ([int], "") + }, ""), + "min_mon_release": (int, ""), + "v1_addr_mons": (int, ""), + "v2_addr_mons": (int, ""), + "ipv4_addr_mons": (int, ""), + "ipv6_addr_mons": (int, ""), + }, ""), + "config": ({ + "cluster_changed": ([str], ""), + "active_changed": ([str], "") + }, ""), + "rbd": ({ + "num_pools": (int, ""), + "num_images_by_pool": ([int], ""), + "mirroring_by_pool": ([bool], ""), + }, ""), + "pools": ([{ + "pool": (int, ""), + "type": (str, ""), + "pg_num": (int, ""), + "pgp_num": (int, ""), + "size": (int, ""), + "min_size": (int, ""), + "pg_autoscale_mode": (str, ""), + "target_max_bytes": (int, ""), + "target_max_objects": (int, ""), + "erasure_code_profile": (str, ""), + "cache_mode": (str, ""), + }], ""), + "osd": ({ + "count": (int, ""), + "require_osd_release": (str, ""), + "require_min_compat_client": (str, ""), + "cluster_network": (bool, ""), + }, ""), + "crush": ({ + "num_devices": (int, ""), + "num_types": (int, ""), + "num_buckets": (int, ""), + "num_rules": (int, ""), + "device_classes": ([int], ""), + "tunables": ({ + "choose_local_tries": (int, ""), + "choose_local_fallback_tries": (int, ""), + "choose_total_tries": (int, ""), + "chooseleaf_descend_once": (int, ""), + "chooseleaf_vary_r": (int, ""), + "chooseleaf_stable": (int, ""), + "straw_calc_version": (int, ""), + "allowed_bucket_algs": (int, ""), + "profile": (str, ""), + "optimal_tunables": (int, ""), + "legacy_tunables": (int, ""), + "minimum_required_version": (str, ""), + "require_feature_tunables": (int, ""), + "require_feature_tunables2": (int, ""), + "has_v2_rules": (int, ""), + "require_feature_tunables3": (int, ""), + "has_v3_rules": (int, ""), + "has_v4_buckets": (int, ""), + "require_feature_tunables5": (int, ""), + "has_v5_rules": (int, ""), + }, ""), + "compat_weight_set": (bool, ""), + "num_weight_sets": (int, ""), + "bucket_algs": ({ + "straw2": (int, ""), + }, ""), + "bucket_sizes": ({ + "1": (int, ""), + "3": (int, ""), + }, ""), + "bucket_types": ({ + "1": (int, ""), + "11": (int, ""), + }, ""), + }, ""), + "fs": ({ + "count": (int, ""), + "feature_flags": ({ + "enable_multiple": (bool, ""), + "ever_enabled_multiple": (bool, ""), + }, ""), + "num_standby_mds": (int, ""), + "filesystems": ([int], ""), + "total_num_mds": (int, ""), + }, ""), + "metadata": ({ + "osd": ({ + "osd_objectstore": ({ + "bluestore": (int, ""), + }, ""), + "rotational": ({ + "1": (int, ""), + }, ""), + "arch": ({ + "x86_64": (int, ""), + }, ""), + "ceph_version": ({ + "ceph version 16.0.0-3151-gf202994fcf": (int, ""), + }, ""), + "os": ({ + "Linux": (int, ""), + }, ""), + "cpu": ({ + "Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz": (int, ""), + }, ""), + "kernel_description": ({ + "#1 SMP Wed Jul 1 19:53:01 UTC 2020": (int, ""), + }, ""), + "kernel_version": ({ + "5.7.7-200.fc32.x86_64": (int, ""), + }, ""), + "distro_description": ({ + "CentOS Linux 8 (Core)": (int, ""), + }, ""), + "distro": ({ + "centos": (int, ""), + }, ""), + }, ""), + "mon": ({ + "arch": ({ + "x86_64": (int, ""), + }, ""), + "ceph_version": ({ + "ceph version 16.0.0-3151-gf202994fcf": (int, ""), + }, ""), + "os": ({ + "Linux": (int, ""), + }, ""), + "cpu": ({ + "Intel(R) Core(TM) i7-8665U CPU @ 1.90GHz": (int, ""), + }, ""), + "kernel_description": ({ + "#1 SMP Wed Jul 1 19:53:01 UTC 2020": (int, ""), + }, ""), + "kernel_version": ({ + "5.7.7-200.fc32.x86_64": (int, ""), + }, ""), + "distro_description": ({ + "CentOS Linux 8 (Core)": (int, ""), + }, ""), + "distro": ({ + "centos": (int, ""), + }, ""), + }, ""), + }, ""), + "hosts": ({ + "num": (int, ""), + "num_with_mon": (int, ""), + "num_with_mds": (int, ""), + "num_with_osd": (int, ""), + "num_with_mgr": (int, ""), + }, ""), + "usage": ({ + "pools": (int, ""), + "pg_num": (int, ""), + "total_used_bytes": (int, ""), + "total_bytes": (int, ""), + "total_avail_bytes": (int, ""), + }, ""), + "services": ({ + "rgw": (int, ""), + }, ""), + "rgw": ({ + "count": (int, ""), + "zones": (int, ""), + "zonegroups": (int, ""), + "frontends": ([str], "") + }, ""), + "balancer": ({ + "active": (bool, ""), + "mode": (str, ""), + }, ""), + "crashes": ([int], "") + }, ""), + "device_report": (str, "") +} + @ApiController('/telemetry', Scope.CONFIG_OPT) +@ControllerDoc("Display Telemetry Report", "Telemetry") class Telemetry(RESTController): @RESTController.Collection('GET') + @EndpointDoc("Get Detailed Telemetry report", + responses={200: REPORT_SCHEMA}) def report(self): """ Get Ceph and device report data diff --git a/src/pybind/mgr/dashboard/controllers/user.py b/src/pybind/mgr/dashboard/controllers/user.py index d32ee4cc8339..bd50da5c0bef 100644 --- a/src/pybind/mgr/dashboard/controllers/user.py +++ b/src/pybind/mgr/dashboard/controllers/user.py @@ -7,7 +7,7 @@ import time import cherrypy -from . import BaseController, ApiController, RESTController, Endpoint +from . import BaseController, ApiController, RESTController, Endpoint, ControllerDoc, EndpointDoc from .. import mgr from ..exceptions import DashboardException, UserAlreadyExists, \ UserDoesNotExist, PasswordPolicyException, PwdExpirationDateNotValid @@ -15,6 +15,17 @@ from ..security import Scope from ..services.access_control import SYSTEM_ROLES, PasswordPolicy from ..services.auth import JwtManager +USER_SCHEMA = ([{ + "username": (str, 'Username of the user'), + "roles": ([str], 'User Roles'), + "name": (str, 'User Name'), + "email": (str, 'User email address'), + "lastUpdate": (int, 'Details last updated'), + "enabled": (bool, 'Is the user enabled?'), + "pwdExpirationDate": (str, 'Password Expiration date'), + "pwdUpdateRequired": (bool, 'Is Password Update Required?') +}], '') + def validate_password_policy(password, username=None, old_password=None): """ @@ -36,6 +47,7 @@ def validate_password_policy(password, username=None, old_password=None): @ApiController('/user', Scope.USER) +@ControllerDoc("Display User Details", "User") class User(RESTController): @staticmethod @@ -54,7 +66,8 @@ class User(RESTController): raise DashboardException(msg='Role does not exist', code='role_does_not_exist', component='user') - + @EndpointDoc("Get List Of Users", + responses={200: USER_SCHEMA}) def list(self): users = mgr.ACCESS_CTRL_DB.users result = [User._user_to_dict(u) for _, u in users.items()] @@ -143,6 +156,7 @@ class User(RESTController): @ApiController('/user') +@ControllerDoc("Get User Password Policy Details", "UserPasswordPolicy") class UserPasswordPolicy(RESTController): @Endpoint('POST') @@ -174,6 +188,7 @@ class UserPasswordPolicy(RESTController): @ApiController('/user/{username}') +@ControllerDoc("Change User Password", "UserChangePassword") class UserChangePassword(BaseController): @Endpoint('POST') diff --git a/src/pybind/mgr/dashboard/plugins/feature_toggles.py b/src/pybind/mgr/dashboard/plugins/feature_toggles.py index 5e7c47905896..f72afc58c13e 100644 --- a/src/pybind/mgr/dashboard/plugins/feature_toggles.py +++ b/src/pybind/mgr/dashboard/plugins/feature_toggles.py @@ -4,7 +4,6 @@ from __future__ import absolute_import from enum import Enum import cherrypy from mgr_module import CLICommand, Option - from . import PLUGIN_MANAGER as PM from . import interfaces as I # noqa: E741,N812 from .ttl_cache import ttl_cache @@ -34,7 +33,6 @@ class Features(Enum): PREDISABLED_FEATURES = set() # type: Set[str] - Feature2Controller = { Features.RBD: [Rbd, RbdSnapshot, RbdTrash], Features.MIRRORING: [ @@ -139,11 +137,22 @@ class FeatureToggles(I.CanMgr, I.Setupable, I.HasOptions, @PM.add_hook def get_controllers(self): - from ..controllers import ApiController, RESTController + from ..controllers import ApiController, RESTController, ControllerDoc, EndpointDoc + + FEATURES_SCHEMA = { + "rbd": (bool, ''), + "mirroring": (bool, ''), + "iscsi": (bool, ''), + "cephfs": (bool, ''), + "rgw": (bool, ''), + "nfs": (bool, '') + } @ApiController('/feature_toggles') + @ControllerDoc("Manage Features API", "FeatureTogglesEndpoint") class FeatureTogglesEndpoint(RESTController): - + @EndpointDoc("Get List Of Features", + responses={200: FEATURES_SCHEMA}) def list(_): # pylint: disable=no-self-argument # noqa: N805 return { # pylint: disable=protected-access