]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: enable SSL in standby mode
authorJohn Spray <john.spray@redhat.com>
Thu, 26 Apr 2018 10:47:32 +0000 (06:47 -0400)
committerJohn Spray <john.spray@redhat.com>
Fri, 27 Apr 2018 13:58:47 +0000 (09:58 -0400)
Signed-off-by: John Spray <john.spray@redhat.com>
src/pybind/mgr/dashboard/module.py

index 67f85d9ee2049de85af01c0586003031dfce543c..8e7c16a20e83f89082901c831725b8913feba8dd 100644 (file)
@@ -84,73 +84,28 @@ class ServerConfigException(Exception):
     pass
 
 
-class Module(MgrModule):
+class SSLCherryPyConfig(object):
     """
-    dashboard module entrypoint
+    Class for common server configuration done by both active and
+    standby module, especially setting up SSL.
     """
+    def __init__(self):
+        self._stopping = threading.Event()
+        self._url_prefix = ""
 
-    COMMANDS = [
-        {
-            'cmd': 'dashboard set-login-credentials '
-                   'name=username,type=CephString '
-                   'name=password,type=CephString',
-            'desc': 'Set the login credentials',
-            'perm': 'w'
-        },
-        {
-            'cmd': 'dashboard set-session-expire '
-                   'name=seconds,type=CephInt',
-            'desc': 'Set the session expire timeout',
-            'perm': 'w'
-        },
-        {
-            "cmd": "dashboard create-self-signed-cert",
-            "desc": "Create self signed certificate",
-            "perm": "w"
-        },
-    ]
-    COMMANDS.extend(options_command_list())
-
-    OPTIONS = [
-        {'name': 'server_addr'},
-        {'name': 'server_port'},
-        {'name': 'session-expire'},
-        {'name': 'password'},
-        {'name': 'url_prefix'},
-        {'name': 'username'},
-        {'name': 'key_file'},
-        {'name': 'crt_file'},
-    ]
-    OPTIONS.extend(options_schema_list())
+    def shutdown(self):
+        self._stopping.set()
 
     @property
     def url_prefix(self):
         return self._url_prefix
 
-    def __init__(self, *args, **kwargs):
-        super(Module, self).__init__(*args, **kwargs)
-        mgr.init(self)
-        self._url_prefix = ''
-
-        self._stopping = threading.Event()
+    def _configure(self):
+        """
+        Configure CherryPy and initialize self.url_prefix
 
-    @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 configure_cherrypy(self):
-        # pylint: disable=too-many-locals
+        :returns our URI
+        """
         server_addr = self.get_localized_config('server_addr', '::')
         server_port = self.get_localized_config('server_port', '8080')
         if server_addr is None:
@@ -161,9 +116,6 @@ class Module(MgrModule):
         self.log.info('server_addr: %s server_port: %s', server_addr,
                       server_port)
 
-        self._url_prefix = prepare_url_prefix(self.get_config('url_prefix',
-                                                              default=''))
-
         # Initialize custom handlers.
         cherrypy.tools.authenticate = cherrypy.Tool('before_handler', Auth.check_auth)
         cherrypy.tools.session_expire_at_browser_close = SessionExpireAtBrowserCloseTool()
@@ -208,33 +160,27 @@ class Module(MgrModule):
         }
         cherrypy.config.update(config)
 
-        # Publish the URI that others may use to access the service we're
-        # about to start serving
-        self.set_uri("https://{0}:{1}{2}/".format(
+        self._url_prefix = prepare_url_prefix(self.get_config('url_prefix',
+                                                              default=''))
+
+        uri = "https://{0}:{1}{2}/".format(
             socket.getfqdn() if server_addr == "::" else server_addr,
             server_port,
             self.url_prefix
-        ))
+        )
 
-        mapper = generate_routes(self.url_prefix)
+        return uri
 
-        config = {
-            '{}/'.format(self.url_prefix): {
-                'tools.staticdir.on': True,
-                'tools.staticdir.dir': self.get_frontend_path(),
-                'tools.staticdir.index': 'index.html'
-            },
-            '{}/api'.format(self.url_prefix): {'request.dispatch': mapper}
-        }
-        cherrypy.tree.mount(None, config=config)
-
-    def serve(self):
-        if 'COVERAGE_ENABLED' in os.environ:
-            _cov.start()
+    def await_configuration(self):
+        """
+        Block until configuration is ready (i.e. all needed keys are set)
+        or self._stopping is set.
 
+        :returns URI of configured webserver
+        """
         while not self._stopping.is_set():
             try:
