mirror of
https://github.com/ceph/ceph
synced 2025-02-24 11:37:37 +00:00
mgr/dashboard: report ceph tracker bug/feature through CLI/API
Fixes: https://tracker.ceph.com/issues/44851 Signed-off-by: Shreya Sharma <shreyasharma.ss305@gmail.com>
This commit is contained in:
parent
384c2f04c5
commit
e90ebc195e
@ -1458,3 +1458,44 @@ something like this::
|
||||
...
|
||||
$ 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.
|
||||
|
||||
|
60
src/pybind/mgr/dashboard/controllers/feedback.py
Normal file
60
src/pybind/mgr/dashboard/controllers/feedback.py
Normal file
@ -0,0 +1,60 @@
|
||||
# # -*- 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
|
1
src/pybind/mgr/dashboard/model/__init__.py
Normal file
1
src/pybind/mgr/dashboard/model/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
47
src/pybind/mgr/dashboard/model/feedback.py
Normal file
47
src/pybind/mgr/dashboard/model/feedback.py
Normal file
@ -0,0 +1,47 @@
|
||||
# # -*- 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
|
||||
}
|
||||
}
|
@ -19,16 +19,19 @@ if TYPE_CHECKING:
|
||||
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
|
||||
@ -406,6 +409,45 @@ class Module(MgrModule, CherryPyConfig):
|
||||
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:
|
||||
|
@ -2644,6 +2644,85 @@ paths:
|
||||
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: []
|
||||
@ -10361,6 +10440,8 @@ tags:
|
||||
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
|
||||
|
85
src/pybind/mgr/dashboard/services/feedback.py
Normal file
85
src/pybind/mgr/dashboard/services/feedback.py
Normal file
@ -0,0 +1,85 @@
|
||||
# -*- 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}
|
@ -68,6 +68,9 @@ class Options(object):
|
||||
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])
|
||||
|
Loading…
Reference in New Issue
Block a user