Add documentation
This patchs adds the first revision of the documentation. There are still parts that are missing in sections like the internals and the connection sections.
This commit is contained in:
parent
d15a6d7063
commit
b49f5d8918
106
CONTRIBUTING.rst
106
CONTRIBUTING.rst
|
@ -20,6 +20,8 @@ Report bugs at https://github.com/akrog/cinderlib/issues.
|
|||
If you are reporting a bug, please include:
|
||||
|
||||
* Your operating system name and version.
|
||||
* Storage backend and configuration used (replacing sensitive information with
|
||||
asterisks).
|
||||
* Any details about your local setup that might be helpful in troubleshooting.
|
||||
* Detailed steps to reproduce the bug.
|
||||
|
||||
|
@ -32,8 +34,16 @@ and "help wanted" is open to whoever wants to implement it.
|
|||
Implement Features
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Look through the GitHub issues for features. Anything tagged with "enhancement"
|
||||
and "help wanted" is open to whoever wants to implement it.
|
||||
Look through the GitHub issues and the :doc:`todo` file for features. Anything
|
||||
tagged with "enhancement" and "help wanted" is open to whoever wants to
|
||||
implement it.
|
||||
|
||||
Write tests
|
||||
~~~~~~~~~~~
|
||||
|
||||
We currently lack decent test coverage, so feel free to look into our existing
|
||||
tests to add missing tests, because any test that increases our coverage is
|
||||
more than welcome.
|
||||
|
||||
Write Documentation
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -62,35 +72,89 @@ Ready to contribute? Here's how to set up `cinderlib` for local development.
|
|||
1. Fork the `cinderlib` repo on GitHub.
|
||||
2. Clone your fork locally::
|
||||
|
||||
$ git clone git@github.com:your_name_here/cinderlib.git
|
||||
$ git clone git@github.com:YOUR_NAME_HERE/cinderlib.git
|
||||
|
||||
3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development::
|
||||
3. Install tox::
|
||||
|
||||
$ mkvirtualenv cinderlib
|
||||
$ cd cinderlib/
|
||||
$ python setup.py develop
|
||||
$ sudo dnf install python2-tox
|
||||
|
||||
4. Create a branch for local development::
|
||||
4. Generate a virtual environment, for example for Python 2.7::
|
||||
|
||||
$ tox --notest -epy27
|
||||
|
||||
5. Create a branch for local development::
|
||||
|
||||
$ git checkout -b name-of-your-bugfix-or-feature
|
||||
|
||||
Now you can make your changes locally.
|
||||
|
||||
5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox::
|
||||
6. When you're done making changes, you can check that your changes pass flake8
|
||||
and the tests with::
|
||||
|
||||
$ tox -eflake8
|
||||
$ tox -epy27
|
||||
|
||||
Or if you don't want to create a specific environment for flake8 you can run
|
||||
things directly without tox::
|
||||
|
||||
$ source .tox/py27/bin/activate
|
||||
$ flake8 cinderlib tests
|
||||
$ python setup.py test or py.test
|
||||
$ tox
|
||||
$ python setup.py test
|
||||
|
||||
To get flake8 and tox, just pip install them into your virtualenv.
|
||||
|
||||
6. Commit your changes and push your branch to GitHub::
|
||||
7. Commit your changes making sure the commit message is descriptive enough,
|
||||
covering the patch changes as well as why the patch might be necessary. The
|
||||
commit message should also conform to the `50/72 rule
|
||||
<https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html>`_.
|
||||
|
||||
$ git add .
|
||||
$ git commit -m "Your detailed description of your changes."
|
||||
$ git commit
|
||||
|
||||
|
||||
8. Push your branch to GitHub::
|
||||
|
||||
$ git push origin name-of-your-bugfix-or-feature
|
||||
|
||||
7. Submit a pull request through the GitHub website.
|
||||
9. Submit a pull request through the GitHub website.
|
||||
|
||||
LVM Backend
|
||||
-----------
|
||||
|
||||
You may not have a fancy storage array, but that doesn't mean that you cannot
|
||||
use *cinderlib*, because you can just use NFS or LVM. Here we are going to see
|
||||
how to setup an LVM backend that we can use with *cinderlib*.
|
||||
|
||||
First you create your LVM, which is a 22GB file that is currently only using
|
||||
1MB, and then you mount it as a loopback device and create a PV and VG on the
|
||||
loopback device.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
$ dd if=/dev/zero of=cinder-volumes bs=1048576 seek=22527 count=1
|
||||
$ sudo lodevice=`losetup -f`
|
||||
$ sudo losetup $lodevice ./cinder-volumes
|
||||
$ sudo pvcreate $lodevice
|
||||
$ sudo vgcreate cinder-volumes $lodevice
|
||||
$ sudo vgscan --cache
|
||||
|
||||
Now you can use this LVM backend in *cinderlib*:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cinderlib
|
||||
|
||||
lvm = cl.Backend(volume_driver='cinder.volume.drivers.lvm.LVMVolumeDriver',
|
||||
volume_group='cinder-volumes',
|
||||
iscsi_protocol='iscsi',
|
||||
iscsi_helper='lioadm',
|
||||
volume_backend_name='lvm_iscsi')
|
||||
|
||||
vol = lvm.volume_create(size=1)
|
||||
|
||||
attach = vol.attach()
|
||||
pp('Volume %s attached to %s' % (vol.id, attach.path))
|
||||
vol.detach()
|
||||
|
||||
vol.delete()
|
||||
|
||||
Pull Request Guidelines
|
||||
-----------------------
|
||||
|
@ -101,14 +165,14 @@ Before you submit a pull request, check that it meets these guidelines:
|
|||
2. If the pull request adds functionality, the docs should be updated. Put
|
||||
your new functionality into a function with a docstring, and add the
|
||||
feature to the list in README.rst.
|
||||
3. The pull request should work for Python 2.7, 3.3, 3.4 and 3.5, and for PyPy. Check
|
||||
https://travis-ci.org/akrog/cinderlib/pull_requests
|
||||
and make sure that the tests pass for all supported Python versions.
|
||||
3. The pull request should work for Python 2.7, 3.3, 3.4 and 3.5, and for PyPy.
|
||||
Check https://travis-ci.org/akrog/cinderlib/pull_requests and make sure that
|
||||
the tests pass for all supported Python versions.
|
||||
|
||||
Tips
|
||||
----
|
||||
|
||||
To run a subset of tests::
|
||||
|
||||
|
||||
$ python -m unittest tests.test_cinderlib
|
||||
$ source .tox/py27/bin/activate
|
||||
$ python -m unittest tests.test_cinderlib.TestCinderlib.test_lib_setup
|
||||
|
|
65
README.rst
65
README.rst
|
@ -1,8 +1,6 @@
|
|||
Cinder Library
|
||||
===============================
|
||||
|
||||
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/cinderlib.svg
|
||||
:target: https://pypi.python.org/pypi/cinderlib
|
||||
|
||||
|
@ -26,43 +24,13 @@ Cinder.
|
|||
* Free software: Apache Software License 2.0
|
||||
* Documentation: https://cinderlib.readthedocs.io.
|
||||
|
||||
This library is currently at an Alpha status and is primarily intended as a
|
||||
proof of concept at this stage. While some drivers have been manually
|
||||
validated most drivers have not, so there's a good chance that they could
|
||||
experience issues.
|
||||
This library is currently in Alpha stage and is primarily intended as a proof
|
||||
of concept at this stage. While some drivers have been manually validated most
|
||||
drivers have not, so there's a good chance that they could experience issues.
|
||||
|
||||
When using this library one should be aware that this is in no way close to the
|
||||
robustness or feature richness that the Cinder project provides. Some of the
|
||||
more obvious limitations are:
|
||||
|
||||
* There are no argument validation on the methods so it's a classic GIGO_
|
||||
library.
|
||||
* The logic has been kept to a minimum and higher functioning logic is expected
|
||||
to be in the caller. For example you can delete a volume that still has
|
||||
snapshots, and the end results will depend on the Cinder driver and the
|
||||
storage array, so you will have some that will delete the snapshots and
|
||||
others that will leave them there.
|
||||
* There is no CI, or unit tests for that matter, and certainly nothing so fancy
|
||||
as third party vendor CIs, so things being broken could be considered the
|
||||
norm.
|
||||
* Only a subset number of basic operations are supported by the library.
|
||||
|
||||
The minimum version of Cinder required by this library is Pike; although,
|
||||
depending on my my availability, I may make the library support Ocata as well.
|
||||
|
||||
Since it's using Cinder's code the library is still bound by the same
|
||||
restrictions and behaviors of the drivers running under the standard Cinder
|
||||
services, which means that not all operations will behave consistently across
|
||||
drivers. For example you can find drivers where cloning is a cheap operation
|
||||
performed by the storage array whereas other will actually create a new volume,
|
||||
attach the source and the new volume and perform a full copy of the data.
|
||||
|
||||
If a driver in Cinder requires external libraries or packages they will also
|
||||
be required by the library and will need to be manually installed.
|
||||
|
||||
For more detailed information please refer to the `official project
|
||||
documentation`_ and `OpenStack's Cinder volume driver configuration
|
||||
documentation`_.
|
||||
robustness or feature richness that the Cinder project provides, for detailed
|
||||
information on the current limitations please refer to the documentation.
|
||||
|
||||
Due to the limited access to Cinder backends and time constraints the list of
|
||||
drivers that have been manually tested are (I'll try to test more):
|
||||
|
@ -95,6 +63,17 @@ Features
|
|||
- Local attach
|
||||
- Local detach
|
||||
- Validate connector
|
||||
* Code should support multiple concurrent connections to a volume, though this
|
||||
has not yet been tested.
|
||||
|
||||
Demo
|
||||
----
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<a href="https://asciinema.org/a/TcTR7Lu7jI0pEsd9ThEn01l7n?autoplay=1"
|
||||
target="_blank"><img
|
||||
src="https://asciinema.org/a/TcTR7Lu7jI0pEsd9ThEn01l7n.png"/></a>
|
||||
|
||||
Example
|
||||
-------
|
||||
|
@ -157,7 +136,9 @@ control LVM and do the attach) and execute:
|
|||
exit()
|
||||
|
||||
Now we can check that the logical volume is there, exported, and attached to
|
||||
our system::
|
||||
our system:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
# lvdisplay
|
||||
# targetcli ls
|
||||
|
@ -185,7 +166,15 @@ And now let's run a new `python` interpreter and clean things up:
|
|||
# Finally delete the volume
|
||||
vol.delete()
|
||||
|
||||
We should confirm that the logical volume is no longer there, there's nothing
|
||||
exported or attached to our system:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
# lvdisplay
|
||||
# targetcli ls
|
||||
# iscsiadm -m session
|
||||
# lsblk
|
||||
|
||||
.. _GIGO: https://en.wikipedia.org/wiki/Garbage_in,_garbage_out
|
||||
.. _official project documentation: https://readthedocs.org/projects/cinderlib/badge/?version=latest
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
====
|
||||
TODO
|
||||
====
|
||||
|
||||
There are many things that need improvements in *cinderlib*, this is a simple
|
||||
list to keep track of the most relevant topics.
|
||||
|
||||
- Connect & attach snapshot for drivers that support it.
|
||||
- Replication and failover support
|
||||
- QoS
|
||||
- Support custom features via extra specs
|
||||
- Unit tests
|
||||
- Integration tests
|
||||
- Parameter validation
|
||||
- Support using *cinderlib* without cinder to just handle the attach/detach
|
||||
- Add .py examples
|
||||
- Support other Cinder releases besides Pike
|
||||
- Add support for new Attach/Detach mechanism
|
||||
- Consistency Groups
|
||||
- Encryption
|
||||
- Support name and description attributes in Volume and Snapshot
|
||||
- Verify multiattach support
|
||||
- Use created_at, updated_at, and deleted_at fields
|
||||
- Use a list instead of a set in Volume.snapshots so they are ordered, which
|
||||
can be useful to restore to the latest snapshot as well as to delete them in
|
||||
reverse order of creation.
|
||||
- Revert to snapshot support.
|
||||
- Add documentation to connect remote host. `use_multipath_for_image_xfer` and
|
||||
the `enforce_multipath_for_image_xfer` options.
|
||||
- Complete internals documentation.
|
||||
- Document the code.
|
||||
- Should *cinderlib* support working with sqlite instead of just RAM?
|
||||
- Improve serialization to limit following of references to other objects.
|
|
@ -1,16 +1,35 @@
|
|||
Welcome to Cinder Library's documentation!
|
||||
======================================
|
||||
==========================================
|
||||
|
||||
Contents:
|
||||
.. image:: https://img.shields.io/pypi/v/cinderlib.svg
|
||||
:target: https://pypi.python.org/pypi/cinderlib
|
||||
|
||||
.. image:: https://readthedocs.org/projects/cinderlib/badge/?version=latest
|
||||
:target: https://cinderlib.readthedocs.io/en/latest/?badge=latest
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/cinderlib.svg
|
||||
:target: https://pypi.python.org/pypi/cinderlib
|
||||
|
||||
.. image:: https://img.shields.io/:license-apache-blue.svg
|
||||
:target: http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Cinder Library is a Python library that allows using Cinder storage drivers not
|
||||
only outside of OpenStack but also outside of Cinder, which means there's no
|
||||
need to run MySQL, RabbitMQ, Cinder API, Scheduler, or Volume services to be
|
||||
able to manage your storage.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
introduction
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
authorshistory
|
||||
internals
|
||||
authors
|
||||
todo
|
||||
history
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
@ -18,3 +37,5 @@ Indices and tables
|
|||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
.. _GIGO: https://en.wikipedia.org/wiki/Garbage_in,_garbage_out
|
||||
|
|
|
@ -8,13 +8,53 @@ Installation
|
|||
Stable release
|
||||
--------------
|
||||
|
||||
To install Cinder Library, run this command in your terminal:
|
||||
The Cinder Library is an interfacing library that doesn't have any storage
|
||||
driver and expects Cinder drivers to be properly installed in the system to run
|
||||
properly.
|
||||
|
||||
Drivers
|
||||
_______
|
||||
|
||||
For Red Hat distributions the recommendation is to use RPMs to install the
|
||||
Cinder drivers instead of using `pip`. If we don't have access to the
|
||||
`Red Hat OpenStack Platform packages
|
||||
<https://www.redhat.com/en/technologies/linux-platforms/openstack-platform>`_
|
||||
we can use the `RDO community packages <https://www.rdoproject.org/>`_.
|
||||
|
||||
On CentOS, the Extras repository provides the RPM that enables the OpenStack
|
||||
repository. Extras is enabled by default on CentOS 7, so you can simply install
|
||||
the RPM to set up the OpenStack repository:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install cinderlib
|
||||
# yum install -y centos-release-openstack-pike
|
||||
# yum-config-manager --enable openstack-pike
|
||||
# yum update -y
|
||||
# yum install -y openstack-cinder
|
||||
|
||||
This is the preferred method to install Cinder Library, as it will always install the most recent stable release.
|
||||
On RHEL and Fedora, you'll need to download and install the RDO repository RPM
|
||||
to set up the OpenStack repository:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# yum install -y https://repos.fedorapeople.org/repos/openstack/openstack-pike/rdo-release-pike-1.noarch.rpm
|
||||
# yum-config-manager --enable openstack-pike
|
||||
# sudo yum update -y
|
||||
# yum install -y openstack-cinder
|
||||
|
||||
Library
|
||||
_______
|
||||
|
||||
To install Cinder Library we'll use PyPI, so we'll make sure to have the `pip`_
|
||||
command available:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# yum install -y python-pip
|
||||
# pip install cinderlib
|
||||
|
||||
This is the preferred method to install Cinder Library, as it will always
|
||||
install the most recent stable release.
|
||||
|
||||
If you don't have `pip`_ installed, this `Python installation guide`_ can guide
|
||||
you through the process.
|
||||
|
@ -22,11 +62,26 @@ you through the process.
|
|||
.. _pip: https://pip.pypa.io
|
||||
.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/
|
||||
|
||||
From source
|
||||
-----------
|
||||
|
||||
From sources
|
||||
------------
|
||||
Drivers
|
||||
_______
|
||||
|
||||
The sources for Cinder Library can be downloaded from the `Github repo`_.
|
||||
If we don't have a packaged version or if we want to use a virtual environment
|
||||
we can install the drivers from source:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ virtualenv cinder
|
||||
$ source cinder/bin/activate
|
||||
$ pip install git+https://github.com/openstack/cinder.git@stable/pike
|
||||
|
||||
Library
|
||||
_______
|
||||
|
||||
The sources for Cinder Library can be downloaded from the `Github repo`_ to use
|
||||
the latest version of the library.
|
||||
|
||||
You can either clone the public repository:
|
||||
|
||||
|
@ -44,7 +99,7 @@ Once you have a copy of the source, you can install it with:
|
|||
|
||||
.. code-block:: console
|
||||
|
||||
$ python setup.py install
|
||||
# python setup.py install
|
||||
|
||||
|
||||
.. _Github repo: https://github.com/akrog/cinderlib
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
=========
|
||||
Internals
|
||||
=========
|
||||
|
||||
Here we'll go over some of the implementation details within *cinderlib* as
|
||||
well as explanations of how we've resolved the different issues that arise from
|
||||
accessing the driver's directly from outside of the cinder-volume service.
|
||||
|
||||
Some of the issues *cinderlib* has had to resolve are:
|
||||
|
||||
- *Oslo config* configuration loading.
|
||||
- Cinder-volume dynamic configuration loading.
|
||||
- Privileged helper service.
|
||||
- DLM configuration.
|
||||
- Disabling of cinder logging.
|
||||
- Direct DB access within drivers.
|
||||
- *Oslo Versioned Objects* DB access methods such as `refresh` and `save`.
|
||||
- Circular references in *Oslo Versioned Objects* for serialization.
|
||||
- Using multiple drivers in the same process.
|
|
@ -0,0 +1,109 @@
|
|||
Cinder Library
|
||||
==============
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/cinderlib.svg
|
||||
:target: https://pypi.python.org/pypi/cinderlib
|
||||
|
||||
.. image:: https://readthedocs.org/projects/cinderlib/badge/?version=latest
|
||||
:target: https://cinderlib.readthedocs.io/en/latest/?badge=latest
|
||||
:alt: Documentation Status
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/cinderlib.svg
|
||||
:target: https://pypi.python.org/pypi/cinderlib
|
||||
|
||||
.. image:: https://img.shields.io/:license-apache-blue.svg
|
||||
:target: http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Cinder Library is a Python library that allows using storage drivers provided
|
||||
by Cinder outside of OpenStack and without needing to run the Cinder service,
|
||||
so we don't need Keystone, MySQL, or RabbitMQ services to control our storage.
|
||||
|
||||
The library is currently in an early development stage and can be considered as
|
||||
a proof of concept and not a finished product at this moment, so please
|
||||
carefully go over the limitations section to avoid surprises.
|
||||
|
||||
Due to the limited access to Cinder backends and time constraints the list of
|
||||
drivers that have been manually tested are:
|
||||
|
||||
- LVM
|
||||
- XtremIO
|
||||
- Kaminario
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Use a Cinder driver without running a DBMS, Message broker, or Cinder
|
||||
services.
|
||||
* Using multiple simultaneous drivers on the same program.
|
||||
* Stateless: Support full serialization of objects and context to JSON or
|
||||
string so the state can be restored.
|
||||
* Basic operations support:
|
||||
|
||||
- Create volume
|
||||
- Delete volume
|
||||
- Extend volume
|
||||
- Clone volume
|
||||
- Create snapshot
|
||||
- Delete snapshot
|
||||
- Create volume from snapshot
|
||||
- Connect volume
|
||||
- Disconnect volume
|
||||
- Local attach
|
||||
- Local detach
|
||||
- Validate connector
|
||||
|
||||
Demo
|
||||
----
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script type="text/javascript" src="https://asciinema.org/a/TcTR7Lu7jI0pEsd9ThEn01l7n.js"
|
||||
id="asciicast-TcTR7Lu7jI0pEsd9ThEn01l7n" async data-autoplay="false"
|
||||
data-loop="false"></script>
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
Being in its early development stages the library is in no way close to the
|
||||
robustness or feature richness that the Cinder project provides. Some of the
|
||||
more noticeable limitations one should be aware of are:
|
||||
|
||||
- Most methods don't perform argument validation so it's a classic GIGO_
|
||||
library.
|
||||
|
||||
- The logic has been kept to a minimum and higher functioning logic is expected
|
||||
to be handled by the caller.
|
||||
|
||||
- There is no CI, or unit tests for that matter, and certainly nothing so fancy
|
||||
as third party vendor CIs, so things could be broken at any point.
|
||||
|
||||
- Only a subset of Cinder available operations are supported by the library.
|
||||
|
||||
- The only Cinder release that has been tested with the library has been Pike.
|
||||
|
||||
- Access to a small number of storage arrays has limited the number of drivers
|
||||
that have been verified to work with cinderlib.
|
||||
|
||||
Besides *cinderlib's* own limitations the library also inherits some from
|
||||
*Cinder's* code and will be bound by the same restrictions and behaviors of the
|
||||
drivers as if they were running under the standard *Cinder* services. The most
|
||||
notorious ones are:
|
||||
|
||||
- Dependency on the *eventlet* library.
|
||||
|
||||
- Behavior inconsistency on some operations across drivers. For example you
|
||||
can find drivers where cloning is a cheap operation performed by the storage
|
||||
array whereas other will actually create a new volume, attach the source and
|
||||
new volume and perform a full copy of the data.
|
||||
|
||||
- External dependencies must be handled manually. So we'll have to take care of
|
||||
any library, package, or CLI tool that is required by the driver.
|
||||
|
||||
- Relies on command execution via *sudo* for attach/detach operations as well
|
||||
as some CLI tools.
|
||||
|
||||
.. _GIGO: https://en.wikipedia.org/wiki/Garbage_in,_garbage_out
|
|
@ -1 +0,0 @@
|
|||
.. include:: ../README.rst
|
|
@ -0,0 +1 @@
|
|||
.. include:: ../TODO.rst
|
|
@ -0,0 +1,249 @@
|
|||
========
|
||||
Backends
|
||||
========
|
||||
|
||||
The *Backend* class provides the abstraction to access a storage array with an
|
||||
specific configuration, which usually constraints our ability to operate on the
|
||||
backend to a single pool.
|
||||
|
||||
.. note::
|
||||
|
||||
While some drivers have been manually validated most drivers have not, so
|
||||
there's a good chance that using any non tested driver will show unexpected
|
||||
behavior.
|
||||
|
||||
If you are testing *cinderlib* with a non verified backend you should use
|
||||
an exclusive pool for the validation so you don't have to be so careful
|
||||
when creating resources as you know that everything within that pool is
|
||||
related to *cinderlib* and can be deleted using the vendor's management
|
||||
tool.
|
||||
|
||||
If you try the library with another storage array I would appreciate a note
|
||||
on the library version, Cinder release, and results of your testing.
|
||||
|
||||
Initialization
|
||||
--------------
|
||||
|
||||
Before we can have access to an storage array we have to initialize the
|
||||
*Backend*, which only has one parameter defined and all other parameters are
|
||||
not defined in the method prototype:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Backend(object):
|
||||
def __init__(self, volume_backend_name, **driver_cfg):
|
||||
|
||||
There are two arguments that we'll always have to pass on the initialization,
|
||||
one is the `volume_backend_name` that is the unique identifier that *cinderlib*
|
||||
will use to identify this specific driver initialization, so we'll need to make
|
||||
sure not to repeat the name, and the other one is the `volume_driver` which
|
||||
refers to the Python path that points to the *Cinder* driver.
|
||||
|
||||
All other *Backend* configuration options are free-form keyword arguments
|
||||
because each driver and storage array requires different information to
|
||||
operate, some require credentials to be passed as parameters while others use a
|
||||
file, some require the control address as well as the data addresses. This
|
||||
behavior is inherited from the *Cinder* project.
|
||||
|
||||
To find what configuration options are available and which ones are compulsory
|
||||
the best is going to the Vendor's documentation or to the `OpenStack's Cinder
|
||||
volume driver configuration documentation`_.
|
||||
|
||||
.. attention::
|
||||
|
||||
Some drivers have external dependencies which we must satisfy before
|
||||
initializing the driver or it may fail either on the initialization or when
|
||||
running specific operations. For example Kaminario requires the *krest*
|
||||
Python library, and Pure requires *purestorage* Python library.
|
||||
|
||||
Python library dependencies are usually documented in the
|
||||
`driver-requirements.txt file
|
||||
<https://github.com/openstack/cinder/blob/master/driver-requirements.txt>`_,
|
||||
as for the CLI required tools we'll have to check in the Vendor's
|
||||
documentation.
|
||||
|
||||
Cinder only supports using one driver at a time, as each process only handles
|
||||
one backend, but *cinderlib* has overcome this limitation and supports having
|
||||
multiple *Backends* simultaneously.
|
||||
|
||||
Let's see now initialization examples of some storage backends:
|
||||
|
||||
LVM
|
||||
---
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cinderlib
|
||||
|
||||
lvm = cinderlib.Backend(
|
||||
volume_driver='cinder.volume.drivers.lvm.LVMVolumeDriver',
|
||||
volume_group='cinder-volumes',
|
||||
iscsi_protocol='iscsi',
|
||||
iscsi_helper='lioadm',
|
||||
volume_backend_name='lvm_iscsi',
|
||||
)
|
||||
|
||||
XtremIO
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cinderlib
|
||||
|
||||
xtremio = cinderlib.Backend(
|
||||
volume_driver='cinder.volume.drivers.dell_emc.xtremio.XtremIOISCSIDriver',
|
||||
san_ip='10.10.10.1',
|
||||
xtremio_cluster_name='xtremio_cluster',
|
||||
san_login='xtremio_user',
|
||||
san_password='xtremio_password',
|
||||
volume_backend_name='xtremio',
|
||||
)
|
||||
|
||||
Kaminario
|
||||
---------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cinderlib
|
||||
|
||||
kaminario = cl.Backend(
|
||||
volume_driver='cinder.volume.drivers.kaminario.kaminario_iscsi.KaminarioISCSIDriver',
|
||||
san_ip='10.10.10.2',
|
||||
san_login='kaminario_user',
|
||||
san_password='kaminario_password',
|
||||
volume_backend_name='kaminario_iscsi',
|
||||
)
|
||||
|
||||
Available Backends
|
||||
------------------
|
||||
|
||||
Usual procedure is to initialize a *Backend* and store it in a variable at the
|
||||
same time so we can use it to manage our storage backend, but there are cases
|
||||
where we may have lost the reference or we are in a place in our code where we
|
||||
don't have access to the original variable.
|
||||
|
||||
For these situations we can use *cinderlib's* tracking of *Backends* through
|
||||
the `backends` class dictionary where all created *Backends* are stored using
|
||||
the `volume_backend_name` as the key.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
for backend in cinderlib.Backend.backends.values():
|
||||
initialized_msg = '' if backend.initialized else 'not '
|
||||
print('Backend %s is %sinitialized with configuration: %s' %
|
||||
(backend.id, initialized_msg, backend.config))
|
||||
|
||||
Stats
|
||||
-----
|
||||
|
||||
In *Cinder* all cinder-volume services periodically report the stats of their
|
||||
backend to the cinder-scheduler services so they can do informed placing
|
||||
decisions on operations such as volume creation and volume migration.
|
||||
|
||||
Some of the keys provided in the stats dictionary include:
|
||||
|
||||
- `driver_version`
|
||||
- `free_capacity_gb`
|
||||
- `storage_protocol`
|
||||
- `total_capacity_gb`
|
||||
- `vendor_name volume_backend_name`
|
||||
|
||||
Additional information can be found in the `Volume Stats section
|
||||
<https://docs.openstack.org/cinder/pike/contributor/drivers.html#volume-stats>`_
|
||||
within the Developer's Documentation.
|
||||
|
||||
Gathering stats is a costly operation for many storage backends, so we have the
|
||||
possibility of retrieving the cached value that was retrieved the last time.
|
||||
|
||||
Here's an example of the output from the LVM *Backend*:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from pprint import pprint
|
||||
>>> pprint(lvm.stats())
|
||||
{'driver_version': '3.0.0',
|
||||
'pools': [{'QoS_support': False,
|
||||
'filter_function': None,
|
||||
'free_capacity_gb': 20.9,
|
||||
'goodness_function': None,
|
||||
'location_info': 'LVMVolumeDriver:router:cinder-volumes:thin:0',
|
||||
'max_over_subscription_ratio': 20.0,
|
||||
'multiattach': False,
|
||||
'pool_name': 'LVM',
|
||||
'provisioned_capacity_gb': 0.0,
|
||||
'reserved_percentage': 0,
|
||||
'thick_provisioning_support': False,
|
||||
'thin_provisioning_support': True,
|
||||
'total_capacity_gb': '20.90',
|
||||
'total_volumes': 1}],
|
||||
'sparse_copy_volume': True,
|
||||
'storage_protocol': 'iSCSI',
|
||||
'vendor_name': 'Open Source',
|
||||
'volume_backend_name': 'LVM'}
|
||||
|
||||
Available volumes
|
||||
-----------------
|
||||
|
||||
Just like the *Backend* class keeps track of all the *Backend* instances in the
|
||||
`backends` class attribute, each *Backend* instance will keep track of all the
|
||||
volumes that have been created in the *Backend*, regardless of how they have
|
||||
been created, and still exist in the storage backend. So all volumes that have
|
||||
been successfully deleted will no longer be there.
|
||||
|
||||
We can access the *Volumes* with the `volumes` instance attribute of type
|
||||
`set`.
|
||||
|
||||
So assuming that we have an `lvm` variable holding an initialized *Backend*
|
||||
instance where we have created volumes we could list them with:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
for vol in lvm.volumes:
|
||||
print('Volume %s has %s GB' % (vol.id, vol.size))
|
||||
|
||||
.. note::
|
||||
|
||||
The `volumes` attribute variable will only hold the volumes that are known
|
||||
to this *cinderlib*, be it because we have created the volumes in this run
|
||||
or because we have loaded them from a serialized source.
|
||||
|
||||
This should not be confused with a listing of the volumes within the pool
|
||||
we are using.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
|
||||
The *Backend* class has no attributes of interest besides the `backends`
|
||||
mentioned above and the `id`, `config`, and JSON related properties we'll see
|
||||
later in the :doc:`serialization` section.
|
||||
|
||||
The `id` property refers to the `volume_backend_name`, which is also the key
|
||||
used in the `backends` class attribute.
|
||||
|
||||
The `config` property will return a dictionary with only the volume backend's
|
||||
name by default to limit unintended exposure of backend credentials on
|
||||
serialization. If we want it to return all the configuration options we need
|
||||
to pass `output_all_backend_info=True` on *cinderlib* initialization.
|
||||
|
||||
If we try to access any non-existent attribute in the *Backend*, *cinderlib*
|
||||
will understand we are trying to access a *Cinder* driver attribute and will
|
||||
try to retrieve it from the driver's instance. This is the case with the
|
||||
`initialized` property we accessed in the backends listing example.
|
||||
|
||||
|
||||
Other methods
|
||||
-------------
|
||||
|
||||
All other methods available in the *Backend* class will be explained in their
|
||||
relevant sections:
|
||||
|
||||
- `load` and `load_backend` will be explained together with `json` and `jsons`
|
||||
properties in the :doc:`serialization` section.
|
||||
|
||||
- `create_volume` method will be covered in the :doc:`volumes` section.
|
||||
|
||||
- `validate_connector` will be explained in the :doc:`connections` section.
|
||||
|
||||
- `global_setup` has been covered in the :doc:`initialization` section.
|
||||
|
||||
.. _OpenStack's Cinder volume driver configuration documentation: https://docs.openstack.org/cinder/latest/configuration/block-storage/volume-drivers.html
|
|
@ -0,0 +1,129 @@
|
|||
===========
|
||||
Connections
|
||||
===========
|
||||
|
||||
When talking about attaching a *Cinder* volume there are three steps that must
|
||||
happen before the volume is available in the host:
|
||||
|
||||
1. Retrieve connection information from the host where the volume is going to
|
||||
be attached. Here we would be getting iSCSI initiator name and such
|
||||
information.
|
||||
|
||||
2. Use the connection information from step 1 and make the volume accessible to
|
||||
it in the storage backend returning the volume connection information. This
|
||||
step entails exporting the volume and initializing the connection.
|
||||
|
||||
3. Attaching the volume to the host using the data retrieved on step 2.
|
||||
|
||||
If we are running *cinderlib* and doing the attach in the same host then all
|
||||
steps will be done in the same host, but in many cases you may want to manage
|
||||
the storage backend in one host and attach to another, in such cases steps 1
|
||||
and 3 will happen in the host that needs the attach and step 2 on the node
|
||||
running *cinderlib*.
|
||||
|
||||
In *OpenStack* there is a connection library called *OS-Brick* that is used by
|
||||
*Cinder* and *Nova* (the compute component) to perform steps 1 and 3, but in
|
||||
*cinderlib* we are not currently using it directly and instead we are
|
||||
leveraging *Cinder*'s helper methods to do this for us.
|
||||
|
||||
This adds an unnecessary dependency on specific *Cinder* code that will be
|
||||
removed in the future and also limits the usefulness of the library to abstract
|
||||
*OS-Brick* library usage from *cinderlib* users.
|
||||
|
||||
*Connection* objects' most interesting attributes are:
|
||||
|
||||
- `connected`: Boolean that reflects if the connection is complete
|
||||
|
||||
- `volume`: The *Volume* to which this instance holds the connection
|
||||
information.
|
||||
|
||||
- `connector`: Connection information from the host that is attaching. Such as
|
||||
it's hostname, IP address, initiator name, etc.
|
||||
|
||||
- `connection_info`: The connection information the host requires to do the
|
||||
attachment, such as IP address, target name, credentials, etc.
|
||||
|
||||
- `attach_info`: If we have done a local attachment this will hold all the
|
||||
attachment information.
|
||||
|
||||
|
||||
Local attach
|
||||
------------
|
||||
|
||||
Doing a local attachment with *cinderlib* once we have created a volume is
|
||||
really simple, we just have to call the `attach` method from the volume and
|
||||
we'll get the *Connection* information from the attached volume, and once we
|
||||
are done we call the `detach` method on the *Volume* or on the *Connection*
|
||||
information that we got from `attach`.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vol = lvm.create_volume(size=1)
|
||||
attach = vol.attach()
|
||||
with open(attach.path, 'w') as f:
|
||||
f.write('*' * 100)
|
||||
vol.detach()
|
||||
|
||||
As mentioned before we could have called `attach.detach()` instead of
|
||||
`vol.detach()` and it would have had the same effect.
|
||||
|
||||
Remote connection
|
||||
-----------------
|
||||
|
||||
For a remote connection it's a little more inconvenient at the moment, since
|
||||
you'll have to manually use the *OS-Brick* library on the host that is going to
|
||||
do the attachment.
|
||||
|
||||
.. note:: THIS SECTION IS INCOMPLETE
|
||||
|
||||
First we need to get the connection information on the host that is going to do
|
||||
the attach:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_brick
|
||||
|
||||
# Retrieve the connection information dictionary
|
||||
|
||||
Then we have to do the connection
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Create the connection
|
||||
attach = vol.connect(connector_dict)
|
||||
|
||||
# Return the volume connection information
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os_brick
|
||||
|
||||
# Do the attachment
|
||||
|
||||
Multipath
|
||||
---------
|
||||
|
||||
If we want to use multipathing for local attachments we must let the *Backend*
|
||||
know when instantiating the driver by passing the
|
||||
`use-multipath_for_image_xfer=True`:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cinderlib
|
||||
|
||||
lvm = cinderlib.Backend(
|
||||
volume_driver='cinder.volume.drivers.lvm.LVMVolumeDriver',
|
||||
volume_group='cinder-volumes',
|
||||
iscsi_protocol='iscsi',
|
||||
iscsi_helper='lioadm',
|
||||
volume_backend_name='lvm_iscsi',
|
||||
use-multipath_for_image_xfer=True,
|
||||
)
|
||||
|
||||
Multi attach
|
||||
------------
|
||||
|
||||
Multi attach support has just been added to *Cinder* in the Queens cycle, and
|
||||
it's not currently supported by *cinderlib*.
|
|
@ -0,0 +1,116 @@
|
|||
==============
|
||||
Initialization
|
||||
==============
|
||||
|
||||
The cinderlib itself doesn't require an initialization, as it tries to provide
|
||||
sensible settings, but in some cases we may want to modify these defaults to
|
||||
fit a specific desired behavior and the library provides a mechanism to support
|
||||
this.
|
||||
|
||||
Library initialization should be done before making any other library call,
|
||||
including *Backend* initialization and loading serialized data, if we try to
|
||||
do it after other calls the library will raise and `Exception`.
|
||||
|
||||
Provided *setup* method is `cinderlib.Backend.global_setup`, but for
|
||||
convenience the library provides a reference to this class method in
|
||||
`cinderlib.setup`
|
||||
|
||||
The method definition is as follows:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@classmethod
|
||||
def global_setup(cls, file_locks_path=None, disable_sudo=False,
|
||||
suppress_requests_ssl_warnings=True, disable_logs=True,
|
||||
non_uuid_ids=False, output_all_backend_info=False,
|
||||
**log_params):
|
||||
|
||||
|
||||
The meaning of the library's configuration options are:
|
||||
|
||||
file_locks_path
|
||||
---------------
|
||||
|
||||
Cinder is a complex system that can support Active-Active deployments, and each
|
||||
driver and storage backend has different restrictions, so in order to
|
||||
facilitate mutual exclusion it provides 3 different types of locks depending
|
||||
on the scope the driver requires:
|
||||
|
||||
- Between threads of the same process.
|
||||
- Between different process on the same host.
|
||||
- In all the OpenStack deployment.
|
||||
|
||||
Cinderlib doesn't currently support the third type of locks, but that should
|
||||
not be an inconvenience for most cinderlib usage.
|
||||
|
||||
Cinder uses file locks for the between process locking and cinderlib uses that
|
||||
same kind of locking for the third type of locks, which is also what Cinder
|
||||
uses when not deployed in an Active-Active fashion.
|
||||
|
||||
Parameter defaults to `None`, which will use the current directory to store all
|
||||
file locks required by the drivers.
|
||||
|
||||
disable_sudo
|
||||
------------
|
||||
|
||||
There are some operations in *Cinder* drivers that require `sudo` privileges,
|
||||
this could be because they are running Python code that requires it or because
|
||||
they are running a command with `sudo`.
|
||||
|
||||
Attaching and detaching operations with *cinderlib* will also require `sudo`
|
||||
privileges.
|
||||
|
||||
This configuration option allows us disabling all `sudo` operations when we
|
||||
know we don't require them and we are running the process with a non
|
||||
passwordless `sudo` user.
|
||||
|
||||
Defaults to `False`.
|
||||
|
||||
suppress_requests_ssl_warnings
|
||||
------------------------------
|
||||
|
||||
Controls the suppression of the *requests* library SSL certificate warnings.
|
||||
|
||||
Defaults to `True`.
|
||||
|
||||
non_uuid_ids
|
||||
------------
|
||||
|
||||
As mentioned in the :doc:`volumes` section we can provide resource IDs manually
|
||||
at creation time, and some drivers even support non UUID identificators, but
|
||||
since that's not a given validation will reject any non UUID value.
|
||||
|
||||
This configuration option allows us to disable the validation on the IDs, at
|
||||
the user's risk.
|
||||
|
||||
Defaults to `False`.
|
||||
|
||||
output_all_backend_info
|
||||
-----------------------
|
||||
|
||||
Whether to include the *Backend* configuration when serializing objects.
|
||||
Detailed information can be found in the :doc:`serialization` section.
|
||||
|
||||
Defaults to `False`.
|
||||
|
||||
disable_logs
|
||||
------------
|
||||
|
||||
*Cinder* drivers are meant to be run within a full blown service, so they can
|
||||
be quite verbose in terms of logging, that's why *cinderlib* disables it by
|
||||
default.
|
||||
|
||||
Defaults to `True`.
|
||||
|
||||
other keyword arguments
|
||||
-----------------------
|
||||
|
||||
Any other keyword argument passed to the initialization method will be
|
||||
considered a *Cinder* configuration option and passed directly to all the
|
||||
drivers.
|
||||
|
||||
This can be useful to set additional logging configuration like debug log
|
||||
level, or many other advanced features.
|
||||
|
||||
For a list of the possible configuration options one should look into the
|
||||
*Cinder* project's documentation.
|
|
@ -0,0 +1,151 @@
|
|||
=============
|
||||
Serialization
|
||||
=============
|
||||
|
||||
A *Cinder* driver is stateless on itself, but it still requires the right data
|
||||
to work, and that's why the cinder-volume service takes care of storing the
|
||||
state in the DB. This means that *cinderlib* will have to simulate the DB for
|
||||
the drivers, as some operations actually return additional data that needs to
|
||||
be kept and provided to any operation that follows.
|
||||
|
||||
During runtime all this information is stored in RAM, but what happens between
|
||||
runs if we have not removed all our resources? And what will happen on a
|
||||
system crash? What will happen is that *cinderlib* will lose all the data and
|
||||
will no longer be able to manage any of the resource, leaving them stranded in
|
||||
the storage backend.
|
||||
|
||||
What we have here is a requirement to provide a way to store the internal
|
||||
*cinderclient* data, but that's not the only thing we'll want, as many systems
|
||||
will require a mechanism to support over the wire transmission of *cinderlib*
|
||||
objects. To solve this problem *cinderlib* provides a JSON encoding mechanism
|
||||
that allows serialization and deserialization of its objects.
|
||||
|
||||
For the serialization process we have two type of methods, one called `json`
|
||||
that converts to a JSON stored in a Python dictionary, and another called
|
||||
`jsons` that will return a Python string representation. And for the
|
||||
deserialization we have just one type of method called `load`.
|
||||
|
||||
To JSON
|
||||
-------
|
||||
|
||||
As we mentioned before we have `json` and `jsons` methods, and these exist in
|
||||
all *cinderlib* objects: *Backend*, *Volume*, *Snapshot*, and *Connection* as
|
||||
well as in the library itself.
|
||||
|
||||
Current *cinderlib* serialization implementation is suboptimal for many cases
|
||||
since we cannot limit how deep the serialization will go when there are other
|
||||
objects references. This means that if we serialize a *Snapthot* this will
|
||||
return not only the *Snapshot* information but also the information from the
|
||||
*Volume* referenced in the *Snapshot*'s `volume` attribute, and when
|
||||
serializing this *Volume* we will also serialize the `snapshots` field that
|
||||
contain all the other snapshots as well as all its *Connections*. So in the
|
||||
end a simple serialization of a *Snapshot* resulted in a JSON of the *Volume*
|
||||
with all its *Snapshots*.
|
||||
|
||||
.. note::
|
||||
|
||||
We don't have to worry about circular references since *cinderlib* is
|
||||
prepared to handle them.
|
||||
|
||||
Due to this limitation the serialization is mostly useful, at this point, to
|
||||
store all the information from a *Volume*, from a *Backend*, or from all the
|
||||
*Backends*.
|
||||
|
||||
Here is an easy way to save all the *Backend's* resources information from
|
||||
*cinderlib*:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with open('cinderlib.txt', 'w') as f:
|
||||
f.write(cinderlib.jsons())
|
||||
|
||||
In a similar way we can also store a single *Backend* or a single *Volume*:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vol = lvm.create_volume(size=1)
|
||||
|
||||
with open('lvm.txt', 'w') as f:
|
||||
f.write(lvm.jsons())
|
||||
|
||||
with open('vol.txt', 'w') as f:
|
||||
f.write(vol.jsons())
|
||||
|
||||
From JSON
|
||||
---------
|
||||
|
||||
Just like we had the `json` and `jsons` methods in all the *cinderlib* objects,
|
||||
we'll also have the `load` method to recreate a *cinderlib* internal
|
||||
representation from JSON, be it stored in a Python string or a Python
|
||||
dictionary.
|
||||
|
||||
So we have a `load` method in *Backend*, *Volume*, *Snapshot*, and *Connection*
|
||||
as well as in the library itself, but here the library's `load` method is
|
||||
different that it's `json` and `jsons` counterpart, as it will deserialize any
|
||||
kind of *cinderlib* object.
|
||||
|
||||
Considering the files we created in the earlier examples we can easily load our
|
||||
whole configuration with:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# We must have initialized the Backends before reaching this point
|
||||
|
||||
with open('cinderlib.txt', 'r') as f:
|
||||
data = f.read()
|
||||
backends = cinderlib.load(data)
|
||||
|
||||
And for a specific backend or an individual volume:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# We must have initialized the Backends before reaching this point
|
||||
|
||||
with open('lvm.txt', 'r') as f:
|
||||
data = f.read()
|
||||
lvm = cinderlib.load(data)
|
||||
|
||||
with open('vol.txt', 'r') as f:
|
||||
data = f.read()
|
||||
vol = cinderlib.load(data)
|
||||
|
||||
This is the preferred way to deserialize objects, but we could also use the
|
||||
specific object's `load` method.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# We must have initialized the Backends before reaching this point
|
||||
|
||||
with open('lvm.txt', 'r') as f:
|
||||
data = f.read()
|
||||
lvm = cinderlib.Backend.load(data)
|
||||
|
||||
with open('vol.txt', 'r') as f:
|
||||
data = f.read()
|
||||
vol = cinderlib.Volume.load(data)
|
||||
|
||||
Backend configuration
|
||||
---------------------
|
||||
|
||||
When *cinderlib* serializes any object it also stores the *Backend* this object
|
||||
belongs to, and by default and for security reasons, it only stores the
|
||||
identifier of the backend, which is the `volume_backend_name`. Since we are
|
||||
only storing a reference to the *Backend* this means that when you are going
|
||||
through the deserialization process you require that the *Backend* the object
|
||||
belonged to already present in *cinderlib*.
|
||||
|
||||
This should be OK for most *cinderlib* usages, since it's common practice to
|
||||
store you storage backend connection information (credentials, addresses, etc.)
|
||||
in a different location than your data, but there may be situations (for
|
||||
example while testing) where we'll want to store everything in the same file,
|
||||
not only the *cinderlib* representation of all the storage resources but also
|
||||
the *Backend* configuration required to access the storage array.
|
||||
|
||||
To enable the serialization of the whole driver configuration we have to
|
||||
specify `output_all_backend_info=True` on the *cinderlib* initialization
|
||||
resulting in a self contained file with all the information required to manage
|
||||
the resources.
|
||||
|
||||
This means that with this configuration option we won't need to configure the
|
||||
*Backends* prior to loading the serialized JSON data, we can just load the data
|
||||
and *cinderlib* will automatically setup the *Backends*.
|
|
@ -0,0 +1,65 @@
|
|||
=========
|
||||
Snapshots
|
||||
=========
|
||||
|
||||
The *Snapshot* class provides the abstraction layer required to perform all
|
||||
operations on an existing snapshot, which means that the snapshot creation
|
||||
operation must be invoked from other class instance, since the new snapshot we
|
||||
want to create doesn't exist yet and we cannot use the *Snapshot* class to
|
||||
manage it.
|
||||
|
||||
Create
|
||||
------
|
||||
|
||||
Once we have a *Volume* instance we are ready to create snapshots from it, and
|
||||
we can do it for attached as well as detached volumes.
|
||||
|
||||
.. note::
|
||||
|
||||
Some drivers, like the NFS, require assistance from the Compute service for
|
||||
attached volumes, so they is currently no way of doing this with
|
||||
*cinderlib*
|
||||
|
||||
Creating a snapshot can only be performed by the `create_snapshot` method from
|
||||
our *Volume* instance, and once we have have created a snapshot it will be
|
||||
tracked in the *Volume* instance's `snapshots` set.
|
||||
|
||||
Here is a simple code to create a snapshot and use the `snapshots` set to
|
||||
verify that both, the returned value by the call as well as the entry added to
|
||||
the `snapshots` attribute, reference the same object and that the `volume`
|
||||
attribute in the *Snapshot* is referencing the source volume.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vol = lvm.create_volume(size=1)
|
||||
snap = vol.create_snapshot()
|
||||
assert snap is list(vol.snapshots)[0]
|
||||
assert vol is snap.volume
|
||||
|
||||
Delete
|
||||
------
|
||||
|
||||
Once we have created a *Sanpshot* we can use its `delete` method to permanently
|
||||
remove it from the storage backend.
|
||||
|
||||
Deleting a snapshot will remove its reference from the source *Volume*'s
|
||||
`snapshots` set.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vol = lvm.create_volume(size=1)
|
||||
snap = vol.create_snapshot()
|
||||
assert 1 == len(vol.snapshots)
|
||||
snap.delete()
|
||||
assert 0 == len(vol.snapshots)
|
||||
|
||||
Other methods
|
||||
-------------
|
||||
|
||||
All other methods available in the *Snapshot* class will be explained in their
|
||||
relevant sections:
|
||||
|
||||
- `load` will be explained together with `json` and `jsons` properties in the
|
||||
:doc:`serialization` section.
|
||||
|
||||
- `create_volume` method has been covered in the :doc:`volumes` section.
|
|
@ -0,0 +1,60 @@
|
|||
Resource tracking
|
||||
-----------------
|
||||
|
||||
*Cinderlib* users will surely have their own variables to keep track of the
|
||||
*Backends*, *Volumes*, *Snapshots*, and *Connections*, but there may be cases
|
||||
where this is not enough, be it because we are in a place in our code where we
|
||||
don't have access to the original variables, because we want to iterate all
|
||||
instances, or maybe we are running some manual tests and we have lost the
|
||||
reference to a resource. F
|
||||
|
||||
For these cases we can use *cinderlib's* various tracking systems to access the
|
||||
resources. These tracking systems are also used by *cinderlib* in the
|
||||
serialization process.
|
||||
|
||||
*Cinderlib* keeps track of all:
|
||||
|
||||
- Initialized *Backends*.
|
||||
- Existing volumes in a *Backend*.
|
||||
- Connections to a volume.
|
||||
- Local attachment to a volume.
|
||||
- Snapshots for a given volume.
|
||||
- Known volumes in this run, even deleted ones.
|
||||
- Known snapshots in this run, even delete ones.
|
||||
- Connections made in this run, even disconnected ones.
|
||||
|
||||
Initialized *Backends* are stored in a dictionary in `Backends.backends` using
|
||||
the `volume_backend_name` as key.
|
||||
|
||||
Existing volumes in a *Backend* are stored in the *Backend* instance's
|
||||
`volumes` attribute as a set.
|
||||
|
||||
Connections to a *Volume* are stored in the *Volume* instance's `connections`
|
||||
attribute as a list.
|
||||
|
||||
The local attachment *Connection* of a volume is stored in the *Volume*
|
||||
instance's `local_attach` attribute.
|
||||
|
||||
Existing *Snapshots* for a *Volume* are stored as a set in the *Volume*
|
||||
instance's `snapshots` attribute.
|
||||
|
||||
Besides the existing resources we also have access to all resources that
|
||||
*cinderlib* has known about in this run through the `objects` attribute that
|
||||
can be found in `Volume`, `Snapshot`, and `Connection` classes.
|
||||
|
||||
We can use this information to display the status of all the resources we've
|
||||
created and destroyed in this run:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
for vol in cinderlib.Volume.objects:
|
||||
print('Volume %s is currently %s' % (vol.id, vol.status)
|
||||
|
||||
for snap in cinderlib.Snapshot.objects:
|
||||
print('Snapshot %s for volume %s is currently %s' %
|
||||
(snap.id, snap.volume.id, snap.status))
|
||||
|
||||
for conn in cinderlib.Connection.objects:
|
||||
print('Connection from %s with ip %s to volume %s is %s' %
|
||||
(conn.connector['host'], conn.connector['ip'],
|
||||
conn.volume.id, conn.status))
|
|
@ -0,0 +1,226 @@
|
|||
=======
|
||||
Volumes
|
||||
=======
|
||||
|
||||
The *Volume* class provides the abstraction layer required to perform all
|
||||
operations on an existing volume, which means that there will be volume
|
||||
creation operations that will be invoked from other class instances, since the
|
||||
new volume we want to create doesn't exist yet and we cannot use the *Volume*
|
||||
class to manage it.
|
||||
|
||||
Create
|
||||
------
|
||||
|
||||
The base resource in storage is the volume, and to create one the *cinderlib*
|
||||
provides three different mechanisms, each one with a different method that will
|
||||
be called on the source of the new volume.
|
||||
|
||||
So we have:
|
||||
|
||||
- Empty volumes that have no resource source and will have to be created
|
||||
directly on the *Backend* via the `create_volume` method.
|
||||
|
||||
- Cloned volumes that will be created from a source *Volume* using its `clone`
|
||||
method.
|
||||
|
||||
- Volumes from a snapshot, where the creation is initiated by the
|
||||
`create_volume` method from the *Snapshot* instance.
|
||||
|
||||
.. note::
|
||||
|
||||
*Cinder* NFS backends will create an image and not a directory where to
|
||||
store files, which falls in line with *Cinder* being a Block Storage
|
||||
provider and not filesystem provider like *Manila* is.
|
||||
|
||||
So assuming that we have an `lvm` variable holding an initialized *Backend*
|
||||
instance we could create a new 1GB volume quite easily:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
print('Stats before creating the volume are:')
|
||||
pprint(lvm.stats())
|
||||
vol = lvm.create_volume(1)
|
||||
pprint(lvm.stats())
|
||||
|
||||
|
||||
Now, if we have a volume that already contains data and we want to create a new
|
||||
volume that starts with the same contents we can use the source volume as the
|
||||
cloning source:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
cloned_vol = vol.clone()
|
||||
|
||||
Some drivers support cloning to a bigger volume, so we could define the new
|
||||
size in the call and the driver would take care of extending the volume after
|
||||
cloning it, this is usually tightly linked to the `extend` operation support by
|
||||
the driver.
|
||||
|
||||
Cloning to a greater size would look like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
new_size = vol.size + 1
|
||||
cloned_bigger_volume = vol.clone(size=new_size)
|
||||
|
||||
.. note::
|
||||
|
||||
Cloning efficiency is directly linked to the storage backend in use, so it
|
||||
will not have the same performance in all backends. While some backends
|
||||
like the Ceph/RBD will be extremely efficient others may range from slow to
|
||||
being actually implemented as a `dd` operation performed by the driver
|
||||
attaching source and destination volumes.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vol = snap.create_volume()
|
||||
|
||||
.. note::
|
||||
|
||||
Just like with the cloning functionality, not all storage backends can
|
||||
efficiently handle creating a volume from a snapshot.
|
||||
|
||||
On volume creation we can pass additional parameters like a `name` or a
|
||||
`description`, but these will be irrelevant for the actual volume creation and
|
||||
will only be useful to us to easily identify our volumes or to store additional
|
||||
information. This information is not written in the storage backend and lives
|
||||
in RAM.
|
||||
|
||||
Available fields with their types can be found in `Cinder's Volume OVO
|
||||
definition
|
||||
<https://github.com/openstack/cinder/blob/stable/pike/cinder/objects/volume.py#L69-L126>`_,
|
||||
but most of them are only relevant within the full *Cinder* service.
|
||||
|
||||
We can access these fields as if they were part of the *cinderlib* *Volume*
|
||||
instance, since the class will try to retrieve any non *cinderlib* *Volume*
|
||||
from *Cinder*'s internal OVO representation.
|
||||
|
||||
Some of the fields we could be interested in are:
|
||||
|
||||
- `id`: UUID-4 unique identifier for the volume.
|
||||
|
||||
- `user_id`: String identifier, in *Cinder* it's a UUID, but we can choose
|
||||
here.
|
||||
|
||||
- `project_id`: String identifier, in *Cinder* it's a UUID, but we can choose
|
||||
here.
|
||||
|
||||
- `snapshot_id`: ID of the source snapshot used to create the volume. This
|
||||
will be filled by *cinderlib*.
|
||||
|
||||
- `host`: In *Cinder* used to store the *host@backend#pool* information, here
|
||||
we can just keep some identification of the process that wrote this.
|
||||
|
||||
- `size`: Volume size in GBi.
|
||||
|
||||
- `availability_zone`: In case we want to define AZs.
|
||||
|
||||
- `status`: This represents the status of the volume, and the most important
|
||||
statuses are `available`, `error`, `deleted`, `in-use`, `creating`.
|
||||
|
||||
- `attach_status`: This can be `attached` or `detached`.
|
||||
|
||||
- `scheduled_at`: Date-time when the volume was scheduled to be created.
|
||||
Currently not being used by *cinderlib*.
|
||||
|
||||
- `launched_at`: Date-time when the volume creation was completed. Currently
|
||||
not being used by *cinderlib*.
|
||||
|
||||
- `deleted`: Boolean value indicating whether the volume has already been
|
||||
deleted. It will be filled by *cinderlib*.
|
||||
|
||||
- `terminated_at`: When the volume delete was sent to the backend.
|
||||
|
||||
- `deleted_at`: When the volume delete was completed.
|
||||
|
||||
- `display_name`: Name identifier, this is passed as `name` to all *cinderlib*
|
||||
volume creation methods.
|
||||
|
||||
- `display_description`: Long description of the volume, this is passed as
|
||||
`description` to all *cinderlib* volume creation methods.
|
||||
|
||||
- `source_volid`: ID of the source volume used to create this volume. This
|
||||
will be filled by *cinderlib*.
|
||||
|
||||
- `bootable`: Not relevant for *cinderlib*, but maybe useful for the
|
||||
*cinderlib* user.
|
||||
|
||||
.. note::
|
||||
|
||||
*Cinderlib* automatically generates a UUID for the `id` if one is not
|
||||
provided at volume creation time, but the caller can actually provide a
|
||||
specific `id`.
|
||||
|
||||
By default the `id` is limited to valid UUID and this is the only kind of
|
||||
ID that is guaranteed to work on all drivers. For drivers that support non
|
||||
UUID IDs we can instruct *cinderlib* to modify *Cinder*'s behavior and
|
||||
allow them. This is done on *cinderlib* initialization time passing
|
||||
`non_uuid_ids=True`.
|
||||
|
||||
Delete
|
||||
------
|
||||
|
||||
Once we have created a *Volume* we can use its `delete` method to permanently
|
||||
remove it from the storage backend.
|
||||
|
||||
In *Cinder* there are safeguards to prevent a delete operation from completing
|
||||
if it has snapshots (unless the delete request comes with the `cascade` option
|
||||
set to true), but here in *cinderlib* we don't, so it's the callers
|
||||
responsibility to delete the snapshots.
|
||||
|
||||
Deleting a volume with snapshots doesn't have a defined behavior for *Cinder*
|
||||
drivers, since it's never meant to happen, so some storage backends delete the
|
||||
snapshots, other leave them as they were, and others will fail the request.
|
||||
|
||||
Example of creating and deleting a volume:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vol = lvm.create_volume(size=1)
|
||||
vol.delete()
|
||||
|
||||
.. attention::
|
||||
|
||||
When deleting a volume that was the source of a cloning operation some
|
||||
backends cannot delete them (since they have copy-on-write clones) and they
|
||||
just keep them as a silent volume that will be deleted when its snapshot
|
||||
and clones are deleted.
|
||||
|
||||
Extend
|
||||
------
|
||||
|
||||
Many storage backends and *Cinder* drivers support extending a volume to have
|
||||
more space and you can do this via the `extend` method present in your *Volume*
|
||||
instance.
|
||||
|
||||
If the *Cinder* driver doesn't implement the extend operation it will raise a
|
||||
`NotImplementedError`.
|
||||
|
||||
The only parameter received by the `extend` method is the new size, and this
|
||||
must always be greater than the current value because *cinderlib* is not
|
||||
validating this at the moment.
|
||||
|
||||
Example of creating, extending, and deleting a volume:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vol = lvm.create_volume(size=1)
|
||||
print('Vol %s has %s GBi' % (vol.id, vol.size))
|
||||
vol.extend(2)
|
||||
print('Extended vol %s has %s GBi' % (vol.id, vol.size))
|
||||
vol.delete()
|
||||
|
||||
Other methods
|
||||
-------------
|
||||
|
||||
All other methods available in the *Volume* class will be explained in their
|
||||
relevant sections:
|
||||
|
||||
- `load` will be explained together with `json` and `jsons` properties in the
|
||||
:doc:`serialization` section.
|
||||
|
||||
- `create_snapshot` method will be covered in the :doc:`snapshots` section
|
||||
together with the `snapshots` attribute.
|
||||
|
||||
- `attach`, `detach`, `connect`, and `disconnect` methods will be explained in
|
||||
the :doc:`connections` section.
|
|
@ -2,6 +2,54 @@
|
|||
Usage
|
||||
=====
|
||||
|
||||
To use Cinder Library in a project::
|
||||
Providing a fully Object Oriented abstraction, instead of a classic method
|
||||
invocation passing the resources to work on, *cinderlib* makes it easy to hit
|
||||
the ground running when managing storage resources.
|
||||
|
||||
Once Cinder drivers and *cinderlib* are installed we just have to import the
|
||||
library to start using it:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import cinderlib
|
||||
|
||||
Usage documentation is not too long and it is recommended to read it all before
|
||||
using the library to be sure we have at least a high level view of the
|
||||
different aspects related to managing our storage with *cinderlib*.
|
||||
|
||||
Before going into too much detail there are some aspects we need to clarify to
|
||||
make sure our terminology is in sync and we understand where each piece fits.
|
||||
|
||||
In *cinderlib* we have *Backends*, that refer to a storage array's specific
|
||||
connection configuration so it usually doesn't refer to the whole storage. With
|
||||
a backend we'll usually have access to the configured pool.
|
||||
|
||||
Resources managed by *cinderlib* are *Volumes* and *Snapshots*, and a *Volume*
|
||||
can be created from a *Backend*, another *Volume*, or from a *Snapshot*, and a
|
||||
*Snapshot* can only be created from a *Volume*.
|
||||
|
||||
Once we have a volume we can create *Connections* so it can be accessible from
|
||||
other hosts or we can do a local *Attachment* of the volume which will retrieve
|
||||
required local connection information of this host, create a *Connection* on
|
||||
the storage to this host, and then do the local *Attachment*.
|
||||
|
||||
Given that Cinder drivers are not stateless, *cinderlib* cannot be either and
|
||||
will keep track of all the resources in memory while the library is running,
|
||||
and provides a JSON serialization mechanism to support saving and restoring the
|
||||
state of all the resources.
|
||||
|
||||
For extended information on these topics please refer to their specific
|
||||
sections.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
topics/initialization
|
||||
topics/backends
|
||||
topics/volumes
|
||||
topics/snapshots
|
||||
topics/connections
|
||||
topics/serialization
|
||||
topics/tracking
|
||||
|
||||
.. _GIGO: https://en.wikipedia.org/wiki/Garbage_in,_garbage_out
|
||||
|
|
Loading…
Reference in New Issue