Beispiel #1
0
    def serve(self):
        server_addr = self.get_localized_module_option(
            'server_addr', get_default_addr())
        server_port = self.get_localized_module_option(
            'server_port', DEFAULT_PORT)
        self.log.info("server_addr: %s server_port: %s" %
                      (server_addr, server_port))
        cherrypy.config.update({
            'server.socket_host': server_addr,
            'server.socket_port': int(server_port),
            'engine.autoreload.on': False
        })

        module = self

        class Root(object):
            @cherrypy.expose
            def index(self):
                active_uri = module.get_active_uri()
                return '''<!DOCTYPE html>
<html>
    <head><title>Ceph Exporter</title></head>
    <body>
        <h1>Ceph Exporter</h1>
        <p><a href='{}metrics'>Metrics</a></p>
    </body>
</html>'''.format(active_uri)

            @cherrypy.expose
            def metrics(self):
                cherrypy.response.headers['Content-Type'] = 'text/plain'
                return ''

        cherrypy.tree.mount(Root(), '/', {})
        self.log.info('Starting engine...')
        cherrypy.engine.start()
        self.log.info('Engine started.')
        # Wait for shutdown event
        self.shutdown_event.wait()
        self.shutdown_event.clear()
        cherrypy.engine.stop()
        self.log.info('Engine stopped.')
Beispiel #2
0
    def serve(self):
        class Root(object):

            # collapse everything to '/'
            def _cp_dispatch(self, vpath):
                cherrypy.request.path = ''
                return self

            @cherrypy.expose
            def index(self):
                return '''<!DOCTYPE html>
<html>
    <head><title>Ceph Exporter</title></head>
    <body>
        <h1>Ceph Exporter</h1>
        <p><a href='/metrics'>Metrics</a></p>
    </body>
</html>'''

            @cherrypy.expose
            def metrics(self):
                # Lock the function execution
                assert isinstance(_global_instance, Module)
                with _global_instance.collect_lock:
                    return self._metrics(_global_instance)

            @staticmethod
            def _metrics(instance):
                # type: (Module) -> Any
                # Return cached data if available
                if not instance.collect_cache:
                    raise cherrypy.HTTPError(503,
                                             'No cached data available yet')

                def respond():
                    assert isinstance(instance, Module)
                    cherrypy.response.headers['Content-Type'] = 'text/plain'
                    return instance.collect_cache

                if instance.collect_time < instance.scrape_interval:
                    # Respond if cache isn't stale
                    return respond()

                if instance.stale_cache_strategy == instance.STALE_CACHE_RETURN:
                    # Respond even if cache is stale
                    instance.log.info(
                        'Gathering data took {:.2f} seconds, metrics are stale for {:.2f} seconds, '
                        'returning metrics from stale cache.'.format(
                            instance.collect_time,
                            instance.collect_time - instance.scrape_interval))
                    return respond()

                if instance.stale_cache_strategy == instance.STALE_CACHE_FAIL:
                    # Fail if cache is stale
                    msg = (
                        'Gathering data took {:.2f} seconds, metrics are stale for {:.2f} seconds, '
                        'returning "service unavailable".'.format(
                            instance.collect_time,
                            instance.collect_time - instance.scrape_interval,
                        ))
                    instance.log.error(msg)
                    raise cherrypy.HTTPError(503, msg)

        # Make the cache timeout for collecting configurable
        self.scrape_interval = float(
            self.get_localized_module_option('scrape_interval', 15.0))

        self.stale_cache_strategy = self.get_localized_module_option(
            'stale_cache_strategy', 'log')
        if self.stale_cache_strategy not in [
                self.STALE_CACHE_FAIL, self.STALE_CACHE_RETURN
        ]:
            self.stale_cache_strategy = self.STALE_CACHE_FAIL

        server_addr = self.get_localized_module_option('server_addr',
                                                       get_default_addr())
        server_port = self.get_localized_module_option('server_port',
                                                       DEFAULT_PORT)
        self.log.info("server_addr: %s server_port: %s" %
                      (server_addr, server_port))

        # Publish the URI that others may use to access the service we're
        # about to start serving
        self.set_uri('http://{0}:{1}/'.format(
            socket.getfqdn()
            if server_addr in ['::', '0.0.0.0'] else server_addr, server_port))

        cherrypy.config.update({
            'server.socket_host': server_addr,
            'server.socket_port': int(server_port),
            'engine.autoreload.on': False
        })
        cherrypy.tree.mount(Root(), "/")
        self.log.info('Starting engine...')
        cherrypy.engine.start()
        self.log.info('Engine started.')
        # wait for the shutdown event
        self.shutdown_event.wait()
        self.shutdown_event.clear()
        cherrypy.engine.stop()
        self.log.info('Engine stopped.')
        self.shutdown_rbd_stats()
