# -*- coding: utf-8 -*-
import json
+
import requests
-from . import APIDoc, APIRouter, CreatePermission, UpdatePermission, ReadPermission, Endpoint, EndpointDoc, RESTController
from ..exceptions import DashboardException
-from ..settings import Settings
from ..security import Scope
+from ..settings import Settings
+from ..tools import configure_cors
+from . import APIDoc, APIRouter, CreatePermission, Endpoint, EndpointDoc, \
+ RESTController, UIRouter, UpdatePermission
@APIRouter('/multi-cluster', Scope.CONFIG_OPT)
@APIDoc('Multi-cluster Management API', 'Multi-cluster')
class MultiCluster(RESTController):
- def _proxy(self, method, base_url, path, params=None, payload=None, verify=False):
+ def _proxy(self, method, base_url, path, params=None, payload=None, verify=False, token=None):
try:
- headers = {
- 'Accept': 'application/vnd.ceph.api.v1.0+json',
- 'Content-Type': 'application/json',
- }
+ if token:
+ headers = {
+ 'Accept': 'application/vnd.ceph.api.v1.0+json',
+ 'Authorization': 'Bearer ' + token,
+ }
+ else:
+ headers = {
+ 'Accept': 'application/vnd.ceph.api.v1.0+json',
+ 'Content-Type': 'application/json',
+ }
response = requests.request(method, base_url + path, params=params,
json=payload, verify=verify, headers=headers)
except Exception as e:
raise DashboardException(
- "Could not reach {}".format(base_url+path),
+ "Could not reach {}, {}".format(base_url+path, e),
http_status_code=404,
component='dashboard')
component='dashboard')
return content
-
@Endpoint('POST')
@CreatePermission
@EndpointDoc("Authenticate to a remote cluster")
- def auth(self, url: str, name: str, username=None, password=None, token=None):
+ def auth(self, url: str, name: str, username=None, password=None, token=None, hub_url=None):
multicluster_config = {}
if isinstance(Settings.MULTICLUSTER_CONFIG, str):
else:
multicluster_config = Settings.MULTICLUSTER_CONFIG.copy()
+ if 'hub_url' not in multicluster_config:
+ multicluster_config['hub_url'] = hub_url
+
if 'config' not in multicluster_config:
multicluster_config['config'] = []
http_status_code=400,
component='dashboard')
- else:
- token = content['token']
+ token = content['token']
+ # Set CORS endpoint on remote cluster
+ self._proxy('PUT', url, 'ui-api/multi-cluster/set_cors_endpoint',
+ payload={'url': multicluster_config['hub_url']}, token=token)
+
+ multicluster_config['config'].append({
+ 'name': name,
+ 'url': url,
+ 'token': token
+ })
+
+ Settings.MULTICLUSTER_CONFIG = multicluster_config
- multicluster_config['config'].append({
- 'name': name,
- 'url': url,
- 'token': token
- })
- Settings.MULTICLUSTER_CONFIG = multicluster_config
+@UIRouter('/multi-cluster', Scope.CONFIG_OPT)
+class MultiClusterUi(RESTController):
+ @Endpoint('PUT')
+ @UpdatePermission
+ def set_cors_endpoint(self, url: str):
+ configure_cors(url)
from .services.sso import SSO_COMMANDS, handle_sso_command
from .settings import handle_option_command, options_command_list, options_schema_list
from .tools import NotificationQueue, RequestLoggingTool, TaskManager, \
- prepare_url_prefix, str_to_bool
+ configure_cors, prepare_url_prefix, str_to_bool
try:
import cherrypy
# Initialize custom handlers.
cherrypy.tools.authenticate = AuthManagerTool()
- self.configure_cors()
+ configure_cors()
cherrypy.tools.plugin_hooks_filter_request = cherrypy.Tool(
'before_handler',
lambda: PLUGIN_MANAGER.hook.filter_request_before_handler(request=cherrypy.request),
self.log.info("Configured CherryPy, starting engine...") # type: ignore
return uri
- def configure_cors(self):
- """
- Allow CORS requests if the cross_origin_url option is set.
- """
- cross_origin_url = mgr.get_localized_module_option('cross_origin_url', '')
- if cross_origin_url:
- cherrypy.tools.CORS = cherrypy.Tool('before_handler', self.cors_tool)
- config = {
- 'tools.CORS.on': True,
- }
- self.update_cherrypy_config(config)
-
- def cors_tool(self):
- '''
- Handle both simple and complex CORS requests
-
- Add CORS headers to each response. If the request is a CORS preflight
- request swap out the default handler with a simple, single-purpose handler
- that verifies the request and provides a valid CORS response.
- '''
- req_head = cherrypy.request.headers
- resp_head = cherrypy.response.headers
-
- # Always set response headers necessary for 'simple' CORS.
- req_header_cross_origin_url = req_head.get('Access-Control-Allow-Origin')
- cross_origin_urls = mgr.get_localized_module_option('cross_origin_url', '')
- cross_origin_url_list = [url.strip() for url in cross_origin_urls.split(',')]
- if req_header_cross_origin_url in cross_origin_url_list:
- resp_head['Access-Control-Allow-Origin'] = req_header_cross_origin_url
- resp_head['Access-Control-Expose-Headers'] = 'GET, POST'
- resp_head['Access-Control-Allow-Credentials'] = 'true'
-
- # Non-simple CORS preflight request; short-circuit the normal handler.
- if cherrypy.request.method == 'OPTIONS':
- req_header_origin_url = req_head.get('Origin')
- if req_header_origin_url in cross_origin_url_list:
- resp_head['Access-Control-Allow-Origin'] = req_header_origin_url
- ac_method = req_head.get('Access-Control-Request-Method', None)
-
- allowed_methods = ['GET', 'POST', 'PUT']
- allowed_headers = [
- 'Content-Type',
- 'Authorization',
- 'Accept',
- 'Access-Control-Allow-Origin'
- ]
-
- if ac_method and ac_method in allowed_methods:
- resp_head['Access-Control-Allow-Methods'] = ', '.join(allowed_methods)
- resp_head['Access-Control-Allow-Headers'] = ', '.join(allowed_headers)
-
- resp_head['Connection'] = 'keep-alive'
- resp_head['Access-Control-Max-Age'] = '3600'
-
- # CORS requests should short-circuit the other tools.
- cherrypy.response.body = ''.encode('utf8')
- cherrypy.response.status = 200
- cherrypy.serving.request.handler = None
-
- # Needed to avoid the auth_tool check.
- if cherrypy.request.config.get('tools.sessions.on', False):
- cherrypy.session['token'] = True
- return True
-
if TYPE_CHECKING:
SslConfigKey = Literal['crt', 'key']
summary: Get Monitor Details
tags:
- Monitor
+ /api/multi-cluster/auth:
+ post:
+ parameters: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ hub_url:
+ type: string
+ name:
+ type: string
+ password:
+ type: string
+ token:
+ type: string
+ url:
+ type: string
+ username:
+ type: string
+ required:
+ - url
+ - name
+ type: object
+ responses:
+ '201':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource created.
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+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: []
+ summary: Authenticate to a remote cluster
+ tags:
+ - Multi-cluster
/api/nfs-ganesha/cluster:
get:
parameters: []
name: MonPerfCounter
- description: Get Monitor Details
name: Monitor
+- description: Multi-cluster Management API
+ name: Multi-cluster
- description: NFS-Ganesha Cluster Management API
name: NFS-Ganesha
- description: NVMe-oF Gateway Management API
target_list[sdict[key]].update(sdict)
target_list = [value for value in target_list.values()]
return target_list
+
+
+def configure_cors(url: str = ''):
+ """
+ Allow CORS requests if the cross_origin_url option is set.
+ """
+ if url:
+ cross_origin_url = url
+ mgr.set_module_option('cross_origin_url', cross_origin_url)
+ else:
+ cross_origin_url = mgr.get_localized_module_option('cross_origin_url', '')
+ if cross_origin_url:
+ cherrypy.tools.CORS = cherrypy.Tool('before_handler', cors_tool)
+ config = {
+ 'tools.CORS.on': True,
+ }
+ cherrypy.config.update(config)
+
+
+def cors_tool():
+ '''
+ Handle both simple and complex CORS requests
+ Add CORS headers to each response. If the request is a CORS preflight
+ request swap out the default handler with a simple, single-purpose handler
+ that verifies the request and provides a valid CORS response.
+ '''
+ req_head = cherrypy.request.headers
+ resp_head = cherrypy.response.headers
+
+ # Always set response headers necessary for 'simple' CORS.
+ req_header_cross_origin_url = req_head.get('Access-Control-Allow-Origin')
+ cross_origin_urls = mgr.get_localized_module_option('cross_origin_url', '')
+ cross_origin_url_list = [url.strip() for url in cross_origin_urls.split(',')]
+ if req_header_cross_origin_url in cross_origin_url_list:
+ resp_head['Access-Control-Allow-Origin'] = req_header_cross_origin_url
+ resp_head['Access-Control-Expose-Headers'] = 'GET, POST'
+ resp_head['Access-Control-Allow-Credentials'] = 'true'
+
+ # Non-simple CORS preflight request; short-circuit the normal handler.
+ if cherrypy.request.method == 'OPTIONS':
+ req_header_origin_url = req_head.get('Origin')
+ if req_header_origin_url in cross_origin_url_list:
+ resp_head['Access-Control-Allow-Origin'] = req_header_origin_url
+ ac_method = req_head.get('Access-Control-Request-Method', None)
+
+ allowed_methods = ['GET', 'POST', 'PUT']
+ allowed_headers = [
+ 'Content-Type',
+ 'Authorization',
+ 'Accept',
+ 'Access-Control-Allow-Origin'
+ ]
+
+ if ac_method and ac_method in allowed_methods:
+ resp_head['Access-Control-Allow-Methods'] = ', '.join(allowed_methods)
+ resp_head['Access-Control-Allow-Headers'] = ', '.join(allowed_headers)
+
+ resp_head['Connection'] = 'keep-alive'
+ resp_head['Access-Control-Max-Age'] = '3600'
+
+ # CORS requests should short-circuit the other tools.
+ cherrypy.response.body = ''.encode('utf8')
+ cherrypy.response.status = 200
+ cherrypy.serving.request.handler = None
+
+ # Needed to avoid the auth_tool check.
+ if cherrypy.request.config.get('tools.sessions.on', False):
+ cherrypy.session['token'] = True
+ return True