-                self.configure_cherrypy()
+                uri = self._configure()
             except ServerConfigException as e:
                 self.log.info("Config not ready to serve, waiting: {0}".format(
                     e
@@ -243,7 +189,94 @@ class Module(MgrModule):
                 self._stopping.wait(5)
             else:
                 self.log.info("Configured CherryPy, starting engine...")
-                break
+                return uri
+
+class Module(MgrModule, SSLCherryPyConfig):
+    """
+    dashboard module entrypoint
+    """
+
+    COMMANDS = [
+        {
+            'cmd': 'dashboard set-login-credentials '
+                   'name=username,type=CephString '
+                   'name=password,type=CephString',
+            'desc': 'Set the login credentials',
+            'perm': 'w'
+        },
+        {
+            'cmd': 'dashboard set-session-expire '
+                   'name=seconds,type=CephInt',
+            'desc': 'Set the session expire timeout',
+            'perm': 'w'
+        },
+        {
+            "cmd": "dashboard create-self-signed-cert",
+            "desc": "Create self signed certificate",
+            "perm": "w"
+        },
+    ]
+    COMMANDS.extend(options_command_list())
+
+    OPTIONS = [
+        {'name': 'server_addr'},
+        {'name': 'server_port'},
+        {'name': 'session-expire'},
+        {'name': 'password'},
+        {'name': 'url_prefix'},
+        {'name': 'username'},
+        {'name': 'key_file'},
+        {'name': 'crt_file'},
+    ]
+    OPTIONS.extend(options_schema_list())
+
+    def __init__(self, *args, **kwargs):
+        super(Module, self).__init__(*args, **kwargs)
+        SSLCherryPyConfig.__init__(self)
+
+        mgr.init(self)
+
+        self._stopping = threading.Event()
+
+    @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):
+        if 'COVERAGE_ENABLED' in os.environ:
+            _cov.start()
+
+        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 = generate_routes(self.url_prefix)
+
+        config = {
+            '{}/'.format(self.url_prefix): {
+                'tools.staticdir.on': True,
+                'tools.staticdir.dir': self.get_frontend_path(),
+                'tools.staticdir.index': 'index.html'
+            },
+            '{}/api'.format(self.url_prefix): {'request.dispatch': mapper}
+        }
+        cherrypy.tree.mount(None, config=config)
 
         cherrypy.engine.start()
         NotificationQueue.start_queue()
@@ -258,7 +291,7 @@ class Module(MgrModule):
     def shutdown(self):
         super(Module, self).shutdown()
 
-        self._stopping.set()
+        SSLCherryPyConfig.shutdown(self)
 
         logger.info('Stopping server...')
         NotificationQueue.stop()
@@ -308,21 +341,20 @@ class Module(MgrModule):
         NotificationQueue.new_notification(notify_type, notify_id)
 
 
-class StandbyModule(MgrStandbyModule):
+class StandbyModule(MgrStandbyModule, SSLCherryPyConfig):
+    def __init__(self, *args, **kwargs):
+        super(StandbyModule, self).__init__(*args, **kwargs)
+        SSLCherryPyConfig.__init__(self)
+
+        # We can set the global mgr instance to ourselves even though
+        # we're just a standby, because it's enough for logging.
+        mgr.init(self)
+
     def serve(self):
-        server_addr = self.get_localized_config('server_addr', '::')
-        server_port = self.get_localized_config('server_port', '7000')
-        if server_addr is None:
-            msg = 'no server_addr configured; try "ceph config-key set ' \
-                  'mgr/dashboard/server_addr <ip>"'
-            raise RuntimeError(msg)
-        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
-        })
+        uri = self.await_configuration()
+        if uri is None:
+            # We were shut down while waiting
+            return
 
         module = self
 
@@ -352,9 +384,7 @@ class StandbyModule(MgrStandbyModule):
                     """
                     return template.format(delay=5)
 
-        url_prefix = prepare_url_prefix(self.get_config('url_prefix',
-                                                        default=''))
-        cherrypy.tree.mount(Root(), "{}/".format(url_prefix), {})
+        cherrypy.tree.mount(Root(), "{}/".format(self.url_prefix), {})
         self.log.info("Starting engine...")
         cherrypy.engine.start()
         self.log.info("Waiting for engine...")
@@ -362,6 +392,8 @@ class StandbyModule(MgrStandbyModule):
         self.log.info("Engine done.")
 
     def shutdown(self):
+        SSLCherryPyConfig.shutdown(self)
+
         self.log.info("Stopping server...")
         cherrypy.engine.wait(state=cherrypy.engine.states.STARTED)
         cherrypy.engine.stop()