Beispiel #3
0
    def serve(self):

        class Root(object):

            # collapse everything to '/'
            def _cp_dispatch(self, vpath):
                cherrypy.request.path = ''
                return self

            @cherrypy.expose
            def index(self):
                return '''<!DOCTYPE html>
<html>
    <head><title>Ceph Exporter</title></head>
    <body>
        <h1>Ceph Exporter</h1>
        <p><a href='/metrics'>Metrics</a></p>
    </body>
</html>'''

            @cherrypy.expose
            def metrics(self):
                instance = global_instance()
                # Lock the function execution
                try:
                    instance.collect_lock.acquire()
                    return self._metrics(instance)
                finally:
                    instance.collect_lock.release()

            @staticmethod
            def _metrics(instance):
                # Return cached data if available and collected before the
                # cache times out
                if instance.collect_cache and time.time() - instance.collect_time < instance.collect_timeout:
                    cherrypy.response.headers['Content-Type'] = 'text/plain'
                    return instance.collect_cache

                if instance.have_mon_connection():
                    instance.collect_cache = None
                    instance.collect_time = time.time()
                    instance.collect_cache = instance.collect()
                    cherrypy.response.headers['Content-Type'] = 'text/plain'
                    return instance.collect_cache
                else:
                    raise cherrypy.HTTPError(503, 'No MON connection')

        # Make the cache timeout for collecting configurable
        self.collect_timeout = float(self.get_localized_module_option(
            'scrape_interval', 5.0))

        server_addr = self.get_localized_module_option(
            'server_addr', get_default_addr())
        server_port = self.get_localized_module_option(
            'server_port', DEFAULT_PORT)
        self.log.info(
            "server_addr: %s server_port: %s" %
            (server_addr, server_port)
        )

        # Publish the URI that others may use to access the service we're
        # about to start serving
        self.set_uri('http://{0}:{1}/'.format(
            socket.getfqdn() if server_addr in ['::', '0.0.0.0'] else server_addr,
            server_port
        ))

        cherrypy.config.update({
            'server.socket_host': server_addr,
            'server.socket_port': int(server_port),
            'engine.autoreload.on': False
        })
        cherrypy.tree.mount(Root(), "/")
        self.log.info('Starting engine...')
        cherrypy.engine.start()
        self.log.info('Engine started.')
        # wait for the shutdown event
        self.shutdown_event.wait()
        self.shutdown_event.clear()
        cherrypy.engine.stop()
        self.log.info('Engine stopped.')
        self.shutdown_rbd_stats()
Beispiel #4
0
    def _configure(self):
        """
        Configure CherryPy and initialize self.url_prefix

        :returns our URI
        """
        server_addr = self.get_localized_module_option(  # type: ignore
            'server_addr', get_default_addr())
        ssl = self.get_localized_module_option('ssl', True)  # type: ignore
        if not ssl:
            server_port = self.get_localized_module_option(
                'server_port', 8080)  # type: ignore
        else:
            server_port = self.get_localized_module_option(
                'ssl_server_port', 8443)  # type: ignore

        if server_addr is None:
            raise ServerConfigException(
                'no server_addr configured; '
                'try "ceph config set mgr mgr/{}/{}/server_addr <ip>"'.format(
                    self.module_name, self.get_mgr_id()))  # type: ignore
        self.log.info(
            'server: ssl=%s host=%s port=%d',
            'yes' if ssl else 'no',  # type: ignore
            server_addr,
            server_port)

        # Initialize custom handlers.
        cherrypy.tools.authenticate = AuthManagerTool()
        cherrypy.tools.plugin_hooks_filter_request = cherrypy.Tool(
            'before_handler',
            lambda: PLUGIN_MANAGER.hook.filter_request_before_handler(
                request=cherrypy.request),
            priority=1)
        cherrypy.tools.request_logging = RequestLoggingTool()
        cherrypy.tools.dashboard_exception_handler = HandlerWrapperTool(
            dashboard_exception_handler, priority=31)

        cherrypy.log.access_log.propagate = False
        cherrypy.log.error_log.propagate = False

        # Apply the 'global' CherryPy configuration.
        config = {
            'engine.autoreload.on':
            False,
            'server.socket_host':
            server_addr,
            'server.socket_port':
            int(server_port),
            'error_page.default':
            json_error_page,
            'tools.request_logging.on':
            True,
            'tools.gzip.on':
            True,
            'tools.gzip.mime_types': [
                # text/html and text/plain are the default types to compress
                'text/html',
                'text/plain',
                # We also want JSON and JavaScript to be compressed
                'application/json',
                'application/javascript',
            ],
            'tools.json_in.on':
            True,
            'tools.json_in.force':
            False,
            'tools.plugin_hooks_filter_request.on':
            True,
        }

        if ssl:
            # SSL initialization
            cert = self.get_store("crt")  # type: ignore
            if cert is not None:
                self.cert_tmp = tempfile.NamedTemporaryFile()
                self.cert_tmp.write(cert.encode('utf-8'))
                self.cert_tmp.flush()  # cert_tmp must not be gc'ed
                cert_fname = self.cert_tmp.name
            else:
                cert_fname = self.get_localized_module_option(
                    'crt_file')  # type: ignore

            pkey = self.get_store("key")  # type: ignore
            if pkey is not None:
                self.pkey_tmp = tempfile.NamedTemporaryFile()
                self.pkey_tmp.write(pkey.encode('utf-8'))
                self.pkey_tmp.flush()  # pkey_tmp must not be gc'ed
                pkey_fname = self.pkey_tmp.name
            else:
                pkey_fname = self.get_localized_module_option(
                    'key_file')  # type: ignore

            verify_tls_files(cert_fname, pkey_fname)

            config['server.ssl_module'] = 'builtin'
            config['server.ssl_certificate'] = cert_fname
            config['server.ssl_private_key'] = pkey_fname

        self.update_cherrypy_config(config)

        self._url_prefix = prepare_url_prefix(
            self.get_module_option(  # type: ignore
                'url_prefix', default=''))

        uri = "{0}://{1}:{2}{3}/".format(
            'https' if ssl else 'http',
            socket.getfqdn() if server_addr in ['::', '0.0.0.0'] else
            server_addr, server_port, self.url_prefix)

        return uri
