...
$ ceph config reset 11
+
+Reporting issues from Dashboard
+"""""""""""""""""""""""""""""""
+
+Ceph-Dashboard provides two ways to create an issue in the Ceph Issue Tracker,
+either using the Ceph command line interface or by using the Ceph Dashboard
+user interface.
+
+To create an issue in the Ceph Issue Tracker, a user needs to have an account
+on the issue tracker. Under the ``my account`` tab in the Ceph Issue Tracker,
+the user can see their API access key. This key is used for authentication
+when creating a new issue. To store the Ceph API access key, in the CLI run:
+
+``ceph dashboard set-issue-tracker-api-key -i <file-containing-key>``
+
+Then on successful update, you can create an issue using:
+
+``ceph dashboard create issue <project> <tracker_type> <subject> <description>``
+
+The available projects to create an issue on are:
+#. dashboard
+#. block
+#. object
+#. file_system
+#. ceph_manager
+#. orchestrator
+#. ceph_volume
+#. core_ceph
+
+The available tracker types are:
+#. bug
+#. feature
+
+The subject and description are then set by the user.
+
+The user can also create an issue using the Dashboard user interface. The settings
+icon drop down menu on the top right of the navigation bar has the option to
+``Raise an issue``. On clicking it, a modal dialog opens that has the option to
+select the project and tracker from their respective drop down menus. The subject
+and multiline description are added by the user. The user can then submit the issue.
+
--- /dev/null
+# # -*- coding: utf-8 -*-
+
+from ..exceptions import DashboardException
+from ..model.feedback import Feedback
+from ..rest_client import RequestException
+from ..security import Scope
+from ..services import feedback
+from . import ApiController, ControllerDoc, RESTController
+
+
+@ApiController('/feedback', Scope.CONFIG_OPT)
+@ControllerDoc("Feedback API", "Report")
+class FeedbackController(RESTController):
+ issueAPIkey = None
+
+ def __init__(self): # pragma: no cover
+ super(FeedbackController, self).__init__()
+ self.tracker_client = feedback.CephTrackerClient()
+
+ @RESTController.MethodMap(version='0.1')
+ def create(self, project, tracker, subject, description):
+ """
+ Create an issue.
+ :param project: The affected ceph component.
+ :param tracker: The tracker type.
+ :param subject: The title of the issue.
+ :param description: The description of the issue.
+ """
+ try:
+ new_issue = Feedback(Feedback.Project[project].value,
+ Feedback.TrackerType[tracker].value, subject, description)
+ except KeyError:
+ raise DashboardException(msg=f'{"Invalid arguments"}', component='feedback')
+ try:
+ return self.tracker_client.create_issue(new_issue)
+ except RequestException as error:
+ if error.status_code == 401:
+ raise DashboardException(msg=f'{"Invalid API key"}',
+ http_status_code=error.status_code,
+ component='feedback')
+ raise error
+ except Exception:
+ raise DashboardException(msg=f'{"API key not set"}',
+ http_status_code=401,
+ component='feedback')
+
+ @RESTController.MethodMap(version='0.1')
+ def get(self, issue_number):
+ """
+ Fetch issue details.
+ :param issueAPI: The issue tracker API access key.
+ """
+ try:
+ return self.tracker_client.get_issues(issue_number)
+ except RequestException as error:
+ if error.status_code == 404:
+ raise DashboardException(msg=f'Issue {issue_number} not found',
+ http_status_code=error.status_code,
+ component='feedback')
+ raise error
--- /dev/null
+# -*- coding: utf-8 -*-
--- /dev/null
+# # -*- coding: utf-8 -*-
+from enum import Enum
+
+
+class Feedback:
+ project_id: int
+ tracker_id: int
+ subject: str
+ description: str
+ status: int
+
+ class Project(Enum):
+ dashboard = 46
+ block = 9 # rbd
+ object = 10 # rgw
+ file_system = 13 # cephfs
+ ceph_manager = 46
+ orchestrator = 42
+ ceph_volume = 39
+ core_ceph = 36 # rados
+
+ class TrackerType(Enum):
+ bug = 1
+ feature = 2
+
+ class Status(Enum):
+ new = 1
+
+ def __init__(self, project_id, tracker_id, subject, description):
+ self.project_id = int(project_id)
+ self.tracker_id = int(tracker_id)
+ self.subject = subject
+ self.description = description
+ self.status = Feedback.Status.new.value
+
+ def as_dict(self):
+ return {
+ "issue": {
+ "project": {
+ "id": self.project_id
+ },
+ "tracker_id": self.tracker_id,
+ "Status": self.status,
+ "subject": self.subject,
+ "description": self.description
+ }
+ }
else:
from typing_extensions import Literal
-from mgr_module import CLIWriteCommand, HandleCommandResult, MgrModule, \
- MgrStandbyModule, Option, _get_localized_key
+from mgr_module import CLICommand, CLIWriteCommand, HandleCommandResult, \
+ MgrModule, MgrStandbyModule, Option, _get_localized_key
from mgr_util import ServerConfigException, build_url, \
create_self_signed_cert, get_default_addr, verify_tls_files
from . import mgr
from .controllers import generate_routes, json_error_page
from .grafana import push_local_dashboards
+from .model.feedback import Feedback
+from .rest_client import RequestException
from .services.auth import AuthManager, AuthManagerTool, JwtManager
from .services.exception import dashboard_exception_handler
+from .services.feedback import CephTrackerClient
from .services.rgw_client import configure_rgw_credentials
from .services.sso import SSO_COMMANDS, handle_sso_command
from .settings import handle_option_command, options_command_list, options_schema_list
return result
return 0, 'Self-signed certificate created', ''
+ @CLICommand("dashboard get issue")
+ def get_issues_cli(self, issue_number: int):
+ try:
+ issue_number = int(issue_number)
+ except TypeError:
+ return -errno.EINVAL, '', f'Invalid issue number {issue_number}'
+ tracker_client = CephTrackerClient()
+ try:
+ response = tracker_client.get_issues(issue_number)
+ except RequestException as error:
+ if error.status_code == 404:
+ return -errno.EINVAL, '', f'Issue {issue_number} not found'
+ else:
+ return -errno.EREMOTEIO, '', f'Error: {str(error)}'
+ return 0, str(response), ''
+
+ @CLICommand("dashboard create issue")
+ def report_issues_cli(self, project: str, tracker: str, subject: str, description: str):
+ '''
+ Create an issue in the Ceph Issue tracker
+ Syntax: ceph dashboard create issue <project> <bug|feature> <subject> <description>
+ '''
+ try:
+ feedback = Feedback(Feedback.Project[project].value,
+ Feedback.TrackerType[tracker].value, subject, description)
+ except KeyError:
+ return -errno.EINVAL, '', 'Invalid arguments'
+ tracker_client = CephTrackerClient()
+ try:
+ response = tracker_client.create_issue(feedback)
+ except RequestException as error:
+ if error.status_code == 401:
+ return -errno.EINVAL, '', 'Invalid API Key'
+ else:
+ return -errno.EINVAL, '', f'Error: {str(error)}'
+ except Exception:
+ return -errno.EINVAL, '', 'Ceph Tracker API key not set'
+ return 0, str(response), ''
+
@CLIWriteCommand("dashboard set-rgw-credentials")
def set_rgw_credentials(self):
try:
summary: Get List Of Features
tags:
- FeatureTogglesEndpoint
+ /api/feedback:
+ post:
+ description: "\n Create an issue.\n :param project: The affected\
+ \ ceph component.\n :param tracker: The tracker type.\n :param\
+ \ subject: The title of the issue.\n :param description: The description\
+ \ of the issue.\n "
+ parameters: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ description:
+ type: string
+ project:
+ type: string
+ subject:
+ type: string
+ tracker:
+ type: string
+ required:
+ - project
+ - tracker
+ - subject
+ - description
+ type: object
+ responses:
+ '201':
+ content:
+ application/vnd.ceph.api.v0.1+json:
+ type: object
+ description: Resource created.
+ '202':
+ content:
+ application/vnd.ceph.api.v0.1+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - Report
+ /api/feedback/{issue_number}:
+ get:
+ description: "\n Fetch issue details.\n :param issueAPI: The issue\
+ \ tracker API access key.\n "
+ parameters:
+ - in: path
+ name: issue_number
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v0.1+json:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - Report
/api/grafana/dashboards:
post:
parameters: []
name: RbdSnapshot
- description: RBD Trash Management API
name: RbdTrash
+- description: Feedback API
+ name: Report
- description: RGW Management API
name: Rgw
- description: RGW Bucket Management API
--- /dev/null
+# -*- coding: utf-8 -*-
+
+import json
+
+import requests
+from requests.auth import AuthBase
+
+from ..model.feedback import Feedback
+from ..rest_client import RequestException, RestClient
+from ..settings import Settings
+
+
+class config:
+ url = 'tracker.ceph.com'
+ port = 443
+
+
+class RedmineAuth(AuthBase):
+ def __init__(self):
+ try:
+ self.access_key = Settings.ISSUE_TRACKER_API_KEY
+ except KeyError:
+ self.access_key = None
+
+ def __call__(self, r):
+ r.headers['X-Redmine-API-Key'] = self.access_key
+ return r
+
+
+class CephTrackerClient(RestClient):
+ access_key = ''
+
+ def __init__(self):
+ super().__init__(config.url, config.port, client_name='CephTracker',
+ ssl=True, auth=RedmineAuth(), ssl_verify=True)
+
+ @staticmethod
+ def get_api_key():
+ try:
+ access_key = Settings.ISSUE_TRACKER_API_KEY
+ except KeyError:
+ raise KeyError("Key not set")
+ if access_key == '':
+ raise KeyError("Empty key")
+ return access_key
+
+ def get_issues(self, issue_number):
+ '''
+ Fetch an issue from the Ceph Issue tracker
+ '''
+ headers = {
+ 'Content-Type': 'application/json',
+ }
+ response = requests.get(
+ f'https://tracker.ceph.com/issues/{issue_number}.json', headers=headers)
+ if not response.ok:
+ raise RequestException(
+ "Request failed with status code {}\n"
+ .format(response.status_code),
+ self._handle_response_status_code(response.status_code),
+ response.content)
+ return {"message": response.text}
+
+ def create_issue(self, feedback: Feedback):
+ '''
+ Create an issue in the Ceph Issue tracker
+ '''
+ try:
+ headers = {
+ 'Content-Type': 'application/json',
+ 'X-Redmine-API-Key': self.get_api_key(),
+ }
+ except KeyError:
+ raise Exception("Ceph Tracker API Key not set")
+ data = json.dumps(feedback.as_dict())
+ response = requests.post(
+ f'https://tracker.ceph.com/projects/{feedback.project_id}/issues.json',
+ headers=headers, data=data)
+ if not response.ok:
+ raise RequestException(
+ "Request failed with status code {}\n"
+ .format(response.status_code),
+ self._handle_response_status_code(response.status_code),
+ response.content)
+ return {"message": response.text}
RGW_API_ADMIN_RESOURCE = Setting('admin', [str])
RGW_API_SSL_VERIFY = Setting(True, [bool])
+ # Ceph Issue Tracker API Access Key
+ ISSUE_TRACKER_API_KEY = Setting('', [str])
+
# Grafana settings
GRAFANA_API_URL = Setting('', [str])
GRAFANA_FRONTEND_API_URL = Setting('', [str])