From: Alfonso Martínez Date: Tue, 6 Oct 2020 07:10:13 +0000 (+0200) Subject: mgr/dashboard: api doc: fix endpoint responses doc generation. X-Git-Tag: v16.1.0~881^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=bc1a33c28eab251c7ee7f3c6321873354ca90d22;p=ceph.git mgr/dashboard: api doc: fix endpoint responses doc generation. Fixes: https://tracker.ceph.com/issues/47615 Signed-off-by: Alfonso Martínez --- diff --git a/src/pybind/mgr/dashboard/api/__init__.py b/src/pybind/mgr/dashboard/api/__init__.py new file mode 100644 index 000000000000..c3961685ab8d --- /dev/null +++ b/src/pybind/mgr/dashboard/api/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import diff --git a/src/pybind/mgr/dashboard/api/doc.py b/src/pybind/mgr/dashboard/api/doc.py new file mode 100644 index 000000000000..172d59d0a821 --- /dev/null +++ b/src/pybind/mgr/dashboard/api/doc.py @@ -0,0 +1,53 @@ +from enum import Enum +from typing import Any, Dict, List, Optional + + +class SchemaType(Enum): + """ + Representation of the type property of a schema object: + http://spec.openapis.org/oas/v3.0.3.html#schema-object + """ + ARRAY = 'array' + BOOLEAN = 'boolean' + INTEGER = 'integer' + NUMBER = 'number' + OBJECT = 'object' + STRING = 'string' + + def __str__(self): + return str(self.value) + + +class Schema: + """ + Representation of a schema object: + http://spec.openapis.org/oas/v3.0.3.html#schema-object + """ + + def __init__(self, schema_type: SchemaType = SchemaType.OBJECT, + properties: Optional[Dict] = None, required: Optional[List] = None): + self._type = schema_type + self._properties = properties if properties else {} + self._required = required if required else [] + + def as_dict(self) -> Dict[str, Any]: + schema: Dict[str, Any] = {'type': str(self._type)} + + if self._type == SchemaType.ARRAY: + items = Schema(properties=self._properties) + schema['items'] = items.as_dict() + else: + schema['properties'] = self._properties + + if self._required: + schema['required'] = self._required + + return schema + + +class SchemaInput: + """ + Simple DTO to transfer data in a structured manner for creating a schema object. + """ + type: SchemaType + params: List[Any] diff --git a/src/pybind/mgr/dashboard/controllers/__init__.py b/src/pybind/mgr/dashboard/controllers/__init__.py index b77ea759eb40..4e52c21d8e43 100644 --- a/src/pybind/mgr/dashboard/controllers/__init__.py +++ b/src/pybind/mgr/dashboard/controllers/__init__.py @@ -11,12 +11,13 @@ import os import pkgutil import re import sys -import urllib from functools import wraps +from urllib.parse import unquote # pylint: disable=wrong-import-position import cherrypy +from ..api.doc import SchemaInput, SchemaType from ..exceptions import PermissionNotValid, ScopeNotValid from ..plugins import PLUGIN_MANAGER from ..security import Permission, Scope @@ -107,7 +108,12 @@ def EndpointDoc(description="", group="", parameters=None, responses=None): # n resp = {} if responses: for status_code, response_body in responses.items(): - resp[str(status_code)] = _split_parameters(response_body) + schema_input = SchemaInput() + schema_input.type = SchemaType.ARRAY if \ + isinstance(response_body, list) else SchemaType.OBJECT + schema_input.params = _split_parameters(response_body) + + resp[str(status_code)] = schema_input def _wrapper(func): func.doc_info = { @@ -662,7 +668,7 @@ class BaseController(object): def inner(*args, **kwargs): for key, value in kwargs.items(): if isinstance(value, str): - kwargs[key] = urllib.parse.unquote(value) + kwargs[key] = unquote(value) # Process method arguments. params = get_request_body_params(cherrypy.request) diff --git a/src/pybind/mgr/dashboard/controllers/docs.py b/src/pybind/mgr/dashboard/controllers/docs.py index a53712199345..ddab096002bd 100644 --- a/src/pybind/mgr/dashboard/controllers/docs.py +++ b/src/pybind/mgr/dashboard/controllers/docs.py @@ -2,11 +2,12 @@ from __future__ import absolute_import import logging -from typing import Any, Dict, Union +from typing import Any, Dict, List, Union import cherrypy from .. import mgr +from ..api.doc import Schema, SchemaInput, SchemaType from . import ENDPOINT_MAP, BaseController, Controller, Endpoint, allow_empty_body NO_DESCRIPTION_AVAILABLE = "*No description available*" @@ -71,35 +72,35 @@ class Docs(BaseController): param_name = param['name'] def_value = param['default'] if 'default' in param else None if param_name.startswith("is_"): - return "boolean" + return str(SchemaType.BOOLEAN) if "size" in param_name: - return "integer" + return str(SchemaType.INTEGER) if "count" in param_name: - return "integer" + return str(SchemaType.INTEGER) if "num" in param_name: - return "integer" + return str(SchemaType.INTEGER) if isinstance(def_value, bool): - return "boolean" + return str(SchemaType.BOOLEAN) if isinstance(def_value, int): - return "integer" - return "string" + return str(SchemaType.INTEGER) + return str(SchemaType.STRING) @classmethod # isinstance doesn't work: input is always . def _type_to_str(cls, type_as_type): """ Used if type is explicitly defined. """ if type_as_type is str: - type_as_str = 'string' + type_as_str = str(SchemaType.STRING) elif type_as_type is int: - type_as_str = 'integer' + type_as_str = str(SchemaType.INTEGER) elif type_as_type is bool: - type_as_str = 'boolean' + type_as_str = str(SchemaType.BOOLEAN) elif type_as_type is list or type_as_type is tuple: - type_as_str = 'array' + type_as_str = str(SchemaType.ARRAY) elif type_as_type is float: - type_as_str = 'number' + type_as_str = str(SchemaType.NUMBER) else: - type_as_str = 'object' + type_as_str = str(SchemaType.OBJECT) return type_as_str @classmethod @@ -143,13 +144,17 @@ class Docs(BaseController): return parameters @classmethod - def _gen_schema_for_content(cls, params): + def _gen_schema_for_content(cls, params: List[Any]) -> Dict[str, Any]: """ Generates information to the content-object in OpenAPI Spec. Used to for request body and responses. """ required_params = [] properties = {} + schema_type = SchemaType.OBJECT + if isinstance(params, SchemaInput): + schema_type = params.type + params = params.params for param in params: if param['required']: @@ -159,12 +164,12 @@ class Docs(BaseController): if 'type' in param: props['type'] = cls._type_to_str(param['type']) if 'nested_params' in param: - if props['type'] == 'array': # dict in array + if props['type'] == str(SchemaType.ARRAY): # dict in array props['items'] = cls._gen_schema_for_content(param['nested_params']) else: # dict in dict props = cls._gen_schema_for_content(param['nested_params']) - elif props['type'] == 'object': # e.g. [int] - props['type'] = 'array' + elif props['type'] == str(SchemaType.OBJECT): # e.g. [int] + props['type'] = str(SchemaType.ARRAY) props['items'] = {'type': cls._type_to_str(param['type'][0])} else: props['type'] = cls._gen_type(param) @@ -174,13 +179,10 @@ class Docs(BaseController): props['default'] = param['default'] properties[param['name']] = props - schema = { - 'type': 'object', - 'properties': properties, - } - if required_params: - schema['required'] = required_params - return schema + schema = Schema(schema_type=schema_type, properties=properties, + required=required_params) + + return schema.as_dict() @classmethod def _gen_responses(cls, method, resp_object=None): @@ -215,10 +217,11 @@ class Docs(BaseController): if resp_object: for status_code, response_body in resp_object.items(): - resp[status_code].update({ - 'content': { - 'application/json': { - 'schema': cls._gen_schema_for_content(response_body)}}}) + if status_code in resp: + resp[status_code].update({ + 'content': { + 'application/json': { + 'schema': cls._gen_schema_for_content(response_body)}}}) return resp diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index e00a77b3cbf8..984c8622d9bd 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -74,7 +74,7 @@ class Rgw(BaseController): @ControllerDoc("RGW Daemon Management API", "RgwDaemon") class RgwDaemon(RESTController): @EndpointDoc("Display RGW Daemons", - responses={200: RGW_DAEMON_SCHEMA}) + responses={200: [RGW_DAEMON_SCHEMA]}) def list(self): # type: () -> List[dict] daemons = [] diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index bba9a054a30e..efcde8b6223a 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -144,23 +144,25 @@ paths: content: application/json: schema: - properties: - pool_name: - description: pool name - type: string - status: - description: Status of the image - type: integer - value: - description: '' - items: + items: + properties: + pool_name: + description: pool name type: string - type: array + status: + description: Status of the image + type: integer + value: + description: '' + items: + type: string + type: array + type: object required: - status - value - pool_name - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -260,23 +262,25 @@ paths: content: application/json: schema: - properties: - pool_name: - description: pool name - type: string - status: - description: '' - type: integer - value: - description: '' - items: + items: + properties: + pool_name: + description: pool name type: string - type: array + status: + description: '' + type: integer + value: + description: '' + items: + type: string + type: array + type: object required: - status - value - pool_name - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -1885,62 +1889,64 @@ paths: content: application/json: schema: - properties: - can_update_at_runtime: - description: Check if can update at runtime - type: boolean - daemon_default: - description: Daemon specific default value - type: string - default: - description: Default value for the config option - type: string - desc: - description: Description of the configuration - type: string - enum_values: - description: List of enums allowed - items: + items: + properties: + can_update_at_runtime: + description: Check if can update at runtime + type: boolean + daemon_default: + description: Daemon specific default value type: string - type: array - flags: - description: List of flags associated - items: + default: + description: Default value for the config option type: string - type: array - level: - description: Config option level - type: string - long_desc: - description: Elaborated description - type: string - max: - description: Maximum value - type: string - min: - description: Minimum value - type: string - name: - description: Name of the config option - type: string - see_also: - description: Related config options - items: + desc: + description: Description of the configuration type: string - type: array - services: - description: Services associated with the config option - items: + enum_values: + description: List of enums allowed + items: + type: string + type: array + flags: + description: List of flags associated + items: + type: string + type: array + level: + description: Config option level type: string - type: array - tags: - description: Tags associated with the cluster - items: + long_desc: + description: Elaborated description type: string - type: array - type: - description: Config option type - type: string + max: + description: Maximum value + type: string + min: + description: Minimum value + type: string + name: + description: Name of the config option + type: string + see_also: + description: Related config options + items: + type: string + type: array + services: + description: Services associated with the config option + items: + type: string + type: array + tags: + description: Tags associated with the cluster + items: + type: string + type: array + type: + description: Config option type + type: string + type: object required: - name - type @@ -1957,7 +1963,7 @@ paths: - max - can_update_at_runtime - flags - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -2178,25 +2184,27 @@ paths: content: application/json: schema: - properties: - crush-failure-domain: - description: '' - type: string - k: - description: Number of data chunks - type: integer - m: - description: Number of coding chunks - type: integer - name: - description: Name of the profile - type: string - plugin: - description: Plugin Info - type: string - technique: - description: '' - type: string + items: + properties: + crush-failure-domain: + description: '' + type: string + k: + description: Number of data chunks + type: integer + m: + description: Number of coding chunks + type: integer + name: + description: Name of the profile + type: string + plugin: + description: Plugin Info + type: string + technique: + description: '' + type: string + type: object required: - crush-failure-domain - k @@ -2204,7 +2212,7 @@ paths: - plugin - technique - name - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -3113,25 +3121,27 @@ paths: content: application/json: schema: - properties: - mutual_password: - description: '' - type: string - mutual_user: - description: '' - type: string - password: - description: password - type: string - user: - description: username - type: string + items: + properties: + mutual_password: + description: '' + type: string + mutual_user: + description: '' + type: string + password: + description: password + type: string + user: + description: username + type: string + type: object required: - user - password - mutual_user - mutual_password - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -3477,87 +3487,89 @@ paths: content: application/json: schema: - properties: - always_on: - description: Is it an always on module? - type: boolean - enabled: - description: Is Module Enabled - type: boolean - name: - description: Module Name - type: string - options: - description: Module Options - properties: - Option_name: - description: Options - properties: - default_value: - description: Default value for the option - type: integer - desc: - description: Description of the option - type: string - enum_allowed: - description: '' - items: + items: + properties: + always_on: + description: Is it an always on module? + type: boolean + enabled: + description: Is Module Enabled + type: boolean + name: + description: Module Name + type: string + options: + description: Module Options + properties: + Option_name: + description: Options + properties: + default_value: + description: Default value for the option + type: integer + desc: + description: Description of the option type: string - type: array - flags: - description: List of flags associated - type: integer - level: - description: Option level - type: string - long_desc: - description: Elaborated description - type: string - max: - description: Maximum value - type: string - min: - description: Minimum value - type: string - name: - description: Name of the option - type: string - see_also: - description: Related options - items: + enum_allowed: + description: '' + items: + type: string + type: array + flags: + description: List of flags associated + type: integer + level: + description: Option level type: string - type: array - tags: - description: Tags associated with the option - items: + long_desc: + description: Elaborated description type: string - type: array - type: - description: Type of the option - type: string - required: - - name - - type - - level - - flags - - default_value - - min - - max - - enum_allowed - - desc - - long_desc - - tags - - see_also - type: object - required: - - Option_name - type: object + max: + description: Maximum value + type: string + min: + description: Minimum value + type: string + name: + description: Name of the option + type: string + see_also: + description: Related options + items: + type: string + type: array + tags: + description: Tags associated with the option + items: + type: string + type: array + type: + description: Type of the option + type: string + required: + - name + - type + - level + - flags + - default_value + - min + - max + - enum_allowed + - desc + - long_desc + - tags + - see_also + type: object + required: + - Option_name + type: object + type: object required: - name - enabled - always_on - options - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -4116,23 +4128,25 @@ paths: content: application/json: schema: - properties: - cluster_id: - description: Cluster identifier - type: string - daemon_id: - description: Daemon identifier - type: string - desc: - description: Error description (if status==-1) - type: string - status: - description: Status of daemon (1=RUNNING, 0=STOPPED, -1=ERROR - type: integer + items: + properties: + cluster_id: + description: Cluster identifier + type: string + daemon_id: + description: Daemon identifier + type: string + desc: + description: Error description (if status==-1) + type: string + status: + description: Status of daemon (1=RUNNING, 0=STOPPED, -1=ERROR + type: integer + type: object required: - daemon_id - cluster_id - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -4156,88 +4170,90 @@ paths: content: application/json: schema: - properties: - access_type: - description: Export access type - type: string - clients: - description: List of client configurations - items: + items: + properties: + access_type: + description: Export access type + type: string + clients: + description: List of client configurations + items: + properties: + access_type: + description: Client access type + type: string + addresses: + description: list of IP addresses + items: + type: string + type: array + squash: + description: Client squash policy + type: string + required: + - addresses + - access_type + - squash + type: object + type: array + cluster_id: + description: Cluster identifier + type: string + daemons: + description: List of NFS Ganesha daemons identifiers + items: + type: string + type: array + export_id: + description: Export ID + type: integer + fsal: + description: FSAL configuration properties: - access_type: - description: Client access type + filesystem: + description: CephFS filesystem ID type: string - addresses: - description: list of IP addresses - items: - type: string - type: array - squash: - description: Client squash policy + name: + description: name of FSAL + type: string + rgw_user_id: + description: RGW user id + type: string + sec_label_xattr: + description: Name of xattr for security label + type: string + user_id: + description: CephX user id type: string required: - - addresses - - access_type - - squash + - name type: object - type: array - cluster_id: - description: Cluster identifier - type: string - daemons: - description: List of NFS Ganesha daemons identifiers - items: + path: + description: Export path type: string - type: array - export_id: - description: Export ID - type: integer - fsal: - description: FSAL configuration - properties: - filesystem: - description: CephFS filesystem ID - type: string - name: - description: name of FSAL - type: string - rgw_user_id: - description: RGW user id - type: string - sec_label_xattr: - description: Name of xattr for security label - type: string - user_id: - description: CephX user id + protocols: + description: List of protocol types + items: + type: integer + type: array + pseudo: + description: Pseudo FS path + type: string + security_label: + description: Security label + type: string + squash: + description: Export squash policy + type: string + tag: + description: NFSv3 export tag + type: string + transports: + description: List of transport types + items: type: string - required: - - name - type: object - path: - description: Export path - type: string - protocols: - description: List of protocol types - items: - type: integer - type: array - pseudo: - description: Pseudo FS path - type: string - security_label: - description: Security label - type: string - squash: - description: Export squash policy - type: string - tag: - description: NFSv3 export tag - type: string - transports: - description: List of transport types - items: - type: string - type: array + type: array + type: object required: - export_id - path @@ -4252,7 +4268,7 @@ paths: - transports - fsal - clients - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -5838,223 +5854,225 @@ paths: content: application/json: schema: - properties: - application_metadata: - description: '' - items: + items: + properties: + application_metadata: + description: '' + items: + type: string + type: array + auid: + description: '' + type: integer + cache_min_evict_age: + description: '' + type: integer + cache_min_flush_age: + description: '' + type: integer + cache_mode: + description: '' type: string - type: array - auid: - description: '' - type: integer - cache_min_evict_age: - description: '' - type: integer - cache_min_flush_age: - description: '' - type: integer - cache_mode: - description: '' - type: string - cache_target_dirty_high_ratio_micro: - description: '' - type: integer - cache_target_dirty_ratio_micro: - description: '' - type: integer - cache_target_full_ratio_micro: - description: '' - type: integer - create_time: - description: '' - type: string - crush_rule: - description: '' - type: string - erasure_code_profile: - description: '' - type: string - expected_num_objects: - description: '' - type: integer - fast_read: - description: '' - type: boolean - flags: - description: '' - type: integer - flags_names: - description: flags name - type: string - grade_table: - description: '' - items: + cache_target_dirty_high_ratio_micro: + description: '' + type: integer + cache_target_dirty_ratio_micro: + description: '' + type: integer + cache_target_full_ratio_micro: + description: '' + type: integer + create_time: + description: '' type: string - type: array - hit_set_count: - description: '' - type: integer - hit_set_grade_decay_rate: - description: '' - type: integer - hit_set_params: - description: '' - properties: - type: - description: '' - type: string - required: - - type - type: object - hit_set_period: - description: '' - type: integer - hit_set_search_last_n: - description: '' - type: integer - last_change: - description: '' - type: string - last_force_op_resend: - description: '' - type: string - last_force_op_resend_preluminous: - description: '' - type: string - last_force_op_resend_prenautilus: - description: '' - type: string - last_pg_merge_meta: - description: '' - properties: - last_epoch_clean: - description: '' - type: integer - last_epoch_started: - description: '' - type: integer - ready_epoch: - description: '' - type: integer - source_pgid: - description: '' + crush_rule: + description: '' + type: string + erasure_code_profile: + description: '' + type: string + expected_num_objects: + description: '' + type: integer + fast_read: + description: '' + type: boolean + flags: + description: '' + type: integer + flags_names: + description: flags name + type: string + grade_table: + description: '' + items: type: string - source_version: - description: '' + type: array + hit_set_count: + description: '' + type: integer + hit_set_grade_decay_rate: + description: '' + type: integer + hit_set_params: + description: '' + properties: + type: + description: '' + type: string + required: + - type + type: object + hit_set_period: + description: '' + type: integer + hit_set_search_last_n: + description: '' + type: integer + last_change: + description: '' + type: string + last_force_op_resend: + description: '' + type: string + last_force_op_resend_preluminous: + description: '' + type: string + last_force_op_resend_prenautilus: + description: '' + type: string + last_pg_merge_meta: + description: '' + properties: + last_epoch_clean: + description: '' + type: integer + last_epoch_started: + description: '' + type: integer + ready_epoch: + description: '' + type: integer + source_pgid: + description: '' + type: string + source_version: + description: '' + type: string + target_version: + description: '' + type: string + required: + - ready_epoch + - last_epoch_started + - last_epoch_clean + - source_pgid + - source_version + - target_version + type: object + min_read_recency_for_promote: + description: '' + type: integer + min_size: + description: '' + type: integer + min_write_recency_for_promote: + description: '' + type: integer + object_hash: + description: '' + type: integer + options: + description: '' + properties: + pg_num_min: + description: '' + type: integer + required: + - pg_num_min + type: object + pg_autoscale_mode: + description: '' + type: string + pg_num: + description: '' + type: integer + pg_num_pending: + description: '' + type: integer + pg_num_target: + description: '' + type: integer + pg_placement_num: + description: '' + type: integer + pg_placement_num_target: + description: '' + type: integer + pool: + description: pool id + type: integer + pool_name: + description: pool name + type: string + pool_snaps: + description: '' + items: type: string - target_version: - description: '' + type: array + quota_max_bytes: + description: '' + type: integer + quota_max_objects: + description: '' + type: integer + read_tier: + description: '' + type: integer + removed_snaps: + description: '' + items: type: string - required: - - ready_epoch - - last_epoch_started - - last_epoch_clean - - source_pgid - - source_version - - target_version - type: object - min_read_recency_for_promote: - description: '' - type: integer - min_size: - description: '' - type: integer - min_write_recency_for_promote: - description: '' - type: integer - object_hash: - description: '' - type: integer - options: - description: '' - properties: - pg_num_min: - description: '' - type: integer - required: - - pg_num_min - type: object - pg_autoscale_mode: - description: '' - type: string - pg_num: - description: '' - type: integer - pg_num_pending: - description: '' - type: integer - pg_num_target: - description: '' - type: integer - pg_placement_num: - description: '' - type: integer - pg_placement_num_target: - description: '' - type: integer - pool: - description: pool id - type: integer - pool_name: - description: pool name - type: string - pool_snaps: - description: '' - items: - type: string - type: array - quota_max_bytes: - description: '' - type: integer - quota_max_objects: - description: '' - type: integer - read_tier: - description: '' - type: integer - removed_snaps: - description: '' - items: + type: array + size: + description: pool size + type: integer + snap_epoch: + description: '' + type: integer + snap_mode: + description: '' type: string - type: array - size: - description: pool size - type: integer - snap_epoch: - description: '' - type: integer - snap_mode: - description: '' - type: string - snap_seq: - description: '' - type: integer - stripe_width: - description: '' - type: integer - target_max_bytes: - description: '' - type: integer - target_max_objects: - description: '' - type: integer - tier_of: - description: '' - type: integer - tiers: - description: '' - items: + snap_seq: + description: '' + type: integer + stripe_width: + description: '' + type: integer + target_max_bytes: + description: '' + type: integer + target_max_objects: + description: '' + type: integer + tier_of: + description: '' + type: integer + tiers: + description: '' + items: + type: string + type: array + type: + description: type of pool type: string - type: array - type: - description: type of pool - type: string - use_gmt_hitset: - description: '' - type: boolean - write_tier: - description: '' - type: integer + use_gmt_hitset: + description: '' + type: boolean + write_tier: + description: '' + type: integer + type: object required: - pool - pool_name @@ -6112,7 +6130,7 @@ paths: - last_force_op_resend_prenautilus - last_force_op_resend_preluminous - removed_snaps - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -6601,21 +6619,23 @@ paths: content: application/json: schema: - properties: - id: - description: Daemon ID - type: string - server_hostname: - description: '' - type: string - version: - description: Ceph Version - type: string + items: + properties: + id: + description: Daemon ID + type: string + server_hostname: + description: '' + type: string + version: + description: Ceph Version + type: string + type: object required: - id - version - server_hostname - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -7219,33 +7239,35 @@ paths: content: application/json: schema: - properties: - description: - description: Role Descriptions - type: string - name: - description: Role Name - type: string - scopes_permissions: - description: '' - properties: - cephfs: - description: '' - items: - type: string - type: array - required: - - cephfs - type: object - system: - description: '' - type: boolean + items: + properties: + description: + description: Role Descriptions + type: string + name: + description: Role Name + type: string + scopes_permissions: + description: '' + properties: + cephfs: + description: '' + items: + type: string + type: array + required: + - cephfs + type: object + system: + description: '' + type: boolean + type: object required: - name - description - scopes_permissions - system - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. @@ -7587,25 +7609,27 @@ paths: content: application/json: schema: - properties: - default: - description: Default Settings - type: boolean - name: - description: Settings Name - type: string - type: - description: Type of Settings - type: string - value: - description: Settings Value - type: boolean + items: + properties: + default: + description: Default Settings + type: boolean + name: + description: Settings Name + type: string + type: + description: Type of Settings + type: string + value: + description: Settings Value + type: boolean + type: object required: - name - default - type - value - type: object + type: array description: OK '400': description: Operation exception. Please check the response body for details. diff --git a/src/pybind/mgr/dashboard/tests/test_docs.py b/src/pybind/mgr/dashboard/tests/test_docs.py index db5eefdddec3..a86f2b678f3c 100644 --- a/src/pybind/mgr/dashboard/tests/test_docs.py +++ b/src/pybind/mgr/dashboard/tests/test_docs.py @@ -1,6 +1,7 @@ # # -*- coding: utf-8 -*- from __future__ import absolute_import +from ..api.doc import SchemaType from ..controllers import ApiController, ControllerDoc, Endpoint, EndpointDoc, RESTController from ..controllers.docs import Docs from . import ControllerTestCase # pylint: disable=no-name-in-module @@ -10,6 +11,8 @@ from . import ControllerTestCase # pylint: disable=no-name-in-module @ControllerDoc("Group description", group="FooGroup") @ApiController("/doctest/", secure=False) class DecoratedController(RESTController): + RESOURCE_ID = 'doctest' + @EndpointDoc( description="Endpoint description", group="BarGroup", @@ -17,12 +20,16 @@ class DecoratedController(RESTController): 'parameter': (int, "Description of parameter"), }, responses={ - 200: { - 'resp': (str, 'Description of response') + 200: [{ + 'my_prop': (str, '200 property desc.') + }], + 202: { + 'my_prop': (str, '202 property desc.') }, }, ) @Endpoint(json_response=False) + @RESTController.Resource('PUT') def decorated_func(self, parameter): pass @@ -54,18 +61,48 @@ class DocDecoratorsTest(ControllerTestCase): class DocsTest(ControllerTestCase): @classmethod def setup_server(cls): - cls.setup_controllers([Docs], "/test") + cls.setup_controllers([DecoratedController, Docs], "/test") def test_type_to_str(self): - self.assertEqual(Docs()._type_to_str(str), "string") + self.assertEqual(Docs()._type_to_str(str), str(SchemaType.STRING)) + self.assertEqual(Docs()._type_to_str(int), str(SchemaType.INTEGER)) + self.assertEqual(Docs()._type_to_str(bool), str(SchemaType.BOOLEAN)) + self.assertEqual(Docs()._type_to_str(list), str(SchemaType.ARRAY)) + self.assertEqual(Docs()._type_to_str(tuple), str(SchemaType.ARRAY)) + self.assertEqual(Docs()._type_to_str(float), str(SchemaType.NUMBER)) + self.assertEqual(Docs()._type_to_str(object), str(SchemaType.OBJECT)) + self.assertEqual(Docs()._type_to_str(None), str(SchemaType.OBJECT)) def test_gen_paths(self): - outcome = Docs()._gen_paths(False)['/api/doctest//decorated_func/{parameter}']['get'] + outcome = Docs()._gen_paths(False)['/api/doctest//{doctest}/decorated_func']['put'] self.assertIn('tags', outcome) self.assertIn('summary', outcome) self.assertIn('parameters', outcome) self.assertIn('responses', outcome) + expected_response_content = { + '200': { + 'application/json': { + 'schema': {'type': 'array', + 'items': {'type': 'object', 'properties': { + 'my_prop': { + 'type': 'string', + 'description': '200 property desc.'}}}, + 'required': ['my_prop']}}}, + '202': { + 'application/json': { + 'schema': {'type': 'object', + 'properties': {'my_prop': { + 'type': 'string', + 'description': '202 property desc.'}}, + 'required': ['my_prop']}} + } + } + # Check that a schema of type 'array' is received in the response. + self.assertEqual(expected_response_content['200'], outcome['responses']['200']['content']) + # Check that a schema of type 'object' is received in the response. + self.assertEqual(expected_response_content['202'], outcome['responses']['202']['content']) + def test_gen_paths_all(self): paths = Docs()._gen_paths(False) for key in paths: diff --git a/src/pybind/mgr/dashboard/tox.ini b/src/pybind/mgr/dashboard/tox.ini index efe18ffc8d7f..39ae748844cd 100644 --- a/src/pybind/mgr/dashboard/tox.ini +++ b/src/pybind/mgr/dashboard/tox.ini @@ -84,7 +84,7 @@ wrap_length = 80 [pylint] # Allow similarity/code duplication detection jobs = 1 -dirs = . controllers plugins services tests +dirs = . api controllers plugins services tests addopts = -rn --rcfile=.pylintrc --jobs={[pylint]jobs} [rstlint]