Beispiel #5
0
class Module(MgrModule, CherryPyConfig):
    """
    dashboard module entrypoint
    """

    COMMANDS = [
        {
            'cmd': 'dashboard set-jwt-token-ttl '
            'name=seconds,type=CephInt',
            'desc': 'Set the JWT token TTL in seconds',
            'perm': 'w'
        },
        {
            'cmd': 'dashboard get-jwt-token-ttl',
            'desc': 'Get the JWT token TTL in seconds',
            'perm': 'r'
        },
        {
            "cmd": "dashboard create-self-signed-cert",
            "desc": "Create self signed certificate",
            "perm": "w"
        },
        {
            "cmd": "dashboard grafana dashboards update",
            "desc": "Push dashboards to Grafana",
            "perm": "w",
        },
    ]
    COMMANDS.extend(options_command_list())
    COMMANDS.extend(SSO_COMMANDS)
    PLUGIN_MANAGER.hook.register_commands()

    MODULE_OPTIONS = [
        Option(name='server_addr', type='str', default=get_default_addr()),
        Option(name='server_port', type='int', default=8080),
        Option(name='ssl_server_port', type='int', default=8443),
        Option(name='jwt_token_ttl', type='int', default=28800),
        Option(name='password', type='str', default=''),
        Option(name='url_prefix', type='str', default=''),
        Option(name='username', type='str', default=''),
        Option(name='key_file', type='str', default=''),
        Option(name='crt_file', type='str', default=''),
        Option(name='ssl', type='bool', default=True),
        Option(name='standby_behaviour',
               type='str',
               default='redirect',
               enum_allowed=['redirect', 'error']),
        Option(name='standby_error_status_code',
               type='int',
               default=500,
               min=400,
               max=599)
    ]
    MODULE_OPTIONS.extend(options_schema_list())
    for options in PLUGIN_MANAGER.hook.get_options() or []:
        MODULE_OPTIONS.extend(options)

    __pool_stats = collections.defaultdict(lambda: collections.defaultdict(
        lambda: collections.deque(maxlen=10)))  # type: dict

    def __init__(self, *args, **kwargs):
        super(Module, self).__init__(*args, **kwargs)
        CherryPyConfig.__init__(self)

        mgr.init(self)

        self._stopping = threading.Event()
        self.shutdown_event = threading.Event()

        self.ACCESS_CTRL_DB = None
        self.SSO_DB = None

    @classmethod
    def can_run(cls):
        if cherrypy is None:
            return False, "Missing dependency: cherrypy"

        if not os.path.exists(cls.get_frontend_path()):
            return False, "Frontend assets not found: incomplete build?"

        return True, ""

    @classmethod
    def get_frontend_path(cls):
        current_dir = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(current_dir, 'frontend/dist')

    def serve(self):
        AuthManager.initialize()
        load_sso_db()

        uri = self.await_configuration()
        if uri is None:
            # We were shut down while waiting
            return

        # Publish the URI that others may use to access the service we're
        # about to start serving
        self.set_uri(uri)

        mapper, parent_urls = generate_routes(self.url_prefix)

        config = {}
        for purl in parent_urls:
            config[purl] = {'request.dispatch': mapper}

        cherrypy.tree.mount(None, config=config)

        PLUGIN_MANAGER.hook.setup()

        cherrypy.engine.start()
        NotificationQueue.start_queue()
        TaskManager.init()
        logger.info('Engine started.')
        update_dashboards = str_to_bool(
            self.get_module_option('GRAFANA_UPDATE_DASHBOARDS', 'False'))
        if update_dashboards:
            logger.info('Starting Grafana dashboard task')
            TaskManager.run(
                'grafana/dashboards/update',
                {},
                push_local_dashboards,
                kwargs=dict(tries=10, sleep=60),
            )
        # wait for the shutdown event
        self.shutdown_event.wait()
        self.shutdown_event.clear()
        NotificationQueue.stop()
        cherrypy.engine.stop()
        logger.info('Engine stopped')

    def shutdown(self):
        super(Module, self).shutdown()
        CherryPyConfig.shutdown(self)
        logger.info('Stopping engine...')
        self.shutdown_event.set()

    @CLIWriteCommand("dashboard set-ssl-certificate",
                     "name=mgr_id,type=CephString,req=false")
    def set_ssl_certificate(self, mgr_id=None, inbuf=None):
        if inbuf is None:
            return -errno.EINVAL, '',\
                   'Please specify the certificate file with "-i" option'
        if mgr_id is not None:
            self.set_store('{}/crt'.format(mgr_id), inbuf)
        else:
            self.set_store('crt', inbuf)
        return 0, 'SSL certificate updated', ''

    @CLIWriteCommand("dashboard set-ssl-certificate-key",
                     "name=mgr_id,type=CephString,req=false")
    def set_ssl_certificate_key(self, mgr_id=None, inbuf=None):
        if inbuf is None:
            return -errno.EINVAL, '',\
                   'Please specify the certificate key file with "-i" option'
        if mgr_id is not None:
            self.set_store('{}/key'.format(mgr_id), inbuf)
        else:
            self.set_store('key', inbuf)
        return 0, 'SSL certificate key updated', ''

    def handle_command(self, inbuf, cmd):
        # pylint: disable=too-many-return-statements
        res = handle_option_command(cmd)
        if res[0] != -errno.ENOSYS:
            return res
        res = handle_sso_command(cmd)
        if res[0] != -errno.ENOSYS:
            return res
        if cmd['prefix'] == 'dashboard set-jwt-token-ttl':
            self.set_module_option('jwt_token_ttl', str(cmd['seconds']))
            return 0, 'JWT token TTL updated', ''
        if cmd['prefix'] == 'dashboard get-jwt-token-ttl':
            ttl = self.get_module_option('jwt_token_ttl',
                                         JwtManager.JWT_TOKEN_TTL)
            return 0, str(ttl), ''
        if cmd['prefix'] == 'dashboard create-self-signed-cert':
            self.create_self_signed_cert()
            return 0, 'Self-signed certificate created', ''
        if cmd['prefix'] == 'dashboard grafana dashboards update':
            push_local_dashboards()
            return 0, 'Grafana dashboards updated', ''

        return (-errno.EINVAL, '',
                'Command not found \'{0}\''.format(cmd['prefix']))

    def create_self_signed_cert(self):
        cert, pkey = create_self_signed_cert('IT', 'ceph-dashboard')
        self.set_store('crt', cert)
        self.set_store('key', pkey)

    def notify(self, notify_type, notify_id):
        NotificationQueue.new_notification(notify_type, notify_id)

    def get_updated_pool_stats(self):
        df = self.get('df')
        pool_stats = {p['id']: p['stats'] for p in df['pools']}
        now = time.time()
        for pool_id, stats in pool_stats.items():
            for stat_name, stat_val in stats.items():
                self.__pool_stats[pool_id][stat_name].append((now, stat_val))

        return self.__pool_stats
