From efd339cdde2a9e3f3355d3aad86a3610979f825c Mon Sep 17 00:00:00 2001 From: Lenz Grimmer Date: Thu, 15 Feb 2018 12:28:41 +0100 Subject: [PATCH] mgr/dashboard_v2: Updated README.rst, added HACKING.rst Moved developer-related documentation from `README.rst` to a new file `HACKING.rst`. Converted `frontend/README.md` to ReST and merged it into `HACKING.rst`. Added updates to the README provided by @rjfd in PR#83 (updated the whole unit tests content to match the latest changes on unit test execution, updated the development notes about controller development) Signed-off-by: Lenz Grimmer --- src/pybind/mgr/dashboard_v2/HACKING.rst | 464 ++++++++++++++++++ src/pybind/mgr/dashboard_v2/README.rst | 331 +------------ .../mgr/dashboard_v2/frontend/README.md | 75 --- 3 files changed, 486 insertions(+), 384 deletions(-) create mode 100644 src/pybind/mgr/dashboard_v2/HACKING.rst delete mode 100644 src/pybind/mgr/dashboard_v2/frontend/README.md diff --git a/src/pybind/mgr/dashboard_v2/HACKING.rst b/src/pybind/mgr/dashboard_v2/HACKING.rst new file mode 100644 index 0000000000000..3cda2278d4bc9 --- /dev/null +++ b/src/pybind/mgr/dashboard_v2/HACKING.rst @@ -0,0 +1,464 @@ +Dashboard v2 Developer Documentation +==================================== + +Frontend Development +-------------------- + +Before you can start the dashboard from within a development environment, you +will need to generate the frontend code and either use a compiled and running +Ceph cluster (e.g. started by ``vstart.sh``) or the standalone development web +server. + +The build process is based on `Node.js `_ and requires the +`Node Package Manager `_ ``npm`` to be installed. + +Prerequisites +~~~~~~~~~~~~~ + +Run ``npm install`` in directory ``src/pybind/mgr/dashboard_v2/frontend`` to +install the required packages locally. + +.. note:: + + If you do not have the `Angular CLI `_ + installed globally, then you need to execute ``ng`` commands with an + additional ``npm run`` before it. + +Setting up a Development Server +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create the ``proxy.conf.json`` file based on ``proxy.conf.json.sample``. + +Run ``npm start -- --proxy-config proxy.conf.json`` for a dev server. +Navigate to ``http://localhost:4200/``. The app will automatically +reload if you change any of the source files. + +Code Scaffolding +~~~~~~~~~~~~~~~~ + +Run ``ng generate component component-name`` to generate a new +component. You can also use +``ng generate directive|pipe|service|class|guard|interface|enum|module``. + +Build the Project +~~~~~~~~~~~~~~~~~ + +Run ``npm run build`` to build the project. The build artifacts will be +stored in the ``dist/`` directory. Use the ``-prod`` flag for a +production build. Navigate to ``http://localhost:8080``. + +Running Unit Tests +~~~~~~~~~~~~~~~~~~ + +Run ``npm run test`` to execute the unit tests via `Karma +`_. + +Running End-to-End Tests +~~~~~~~~~~~~~~~~~~~~~~~~ + +Run ``npm run e2e`` to execute the end-to-end tests via +`Protractor `__. + +Further Help +~~~~~~~~~~~~ + +To get more help on the Angular CLI use ``ng help`` or go check out the +`Angular CLI +README `__. + +Example of a Generator +~~~~~~~~~~~~~~~~~~~~~~ + +:: + + # Create module 'Core' + src/app> ng generate module core -m=app --routing + + # Create module 'Auth' under module 'Core' + src/app/core> ng generate module auth -m=core --routing + or, alternatively: + src/app> ng generate module core/auth -m=core --routing + + # Create component 'Login' under module 'Auth' + src/app/core/auth> ng generate component login -m=core/auth + or, alternatively: + src/app> ng generate component core/auth/login -m=core/auth + +Frontend Typescript Code Style Guide Recommendations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Group the imports based on its source and separate them with a blank +line. + +The source groups can be either from Angular, external or internal. + +Example: + +.. code:: javascript + + import { Component } from '@angular/core'; + import { Router } from '@angular/router'; + + import { ToastsManager } from 'ng2-toastr'; + + import { Credentials } from '../../../shared/models/credentials.model'; + import { HostService } from './services/host.service'; + + +Backend Development +------------------- + +The Python backend code of this module requires a number of Python modules to be +installed. They are listed in file ``requirements.txt``. Using `pip +`_ you may install all required dependencies +by issuing ``pip install -r requirements.txt`` in directory +``src/pybind/mgr/dashboard_v2``. + +If you're using the `ceph-dev-docker development environment +`_, simply run +``./install_deps.sh`` from the toplevel directory to install them. + +Unit Testing and Linting +~~~~~~~~~~~~~~~~~~~~~~~~ + +We included a ``tox`` configuration file that will run the unit tests under +Python 2 or 3, as well as linting tools to guarantee the uniformity of code. + +You need to install ``tox`` and ``coverage`` before running it. To install the +packages in your system, either install it via your operating system's package +management tools, e.g. by running ``dnf install python-tox python-coverage`` on +Fedora Linux. + +Alternatively, you can use Python's native package installation method:: + + $ pip install tox + $ pip install coverage + +The unit tests must run against a real Ceph cluster (no mocks are used). This +has the advantage of catching bugs originated from changes in the internal Ceph +code. + +Our ``tox.ini`` script will start a ``vstart`` Ceph cluster before running the +python unit tests, and then it stops the cluster after the tests are run. Of +course this implies that you have built/compiled Ceph previously. + +To run tox, run the following command in the root directory (where ``tox.ini`` +is located):: + + $ PATH=../../../../build/bin:$PATH tox + +We also collect coverage information from the backend code. You can check the +coverage information provided by the tox output, or by running the following +command after tox has finished successfully:: + + $ coverage html + +This command will create a directory ``htmlcov`` with an HTML representation of +the code coverage of the backend. + +You can also run a single step of the tox script (aka tox environment), for +instance if you only want to run the linting tools, do:: + + $ PATH=../../../../build/bin:$PATH tox -e lint + +How to run a single unit test without using ``tox``? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When developing the code of a controller and respective test code, it's useful +to be able to run that single test file without going through the whole ``tox`` +workflow. + +Since the tests must run against a real Ceph cluster, the first thing is to have +a Ceph cluster running. For that we can leverage the tox environment that starts +a Ceph cluster:: + + $ PATH=../../../../build/bin:$PATH tox -e ceph-cluster-start + +The command above uses ``vstart.sh`` script to start a Ceph cluster and +automatically enables the ``dashboard_v2`` module, and configures its cherrypy +web server to listen in port ``9865``. + +After starting the Ceph cluster we can run our test file using ``py.test`` like +this:: + + DASHBOARD_V2_PORT=9865 UNITTEST=true py.test -s tests/test_mycontroller.py + +You can run tests multiple times without having to start and stop the Ceph +cluster. + +After you finish your tests, you can stop the Ceph cluster using another tox +environment:: + + $ tox -e ceph-cluster-stop + +How to add a new controller? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to add a new endpoint to the backend, you just need to add a +class derived from ``BaseController`` decorated with ``ApiController`` in a +Python file located under the ``controllers`` directory. The Dashboard module +will automatically load your new controller upon start. + +For example create a file ``ping2.py`` under ``controllers`` directory with the +following code:: + + import cherrypy + from ..tools import ApiController, BaseController + + @ApiController('ping2') + class Ping2(BaseController): + @cherrypy.expose + def default(self, *args): + return "Hello" + +Every path given in the ``ApiController`` decorator will automatically be +prefixed with ``api``. After reloading the Dashboard module you can access the +above mentioned controller by pointing your browser to +http://mgr_hostname:8080/api/ping2. + +It is also possible to have nested controllers. The ``RgwController`` uses +this technique to make the daemons available through the URL +http://mgr_hostname:8080/api/rgw/daemon:: + + @ApiController('rgw') + @AuthRequired() + class Rgw(RESTController): + pass + + + @ApiController('rgw/daemon') + @AuthRequired() + class RgwDaemon(RESTController): + + def list(self): + pass + + +Note that paths must be unique and that a path like ``rgw/daemon`` has to have +a parent ``rgw``. Otherwise it won't work. + +How does the RESTController work? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We also provide a simple mechanism to create REST based controllers using the +``RESTController`` class. Any class which inherits from ``RESTController`` will, +by default, return JSON. + +The ``RESTController`` is basically an additional abstraction layer which eases +and unifies the work with collections. A collection is just an array of objects +with a specific type. ``RESTController`` enables some default mappings of +request types and given parameters to specific method names. This may sound +complicated at first, but it's fairly easy. Lets have look at the following +example:: + + import cherrypy + from ..tools import ApiController, RESTController + + @ApiController('ping2') + class Ping2(RESTController): + def list(self): + return {"msg": "Hello"} + + def get(self, id): + return self.objects[id] + +In this case, the ``list`` method is automatically used for all requests to +``api/ping2`` where no additional argument is given and where the request type +is ``GET``. If the request is given an additional argument, the ID in our +case, it won't map to ``list`` anymore but to ``get`` and return the element +with the given ID (assuming that ``self.objects`` has been filled before). The +same applies to other request types: + ++--------------+------------+----------------+-------------+ +| Request type | Arguments | Method | Status Code | ++==============+============+================+=============+ +| GET | No | list | 200 | ++--------------+------------+----------------+-------------+ +| PUT | No | bulk_set | 200 | ++--------------+------------+----------------+-------------+ +| PATCH | No | bulk_set | 200 | ++--------------+------------+----------------+-------------+ +| POST | No | create | 201 | ++--------------+------------+----------------+-------------+ +| DELETE | No | bulk_delete | 204 | ++--------------+------------+----------------+-------------+ +| GET | Yes | get | 200 | ++--------------+------------+----------------+-------------+ +| PUT | Yes | set | 200 | ++--------------+------------+----------------+-------------+ +| PATCH | Yes | set | 200 | ++--------------+------------+----------------+-------------+ +| DELETE | Yes | delete | 204 | ++--------------+------------+----------------+-------------+ + +How to restrict access to a controller? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you require that only authenticated users can access you controller, just +add the ``AuthRequired`` decorator to your controller class. + +Example:: + + import cherrypy + from ..tools import ApiController, AuthRequired, RESTController + + + @ApiController('ping2') + @AuthRequired() + class Ping2(RESTController): + def list(self): + return {"msg": "Hello"} + +Now only authenticated users will be able to "ping" your controller. + + +How to access the manager module instance from a controller? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each controller class derived from ``BaseController``has a class property that +points to the manager module global instance. The property is named ``mgr``. +There is another class property called ``logger`` to easily add log messages. + +Example:: + + import cherrypy + from ..tools import ApiController, RESTController + + + @ApiController('servers') + class Servers(RESTController): + def list(self): + self.logger.debug('Listing available servers') + return {'servers': self.mgr.list_servers()} + + +How to write a unit test for a controller? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We provide a test helper class called ``ControllerTestCase`` to easily create +unit tests for your controller. + +If we want to write a unit test for the above ``Ping2`` controller, create a +``test_ping2.py`` file under the ``tests`` directory with the following code:: + + from .helper import ControllerTestCase + from .controllers.ping2 import Ping2 + + + class Ping2Test(ControllerTestCase): + @classmethod + def setup_test(cls): + Ping2._cp_config['tools.authentica.on'] = False + + def test_ping2(self): + self._get("/api/ping2") + self.assertStatus(200) + self.assertJsonBody({'msg': 'Hello'}) + +The ``ControllerTestCase`` class will call the dashboard module code that loads +the controllers and initializes the CherryPy webserver. Then it will call the +``setup_test()`` class method to execute additional instructions that each test +case needs to add to the test. +In the example above we use the ``setup_test()`` method to disable the +authentication handler for the ``Ping2`` controller. + + +How to listen for manager notifications in a controller? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The manager notifies the modules of several types of cluster events, such +as cluster logging event, etc... + +Each module has a "global" handler function called ``notify`` that the manager +calls to notify the module. But this handler function must not block or spend +too much time processing the event notification. +For this reason we provide a notification queue that controllers can register +themselves with to receive cluster notifications. + +The example below represents a controller that implements a very simple live +log viewer page:: + + from __future__ import absolute_import + + import collections + + import cherrypy + + from ..tools import ApiController, BaseController, NotificationQueue + + + @ApiController('livelog') + class LiveLog(BaseController): + log_buffer = collections.deque(maxlen=1000) + + def __init__(self): + super(LiveLog, self).__init__() + NotificationQueue.register(self.log, 'clog') + + def log(self, log_struct): + self.log_buffer.appendleft(log_struct) + + @cherrypy.expose + def default(self): + ret = '' + for l in self.log_buffer: + ret += "{}
".format(l) + ret += "" + return ret + +As you can see above, the ``NotificationQueue`` class provides a register +method that receives the function as its first argument, and receives the +"notification type" as the second argument. +You can omit the second argument of the ``register`` method, and in that case +you are registering to listen all notifications of any type. + +Here is an list of notification types (these might change in the future) that +can be used: + +* ``clog``: cluster log notifications +* ``command``: notification when a command issued by ``MgrModule.send_command`` + completes +* ``perf_schema_update``: perf counters schema update +* ``mon_map``: monitor map update +* ``fs_map``: cephfs map update +* ``osd_map``: OSD map update +* ``service_map``: services (RGW, RBD-Mirror, etc.) map update +* ``mon_status``: monitor status regular update +* ``health``: health status regular update +* ``pg_summary``: regular update of PG status information + + +How to write a unit test when a controller accesses a Ceph module? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Consider the following example that implements a controller that retrieves the +list of RBD images of the ``rbd`` pool:: + + import rbd + from ..tools import ApiController, RESTController + + + @ApiController('rbdimages') + class RbdImages(RESTController): + def __init__(self): + self.ioctx = self.mgr.rados.open_ioctx('rbd') + self.rbd = rbd.RBD() + + def list(self): + return [{'name': n} for n in self.rbd.list(self.ioctx)] + +In the example above, we want to mock the return value of the ``rbd.list`` +function, so that we can test the JSON response of the controller. + +The unit test code will look like the following:: + + import mock + from .helper import ControllerTestCase + + + class RbdImagesTest(ControllerTestCase): + @mock.patch('rbd.RBD.list') + def test_list(self, rbd_list_mock): + rbd_list_mock.return_value = ['img1', 'img2'] + self._get('/api/rbdimages') + self.assertJsonBody([{'name': 'img1'}, {'name': 'img2'}]) diff --git a/src/pybind/mgr/dashboard_v2/README.rst b/src/pybind/mgr/dashboard_v2/README.rst index 713dbc9cbe0d6..6ee5984892aec 100644 --- a/src/pybind/mgr/dashboard_v2/README.rst +++ b/src/pybind/mgr/dashboard_v2/README.rst @@ -4,8 +4,9 @@ Dashboard and Administration Module for Ceph Manager (aka "Dashboard v2") Overview -------- -The original Ceph Manager Dashboard started out as a simple read-only view into -various run-time information and performance data of a Ceph cluster. +The original Ceph Manager Dashboard that was shipped with Ceph "Luminous" +started out as a simple read-only view into various run-time information and +performance data of a Ceph cluster. However, there is a `growing demand `_ for adding more web-based management capabilities, to make it easier for @@ -38,26 +39,27 @@ JIRA instance `_. Enabling and Starting the Dashboard ----------------------------------- -The Python backend code of this module requires a number of Python modules to be -installed. They are listed in file ``requirements.txt``. Using `pip -`_ you may install all required dependencies -by issuing ``pip -r requirements.txt``. +If you have installed Ceph from distribution packages, the package management +system should have taken care of installing all the required dependencies. -If you're using the `ceph-dev-docker development environment -`_, simply run -``./install_deps.sh`` from the current directory to install them. +If you want to start the dashboard from within a development environment, you +need to have built Ceph (see the toplevel ``README.md`` file and the `developer +documentation `_ for details on how to +accomplish this. -Start the Dashboard module by running:: +Finally, you need to build the dashboard frontend code. See the file +``HACKING.rst`` in this directory for instructions on setting up the necessary +development environment. + +From within a running Ceph cluster, you can start the Dashboard module by +running the following command:: $ ceph mgr module enable dashboard_v2 -You can see currently enabled modules with:: +You can see currently enabled Manager modules with:: $ ceph mgr module ls -Currently you will need to manually generate the frontend code. -Instructions can be found in `./frontend/README.md`. - In order to be able to log in, you need to define a username and password, which will be stored in the MON's configuration database:: @@ -65,300 +67,11 @@ will be stored in the MON's configuration database:: The password will be stored as a hash using ``bcrypt``. -The WebUI should then be reachable on TCP port 8080. - -Unit Testing and Linting ------------------------- - -We included a ``tox`` configuration file that will run the unit tests under -Python 2 and 3, as well as linting tools to guarantee the uniformity of code. - -You need to install ``tox`` before running it. To install ``tox`` in your -system, either install it via your operating system's package management -tools, e.g. by running ``dnf install python3-tox`` on Fedora Linux. - -Alternatively, you can use Python's native package installation method:: - - $ pip install tox - -To run tox, run the following command in the root directory (where ``tox.ini`` -is located):: - - $ tox - - -If you just want to run a single tox environment, for instance only run the -linting tools:: - - $ tox -e lint - -Developer Notes ---------------- - -How to add a new controller? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you want to add a new endpoint to the backend, you just need to add a -class derived from ``BaseController`` decorated with ``ApiController`` in a -Python file located under the ``controllers`` directory. The Dashboard module -will automatically load your new controller upon start. - -For example create a file ``ping2.py`` under ``controllers`` directory with the -following code:: - - import cherrypy - from ..tools import ApiController, BaseController - - @ApiController('ping2') - class Ping2(BaseController): - @cherrypy.expose - def default(self, *args): - return "Hello" - -Every path given in the ``ApiController`` decorator will automatically be -prefixed with ``api``. After reloading the Dashboard module you can access the -above mentioned controller by pointing your browser to -http://mgr_hostname:8080/api/ping2. - -It is also possible to have nested controllers. The ``RgwController`` uses -this technique to make the daemons available through the URL -http://mgr_hostname:8080/api/rgw/daemon:: - - @ApiController('rgw') - @AuthRequired() - class Rgw(RESTController): - pass - - - @ApiController('rgw/daemon') - @AuthRequired() - class RgwDaemon(RESTController): - - def list(self): - pass - - -Note that paths must be unique and that a path like ``rgw/daemon`` has to have -a parent ``rgw``. Otherwise it won't work. - -How does the RESTController work? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -We also provide a simple mechanism to create REST based controllers using the -``RESTController`` class. Any class which inherits from ``RESTController`` -will, by default, return JSON. - -The ``RESTController`` is basically an additional abstraction layer which eases -and unifies the work with collections. A collection is just an array of -objects with a specific type. ``RestController`` enable some default mappings -of request type and given parameters to specific method names. This may sound -complicated at first, but it's fairly easy. Lets have look at the following -example:: - - import cherrypy - from ..tools import ApiController, RESTController - - @ApiController('ping2') - class Ping2(RESTController): - def list(self): - return {"msg": "Hello"} - - def get(self, id): - return self.objects[id] - -In this case, the ``list`` method is automatically used for all requests to -``api/ping2`` where no additional argument is given and where the request type -is ``GET``. If the request is given an additional argument, the ID in our -case, it won't map to ``list`` anymore but to ``get`` and return the element -with the given ID (assuming that ``self.objects`` has been filled before). The -same applies to other request types: - -+--------------+------------+----------------+-------------+ -| Request type | Arguments | Method | Status Code | -+==============+============+================+=============+ -| GET | No | list | 200 | -+--------------+------------+----------------+-------------+ -| PUT | No | bulk_set | 200 | -+--------------+------------+----------------+-------------+ -| PATCH | No | bulk_set | 200 | -+--------------+------------+----------------+-------------+ -| POST | No | create | 201 | -+--------------+------------+----------------+-------------+ -| DELETE | No | bulk_delete | 204 | -+--------------+------------+----------------+-------------+ -| GET | Yes | get | 200 | -+--------------+------------+----------------+-------------+ -| PUT | Yes | set | 200 | -+--------------+------------+----------------+-------------+ -| PATCH | Yes | set | 200 | -+--------------+------------+----------------+-------------+ -| DELETE | Yes | delete | 204 | -+--------------+------------+----------------+-------------+ - -How to restrict access to a controller? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you require that only authenticated users can access you controller, just -add the ``AuthRequired`` decorator to your controller class. - -Example:: - - import cherrypy - from ..tools import ApiController, AuthRequired, RESTController - - @ApiController('ping2') - @AuthRequired() - class Ping2(RESTController): - def list(self): - return {"msg": "Hello"} - -Now only authenticated users will be able to "ping" your controller. - - -How to access the manager module instance from a controller? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Each controller class derived from ``BaseController``has a class property that -points to the manager module global instance. The property is named ``mgr``. -There is another class property called ``logger`` to easily add log messages. - -Example:: - - import cherrypy - from ..tools import ApiController, RESTController - - @ApiController('servers') - class Servers(RESTController): - def list(self): - self.logger.debug('Listing available servers') - return {'servers': self.mgr.list_servers()} - - -How to write a unit test for a controller? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -We provide a test helper class called ``ControllerTestCase`` to easily create -unit tests for your controller. - -If we want to write a unit test for the above ``Ping2`` controller, create a -``test_ping2.py`` file under the ``tests`` directory with the following code:: - - from .helper import ControllerTestCase - from .controllers.ping2 import Ping2 - - class Ping2Test(ControllerTestCase): - @classmethod - def setup_test(cls): - Ping2._cp_config['tools.authentica.on'] = False - - def test_ping2(self): - self._get("/api/ping2") - self.assertStatus(200) - self.assertJsonBody({'msg': 'Hello'}) - -The ``ControllerTestCase`` class will call the dashboard module code that loads -the controllers and initializes the CherryPy webserver. Then it will call the -``setup_test()`` class method to execute additional instructions that each test -case needs to add to the test. -In the example above we use the ``setup_test()`` method to disable the -authentication handler for the ``Ping2`` controller. - - -How to listen for manager notifications in a controller? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The manager notifies the modules of several types of cluster events, such -as cluster logging event, etc... - -Each module has a "global" handler function called ``notify`` that the manager -calls to notify the module. But this handler function must not block or spend -too much time processing the event notification. -For this reason we provide a notification queue that controllers can register -themselves with to receive cluster notifications. - -The example below represents a controller that implements a very simple live -log viewer page:: - - from __future__ import absolute_import - - import collections - - import cherrypy - - from ..tools import ApiController, BaseController, NotificationQueue - - - @ApiController('livelog') - class LiveLog(BaseController): - log_buffer = collections.deque(maxlen=1000) - - def __init__(self): - super(LiveLog, self).__init__() - NotificationQueue.register(self.log, 'clog') - - def log(self, log_struct): - self.log_buffer.appendleft(log_struct) - - @cherrypy.expose - def default(self): - ret = '' - for l in self.log_buffer: - ret += "{}
".format(l) - ret += "" - return ret - -As you can see above, the ``NotificationQueue`` class provides a register -method that receives the function as its first argument, and receives the -"notification type" as the second argument. -You can omit the second argument of the ``register`` method, and in that case -you are registering to listen all notifications of any type. - -Here is an list of notification types (these might change in the future) that -can be used: - -* ``clog``: cluster log notifications -* ``command``: notification when a command issued by ``MgrModule.send_command`` - completes -* ``perf_schema_update``: perf counters schema update -* ``mon_map``: monitor map update -* ``fs_map``: cephfs map update -* ``osd_map``: OSD map update -* ``service_map``: services (RGW, RBD-Mirror, etc.) map update -* ``mon_status``: monitor status regular update -* ``health``: health status regular update -* ``pg_summary``: regular update of PG status information - - -How to write a unit test when a controller accesses a Ceph module? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Consider the following example that implements a controller that retrieves the -list of RBD images of the ``rbd`` pool:: - - import rbd - from ..tools import ApiController, RESTController - - @ApiController('rbdimages') - class RbdImages(RESTController): - def __init__(self): - self.ioctx = self.mgr.rados.open_ioctx('rbd') - self.rbd = rbd.RBD() - - def list(self): - return [{'name': n} for n in self.rbd.list(self.ioctx)] - -In the example above, we want to mock the return value of the ``rbd.list`` -function, so that we can test the JSON response of the controller. - -The unit test code will look like the following:: - - import mock - from .helper import ControllerTestCase +The Dashboard's WebUI should then be reachable on TCP port 8080. - class RbdImagesTest(ControllerTestCase): - @mock.patch('rbd.RBD.list') - def test_list(self, rbd_list_mock): - rbd_list_mock.return_value = ['img1', 'img2'] - self._get('/api/rbdimages') - self.assertJsonBody([{'name': 'img1'}, {'name': 'img2'}]) +Working on the Dashboard Code +----------------------------- +If you're interested in helping with the development of the dashboard, please +see the file ``HACKING.rst`` for details on how to set up a development +environment and some other development-related topics. diff --git a/src/pybind/mgr/dashboard_v2/frontend/README.md b/src/pybind/mgr/dashboard_v2/frontend/README.md deleted file mode 100644 index 75554d5f2871d..0000000000000 --- a/src/pybind/mgr/dashboard_v2/frontend/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# Ceph Dashboard - -This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.6.3. - -## Installation - -Run `npm install` to install the required packages locally. - -**Note** - -If you do not have installed [Angular CLI](https://github.com/angular/angular-cli) globally, then you need to execute ``ng`` commands with an additional ``npm run`` before it. - -## Development server - -Create the `proxy.conf.json` file based on `proxy.conf.json.sample`. - -Run `npm start -- --proxy-config proxy.conf.json` for a dev server. -Navigate to `http://localhost:4200/`. -The app will automatically reload if you change any of the source files. - -## Code scaffolding - -Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - -## Build - -Run `npm run build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. Navigate to `http://localhost:8080`. - -## Running unit tests - -Run `npm run test` to execute the unit tests via [Karma](https://karma-runner.github.io). - -## Running end-to-end tests - -Run `npm run e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). - -## Further help - -To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). - -## Examples of generator - -``` -# Create module 'Core' -src/app> ng generate module core -m=app --routing - -# Create module 'Auth' under module 'Core' -src/app/core> ng generate module auth -m=core --routing -or, alternatively: -src/app> ng generate module core/auth -m=core --routing - -# Create component 'Login' under module 'Auth' -src/app/core/auth> ng generate component login -m=core/auth -or, alternatively: -src/app> ng generate component core/auth/login -m=core/auth -``` - -## Recommended style guide - -### Typescript - -Group the imports based on its source and separate them with a blank line. - -The source groups can be either from angular, external or internal. - -Example: -```javascript -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; - -import { ToastsManager } from 'ng2-toastr'; - -import { Credentials } from '../../../shared/models/credentials.model'; -import { HostService } from './services/host.service'; -``` \ No newline at end of file -- 2.39.5