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 <lgrimmer@suse.com>
This commit is contained in:
Lenz Grimmer 2018-02-15 12:28:41 +01:00 committed by Ricardo Dias
parent 8bfdb073ac
commit efd339cdde
No known key found for this signature in database
GPG Key ID: 74390C579BD37B68
3 changed files with 486 additions and 384 deletions

View File

@ -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 <https://nodejs.org/>`_ and requires the
`Node Package Manager <https://www.npmjs.com/>`_ ``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 <https://github.com/angular/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
<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>`__.
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
<https://pypi.python.org/pypi/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
<https://github.com/ricardoasmarques/ceph-dev-docker/>`_, 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 = '<html><meta http-equiv="refresh" content="2" /><body>'
for l in self.log_buffer:
ret += "{}<br>".format(l)
ret += "</body></html>"
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'}])

View File

@ -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 <http://pad.ceph.com/p/mimic-dashboard>`_
for adding more web-based management capabilities, to make it easier for
@ -38,26 +39,27 @@ JIRA instance <https://tracker.openattic.org/browse/OP-3039>`_.
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
<https://pypi.python.org/pypi/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
<https://github.com/ricardoasmarques/ceph-dev-docker/>`_, 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 <http://docs.ceph.com/docs/master/dev/>`_ 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.
The Dashboard's 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 = '<html><meta http-equiv="refresh" content="2" /><body>'
for l in self.log_buffer:
ret += "{}<br>".format(l)
ret += "</body></html>"
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'}])
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.

View File

@ -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';
```