Beispiel #6
0
class Module(MgrModule, CherryPyConfig):
    """
    dashboard module entrypoint
    """

    COMMANDS = [
        {
            'cmd': 'dashboard set-jwt-token-ttl '
            'name=seconds,type=CephInt',
            'desc': 'Set the JWT token TTL in seconds',
            'perm': 'w'
        },
        {
            'cmd': 'dashboard get-jwt-token-ttl',
            'desc': 'Get the JWT token TTL in seconds',
            'perm': 'r'
        },
        {
            "cmd": "dashboard create-self-signed-cert",
            "desc": "Create self signed certificate",
            "perm": "w"
        },
        {
            "cmd": "dashboard grafana dashboards update",
            "desc": "Push dashboards to Grafana",
            "perm": "w",
        },
    ]
    COMMANDS.extend(options_command_list())
    COMMANDS.extend(SSO_COMMANDS)
    PLUGIN_MANAGER.hook.register_commands()

    MODULE_OPTIONS = [
        Option(name='server_addr', type='str', default=get_default_addr()),
        Option(name='server_port', type='int', default=8080),
        Option(name='ssl_server_port', type='int', default=8443),
        Option(name='jwt_token_ttl', type='int', default=28800),
        Option(name='url_prefix', type='str', default=''),
        Option(name='key_file', type='str', default=''),
        Option(name='crt_file', type='str', default=''),
        Option(name='ssl', type='bool', default=True),
        Option(name='standby_behaviour',
               type='str',
               default='redirect',
               enum_allowed=['redirect', 'error']),
        Option(name='standby_error_status_code',
               type='int',
               default=500,
               min=400,
               max=599)
    ]
    MODULE_OPTIONS.extend(options_schema_list())
    for options in PLUGIN_MANAGER.hook.get_options() or []:
        MODULE_OPTIONS.extend(options)

    __pool_stats = collections.defaultdict(lambda: collections.defaultdict(
        lambda: collections.deque(maxlen=10)))  # type: dict

    def __init__(self, *args, **kwargs):
        super(Module, self).__init__(*args, **kwargs)
        CherryPyConfig.__init__(self)

        mgr.init(self)

        self._stopping = threading.Event()
        self.shutdown_event = threading.Event()
        self.ACCESS_CTRL_DB = None
        self.SSO_DB = None
        self.health_checks = {}

    @classmethod
    def can_run(cls):
        if cherrypy is None:
            return False, "Missing dependency: cherrypy"

        if not os.path.exists(cls.get_frontend_path()):
            return False, (
                "Frontend assets not found at '{}': incomplete build?".format(
                    cls.get_frontend_path()))

        return True, ""

    @classmethod
    def get_frontend_path(cls):
        current_dir = os.path.dirname(os.path.abspath(__file__))
        path = os.path.join(current_dir, 'frontend/dist')
        if os.path.exists(path):
            return path
        else:
            path = os.path.join(current_dir, '../../../../build',
                                'src/pybind/mgr/dashboard', 'frontend/dist')
            return os.path.abspath(path)

    def serve(self):

        if 'COVERAGE_ENABLED' in os.environ:
            import coverage
            __cov = coverage.Coverage(config_file="{}/.coveragerc".format(
                os.path.dirname(__file__)),
                                      data_suffix=True)
            __cov.start()
            cherrypy.engine.subscribe('after_request', __cov.save)
            cherrypy.engine.subscribe('stop', __cov.stop)

        AuthManager.initialize()
        load_sso_db()

        uri = self.await_configuration()
        if uri is None:
            # We were shut down while waiting
            return

        # Publish the URI that others may use to access the service we're
        # about to start serving
        self.set_uri(uri)

        mapper, parent_urls = generate_routes(self.url_prefix)

        config = {}
        for purl in parent_urls:
            config[purl] = {'request.dispatch': mapper}

        cherrypy.tree.mount(None, config=config)

        PLUGIN_MANAGER.hook.setup()

        cherrypy.engine.start()
        NotificationQueue.start_queue()
        TaskManager.init()
        logger.info('Engine started.')
        update_dashboards = str_to_bool(
            self.get_module_option('GRAFANA_UPDATE_DASHBOARDS', 'False'))
        if update_dashboards:
            logger.info('Starting Grafana dashboard task')
            TaskManager.run(
                'grafana/dashboards/update',
                {},
                push_local_dashboards,
                kwargs=dict(tries=10, sleep=60),
            )
        # wait for the shutdown event
        self.shutdown_event.wait()
        self.shutdown_event.clear()
        NotificationQueue.stop()
        cherrypy.engine.stop()
        logger.info('Engine stopped')

    def shutdown(self):
        super(Module, self).shutdown()
        CherryPyConfig.shutdown(self)
        logger.info('Stopping engine...')
        self.shutdown_event.set()

    def _set_ssl_item(self,
                      item_label: str,
                      item_key: 'SslConfigKey' = 'crt',
                      mgr_id: Optional[str] = None,
                      inbuf: Optional[str] = None):
        if inbuf is None:
            return -errno.EINVAL, '', f'Please specify the {item_label} with "-i" option'

        if mgr_id is not None:
            self.set_store(_get_localized_key(mgr_id, item_key), inbuf)
        else:
            self.set_store(item_key, inbuf)
        return 0, f'SSL {item_label} updated', ''

    @CLIWriteCommand("dashboard set-ssl-certificate")
    def set_ssl_certificate(self,
                            mgr_id: Optional[str] = None,
                            inbuf: Optional[str] = None):
        return self._set_ssl_item('certificate', 'crt', mgr_id, inbuf)

    @CLIWriteCommand("dashboard set-ssl-certificate-key")
    def set_ssl_certificate_key(self,
                                mgr_id: Optional[str] = None,
                                inbuf: Optional[str] = None):
        return self._set_ssl_item('certificate key', 'key', mgr_id, inbuf)

    @CLIWriteCommand("dashboard create-self-signed-cert")
    def set_mgr_created_self_signed_cert(self):
        cert, pkey = create_self_signed_cert('IT', 'ceph-dashboard')
        result = HandleCommandResult(*self.set_ssl_certificate(inbuf=cert))
        if result.retval != 0:
            return result

        result = HandleCommandResult(*self.set_ssl_certificate_key(inbuf=pkey))
        if result.retval != 0:
            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:
            configure_rgw_credentials()
        except Exception as error:
            return -errno.EINVAL, '', str(error)

        return 0, 'RGW credentials configured', ''

    def handle_command(self, inbuf, cmd):
        # pylint: disable=too-many-return-statements
        res = handle_option_command(cmd, inbuf)
        if res[0] != -errno.ENOSYS:
            return res
        res = handle_sso_command(cmd)
        if res[0] != -errno.ENOSYS:
            return res
        if cmd['prefix'] == 'dashboard set-jwt-token-ttl':
            self.set_module_option('jwt_token_ttl', str(cmd['seconds']))
            return 0, 'JWT token TTL updated', ''
        if cmd['prefix'] == 'dashboard get-jwt-token-ttl':
            ttl = self.get_module_option('jwt_token_ttl',
                                         JwtManager.JWT_TOKEN_TTL)
            return 0, str(ttl), ''
        if cmd['prefix'] == 'dashboard grafana dashboards update':
            push_local_dashboards()
            return 0, 'Grafana dashboards updated', ''

        return (-errno.EINVAL, '',
                'Command not found \'{0}\''.format(cmd['prefix']))

    def notify(self, notify_type, notify_id):
        NotificationQueue.new_notification(notify_type, notify_id)

    def get_updated_pool_stats(self):
        df = self.get('df')
        pool_stats = {p['id']: p['stats'] for p in df['pools']}
        now = time.time()
        for pool_id, stats in pool_stats.items():
            for stat_name, stat_val in stats.items():
                self.__pool_stats[pool_id][stat_name].append((now, stat_val))

        return self.__pool_stats

    def config_notify(self):
        """
        This method is called whenever one of our config options is changed.
        """
        PLUGIN_MANAGER.hook.config_notify()

    def refresh_health_checks(self):
        self.set_health_checks(self.health_checks)
Beispiel #7
0
    def _configure(self):
        """
        Configure CherryPy and initialize self.url_prefix

        :returns our URI
        """
        server_addr = self.get_localized_module_option('server_addr',
                                                       get_default_addr())
        ssl = self.get_localized_module_option('ssl', True)
        if not ssl:
            server_port = self.get_localized_module_option('server_port', 8080)
        else:
            server_port = self.get_localized_module_option(
                'ssl_server_port', 8443)

        if server_addr is None:
            raise ServerConfigException(
                'no server_addr configured; '
                'try "ceph config set mgr mgr/{}/{}/server_addr <ip>"'.format(
                    self.module_name, self.get_mgr_id()))
        self.log.info('server: ssl=%s host=%s port=%d', 'yes' if ssl else 'no',
                      server_addr, server_port)

        # Initialize custom handlers.
        cherrypy.tools.authenticate = AuthManagerTool()
        cherrypy.tools.plugin_hooks = cherrypy.Tool(
            'before_handler',
            lambda: PLUGIN_MANAGER.hook.filter_request_before_handler(
                request=cherrypy.request),
            priority=10)
        cherrypy.tools.request_logging = RequestLoggingTool()
        cherrypy.tools.dashboard_exception_handler = HandlerWrapperTool(
            dashboard_exception_handler, priority=31)

        # Apply the 'global' CherryPy configuration.
        config = {
            'engine.autoreload.on':
            False,
            'server.socket_host':
            server_addr,
            'server.socket_port':
            int(server_port),
            'error_page.default':
            json_error_page,
            'tools.request_logging.on':
            True,
            'tools.gzip.on':
            True,
            'tools.gzip.mime_types': [
                # text/html and text/plain are the default types to compress
                'text/html',
                'text/plain',
                # We also want JSON and JavaScript to be compressed
                'application/json',
                'application/javascript',
            ],
            'tools.json_in.on':
            True,
            'tools.json_in.force':
            False,
            'tools.plugin_hooks.on':
            True,
        }

        if ssl:
            # SSL initialization
            cert = self.get_store("crt")
            if cert is not None:
                self.cert_tmp = tempfile.NamedTemporaryFile()
                self.cert_tmp.write(cert.encode('utf-8'))
                self.cert_tmp.flush()  # cert_tmp must not be gc'ed
                cert_fname = self.cert_tmp.name
            else:
                cert_fname = self.get_localized_module_option('crt_file')

            pkey = self.get_store("key")
            if pkey is not None:
                self.pkey_tmp = tempfile.NamedTemporaryFile()
                self.pkey_tmp.write(pkey.encode('utf-8'))
                self.pkey_tmp.flush()  # pkey_tmp must not be gc'ed
                pkey_fname = self.pkey_tmp.name
            else:
                pkey_fname = self.get_localized_module_option('key_file')

            if not cert_fname or not pkey_fname:
                raise ServerConfigException('no certificate configured')
            if not os.path.isfile(cert_fname):
                raise ServerConfigException('certificate %s does not exist' %
                                            cert_fname)
            if not os.path.isfile(pkey_fname):
                raise ServerConfigException('private key %s does not exist' %
                                            pkey_fname)

            # Do some validations to the private key and certificate:
            # - Check the type and format
            # - Check the certificate expiration date
            # - Check the consistency of the private key
            # - Check that the private key and certificate match up
            try:
                with open(cert_fname) as f:
                    x509 = crypto.load_certificate(crypto.FILETYPE_PEM,
                                                   f.read())
                    if x509.has_expired():
                        self.log.warning(
                            'Certificate {} has been expired'.format(
                                cert_fname))
            except (ValueError, crypto.Error) as e:
                raise ServerConfigException(
                    'Invalid certificate {}: {}'.format(cert_fname, str(e)))
            try:
                with open(pkey_fname) as f:
                    pkey = crypto.load_privatekey(crypto.FILETYPE_PEM,
                                                  f.read())
                    pkey.check()
            except (ValueError, crypto.Error) as e:
                raise ServerConfigException(
                    'Invalid private key {}: {}'.format(pkey_fname, str(e)))
            try:
                context = SSL.Context(SSL.TLSv1_METHOD)
                context.use_certificate_file(cert_fname, crypto.FILETYPE_PEM)
                context.use_privatekey_file(pkey_fname, crypto.FILETYPE_PEM)
                context.check_privatekey()
            except crypto.Error as e:
                self.log.warning(
                    'Private key {} and certificate {} do not match up: {}'.
                    format(pkey_fname, cert_fname, str(e)))

            config['server.ssl_module'] = 'builtin'
            config['server.ssl_certificate'] = cert_fname
            config['server.ssl_private_key'] = pkey_fname

        cherrypy.config.update(config)

        self._url_prefix = prepare_url_prefix(
            self.get_module_option('url_prefix', default=''))

        uri = "{0}://{1}:{2}{3}/".format(
            'https' if ssl else 'http',
            socket.getfqdn() if server_addr == "::" else server_addr,
            server_port, self.url_prefix)

        return uri
Beispiel #8
0
class Module(MgrModule, CherryPyConfig):
    """
    dashboard module entrypoint
    """

    COMMANDS = [
        {
            'cmd': 'dashboard set-jwt-token-ttl '
            'name=seconds,type=CephInt',
            'desc': 'Set the JWT token TTL in seconds',
            'perm': 'w'
        },
        {
            'cmd': 'dashboard get-jwt-token-ttl',
            'desc': 'Get the JWT token TTL in seconds',
            'perm': 'r'
        },
        {
            "cmd": "dashboard create-self-signed-cert",
            "desc": "Create self signed certificate",
            "perm": "w"
        },
        {
            "cmd": "dashboard grafana dashboards update",
            "desc": "Push dashboards to Grafana",
            "perm": "w",
        },
    ]
    COMMANDS.extend(options_command_list())
    COMMANDS.extend(SSO_COMMANDS)
    PLUGIN_MANAGER.hook.register_commands()

    MODULE_OPTIONS = [
        Option(name='server_addr', type='str', default=get_default_addr()),
        Option(name='server_port', type='int', default=8080),
        Option(name='ssl_server_port', type='int', default=8443),
        Option(name='jwt_token_ttl', type='int', default=28800),
        Option(name='password', type='str', default=''),
        Option(name='url_prefix', type='str', default=''),
        Option(name='username', type='str', default=''),
        Option(name='key_file', type='str', default=''),
        Option(name='crt_file', type='str', default=''),
        Option(name='ssl', type='bool', default=True)
    ]
    MODULE_OPTIONS.extend(options_schema_list())
    for options in PLUGIN_MANAGER.hook.get_options() or []:
        MODULE_OPTIONS.extend(options)

    __pool_stats = collections.defaultdict(
        lambda: collections.defaultdict(lambda: collections.deque(maxlen=10)))

    def __init__(self, *args, **kwargs):
        super(Module, self).__init__(*args, **kwargs)
        CherryPyConfig.__init__(self)

        mgr.init(self)

        self._stopping = threading.Event()
        self.shutdown_event = threading.Event()

        self.ACCESS_CTRL_DB = None
        self.SSO_DB = None

    @classmethod
    def can_run(cls):
        if cherrypy is None:
            return False, "Missing dependency: cherrypy"

        if not os.path.exists(cls.get_frontend_path()):
            return False, "Frontend assets not found: incomplete build?"

        return True, ""

    @classmethod
    def get_frontend_path(cls):
        current_dir = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(current_dir, 'frontend/dist')

    def serve(self):
        AuthManager.initialize()
        load_sso_db()

        uri = self.await_configuration()
        if uri is None:
            # We were shut down while waiting
            return

        # Publish the URI that others may use to access the service we're
        # about to start serving
        self.set_uri(uri)

        mapper, parent_urls = generate_routes(self.url_prefix)

        config = {
            self.url_prefix or '/': {
                'tools.staticdir.on': True,
                'tools.staticdir.dir': self.get_frontend_path(),
                'tools.staticdir.index': 'index.html'
            }
        }
        for purl in parent_urls:
            config[purl] = {'request.dispatch': mapper}
        cherrypy.tree.mount(None, config=config)

        PLUGIN_MANAGER.hook.setup()

        cherrypy.engine.start()
        NotificationQueue.start_queue()
        TaskManager.init()
        logger.info('Engine started.')
        update_dashboards = str_to_bool(
            self.get_module_option('GRAFANA_UPDATE_DASHBOARDS', 'False'))
        if update_dashboards:
            logger.info('Starting Grafana dashboard task')
            TaskManager.run(
                'grafana/dashboards/update',
                {},
                push_local_dashboards,
                kwargs=dict(tries=10, sleep=60),
            )
        # wait for the shutdown event
        self.shutdown_event.wait()
        self.shutdown_event.clear()
        NotificationQueue.stop()
        cherrypy.engine.stop()
        logger.info('Engine stopped')

    def shutdown(self):
        super(Module, self).shutdown()
        CherryPyConfig.shutdown(self)
        logger.info('Stopping engine...')
        self.shutdown_event.set()

    def handle_command(self, inbuf, cmd):
        # pylint: disable=too-many-return-statements
        res = handle_option_command(cmd)
        if res[0] != -errno.ENOSYS:
            return res
        res = handle_sso_command(cmd)
        if res[0] != -errno.ENOSYS:
            return res
        if cmd['prefix'] == 'dashboard set-jwt-token-ttl':
            self.set_module_option('jwt_token_ttl', str(cmd['seconds']))
            return 0, 'JWT token TTL updated', ''
        if cmd['prefix'] == 'dashboard get-jwt-token-ttl':
            ttl = self.get_module_option('jwt_token_ttl',
                                         JwtManager.JWT_TOKEN_TTL)
            return 0, str(ttl), ''
        if cmd['prefix'] == 'dashboard create-self-signed-cert':
            self.create_self_signed_cert()
            return 0, 'Self-signed certificate created', ''
        if cmd['prefix'] == 'dashboard grafana dashboards update':
            push_local_dashboards()
            return 0, 'Grafana dashboards updated', ''

        return (-errno.EINVAL, '',
                'Command not found \'{0}\''.format(cmd['prefix']))

    def create_self_signed_cert(self):
        # create a key pair
        pkey = crypto.PKey()
        pkey.generate_key(crypto.TYPE_RSA, 2048)

        # create a self-signed cert
        cert = crypto.X509()
        cert.get_subject().O = "IT"
        cert.get_subject().CN = "ceph-dashboard"
        cert.set_serial_number(int(uuid4()))
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
        cert.set_issuer(cert.get_subject())
        cert.set_pubkey(pkey)
        cert.sign(pkey, 'sha512')

        cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
        self.set_store('crt', cert.decode('utf-8'))

        pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
        self.set_store('key', pkey.decode('utf-8'))

    def notify(self, notify_type, notify_id):
        NotificationQueue.new_notification(notify_type, notify_id)

    def get_updated_pool_stats(self):
        df = self.get('df')
        pool_stats = {p['id']: p['stats'] for p in df['pools']}
        now = time.time()
        for pool_id, stats in pool_stats.items():
            for stat_name, stat_val in stats.items():
                self.__pool_stats[pool_id][stat_name].append((now, stat_val))

        return self.__pool_stats