Initial commit
This is the initial commit for the rbd iscsi client. This is a client to talk to the ceph-iscsi project's rbd-target-api service.
This commit is contained in:
commit
7f22765800
15
.github/ISSUE_TEMPLATE.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
* RBD iSCSI Client version:
|
||||||
|
* Python version:
|
||||||
|
* Operating System:
|
||||||
|
|
||||||
|
### Description
|
||||||
|
|
||||||
|
Describe what you were trying to get done.
|
||||||
|
Tell us what happened, what went wrong, and what you expected to happen.
|
||||||
|
|
||||||
|
### What I Did
|
||||||
|
|
||||||
|
```
|
||||||
|
Paste the command(s) you ran and the output.
|
||||||
|
If there was a crash, please include the traceback here.
|
||||||
|
```
|
102
.gitignore
vendored
Normal file
102
.gitignore
vendored
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# dotenv
|
||||||
|
.env
|
||||||
|
|
||||||
|
# virtualenv
|
||||||
|
.venv
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
test_path=${OS_TEST_PATH:-./rbd_iscsi_client/tests}
|
||||||
|
top_dir=./
|
13
.travis.yml
Normal file
13
.travis.yml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Config file for automatic testing at travis-ci.org
|
||||||
|
|
||||||
|
language: python
|
||||||
|
python:
|
||||||
|
- 3.7
|
||||||
|
- 3.6
|
||||||
|
- 2.7
|
||||||
|
|
||||||
|
# Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
|
||||||
|
install: pip install -U tox-travis -r requirements.txt
|
||||||
|
|
||||||
|
# Command to run tests, e.g. python setup.py test
|
||||||
|
script: tox
|
13
AUTHORS.rst
Normal file
13
AUTHORS.rst
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
=======
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
Development Lead
|
||||||
|
----------------
|
||||||
|
|
||||||
|
* Walter A. Boring IV <waboring@hemna.com>
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
None yet. Why not be the first?
|
127
CONTRIBUTING.rst
Normal file
127
CONTRIBUTING.rst
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
.. highlight:: shell
|
||||||
|
|
||||||
|
============
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
Contributions are welcome, and they are greatly appreciated! Every little bit
|
||||||
|
helps, and credit will always be given.
|
||||||
|
|
||||||
|
You can contribute in many ways:
|
||||||
|
|
||||||
|
Types of Contributions
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Report Bugs
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
Report bugs at https://github.com/hemna/rbd-iscsi-client/issues.
|
||||||
|
|
||||||
|
If you are reporting a bug, please include:
|
||||||
|
|
||||||
|
* Your operating system name and version.
|
||||||
|
* Any details about your local setup that might be helpful in troubleshooting.
|
||||||
|
* Detailed steps to reproduce the bug.
|
||||||
|
|
||||||
|
Fix Bugs
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Look through the GitHub issues for bugs. Anything tagged with "bug" 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.
|
||||||
|
|
||||||
|
Write Documentation
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
RBD iSCSI Client could always use more documentation, whether as part of the
|
||||||
|
official RBD iSCSI Client docs, in docstrings, or even on the web in blog posts,
|
||||||
|
articles, and such.
|
||||||
|
|
||||||
|
Submit Feedback
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The best way to send feedback is to file an issue at https://github.com/hemna/rbd-iscsi-client/issues.
|
||||||
|
|
||||||
|
If you are proposing a feature:
|
||||||
|
|
||||||
|
* Explain in detail how it would work.
|
||||||
|
* Keep the scope as narrow as possible, to make it easier to implement.
|
||||||
|
* Remember that this is a volunteer-driven project, and that contributions
|
||||||
|
are welcome :)
|
||||||
|
|
||||||
|
Get Started!
|
||||||
|
------------
|
||||||
|
|
||||||
|
Ready to contribute? Here's how to set up `rbd-iscsi-client` for local development.
|
||||||
|
|
||||||
|
1. Fork the `rbd-iscsi-client` repo on GitHub.
|
||||||
|
2. Clone your fork locally::
|
||||||
|
|
||||||
|
$ git clone git@github.com:hemna/rbd-iscsi-client.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::
|
||||||
|
|
||||||
|
$ mkvirtualenv rbd-iscsi-client
|
||||||
|
$ cd rbd-iscsi-client/
|
||||||
|
$ python setup.py develop
|
||||||
|
|
||||||
|
4. 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::
|
||||||
|
|
||||||
|
$ tox -e pep8
|
||||||
|
$ tox
|
||||||
|
|
||||||
|
To get flake8 and tox, just pip install them into your virtualenv.
|
||||||
|
|
||||||
|
6. Commit your changes and push your branch to GitHub::
|
||||||
|
|
||||||
|
$ git add .
|
||||||
|
$ git commit -m "Your detailed description of your changes."
|
||||||
|
$ git push origin name-of-your-bugfix-or-feature
|
||||||
|
|
||||||
|
7. Submit a pull request through the GitHub website.
|
||||||
|
|
||||||
|
Pull Request Guidelines
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Before you submit a pull request, check that it meets these guidelines:
|
||||||
|
|
||||||
|
1. The pull request should include tests.
|
||||||
|
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.4, 3.5 and 3.6, and for PyPy. Check
|
||||||
|
https://travis-ci.org/hemna/rbd-iscsi-client/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_rbd_iscsi_client
|
||||||
|
|
||||||
|
Deploying
|
||||||
|
---------
|
||||||
|
|
||||||
|
A reminder for the maintainers on how to deploy.
|
||||||
|
Make sure all your changes are committed (including an entry in HISTORY.rst).
|
||||||
|
Then run::
|
||||||
|
|
||||||
|
$ bumpversion patch # possible: major / minor / patch
|
||||||
|
$ git push
|
||||||
|
$ git push --tags
|
||||||
|
|
||||||
|
Travis will then deploy to PyPI if tests pass.
|
8
HISTORY.rst
Normal file
8
HISTORY.rst
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
=======
|
||||||
|
History
|
||||||
|
=======
|
||||||
|
|
||||||
|
0.1.0 (2019-06-12)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
* First release on PyPI.
|
16
LICENSE
Normal file
16
LICENSE
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
Apache Software License 2.0
|
||||||
|
|
||||||
|
Copyright (c) 2019, Walter A. Boring IV
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
11
MANIFEST.in
Normal file
11
MANIFEST.in
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
include AUTHORS.rst
|
||||||
|
include CONTRIBUTING.rst
|
||||||
|
include HISTORY.rst
|
||||||
|
include LICENSE
|
||||||
|
include README.rst
|
||||||
|
|
||||||
|
recursive-include rbd_iscsi_client *
|
||||||
|
recursive-exclude * __pycache__
|
||||||
|
recursive-exclude * *.py[co]
|
||||||
|
|
||||||
|
recursive-include doc *.rst conf.py Makefile *.jpg *.png *.gif
|
88
Makefile
Normal file
88
Makefile
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
.PHONY: clean clean-test clean-pyc clean-build docs help
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
define BROWSER_PYSCRIPT
|
||||||
|
import os, webbrowser, sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urllib import pathname2url
|
||||||
|
except:
|
||||||
|
from urllib.request import pathname2url
|
||||||
|
|
||||||
|
webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
|
||||||
|
endef
|
||||||
|
export BROWSER_PYSCRIPT
|
||||||
|
|
||||||
|
define PRINT_HELP_PYSCRIPT
|
||||||
|
import re, sys
|
||||||
|
|
||||||
|
for line in sys.stdin:
|
||||||
|
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
|
||||||
|
if match:
|
||||||
|
target, help = match.groups()
|
||||||
|
print("%-20s %s" % (target, help))
|
||||||
|
endef
|
||||||
|
export PRINT_HELP_PYSCRIPT
|
||||||
|
|
||||||
|
BROWSER := python -c "$$BROWSER_PYSCRIPT"
|
||||||
|
|
||||||
|
help:
|
||||||
|
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
|
||||||
|
|
||||||
|
clean-build: ## remove build artifacts
|
||||||
|
rm -fr build/
|
||||||
|
rm -fr dist/
|
||||||
|
rm -fr .eggs/
|
||||||
|
find . -name '*.egg-info' -exec rm -fr {} +
|
||||||
|
find . -name '*.egg' -exec rm -f {} +
|
||||||
|
|
||||||
|
clean-pyc: ## remove Python file artifacts
|
||||||
|
find . -name '*.pyc' -exec rm -f {} +
|
||||||
|
find . -name '*.pyo' -exec rm -f {} +
|
||||||
|
find . -name '*~' -exec rm -f {} +
|
||||||
|
find . -name '__pycache__' -exec rm -fr {} +
|
||||||
|
|
||||||
|
clean-test: ## remove test and coverage artifacts
|
||||||
|
rm -fr .tox/
|
||||||
|
rm -f .coverage
|
||||||
|
rm -fr htmlcov/
|
||||||
|
rm -fr .pytest_cache
|
||||||
|
|
||||||
|
lint: ## check style with flake8
|
||||||
|
tox -e pep8
|
||||||
|
|
||||||
|
test: ## run tests quickly with the default Python
|
||||||
|
python setup.py test
|
||||||
|
|
||||||
|
test-all: ## run tests on every Python version with tox
|
||||||
|
tox
|
||||||
|
|
||||||
|
coverage: ## check code coverage quickly with the default Python
|
||||||
|
coverage run --source rbd_iscsi_client setup.py test
|
||||||
|
coverage report -m
|
||||||
|
coverage html
|
||||||
|
$(BROWSER) htmlcov/index.html
|
||||||
|
|
||||||
|
docs: ## generate Sphinx HTML documentation, including API docs
|
||||||
|
rm -f docs/rbd_iscsi_client.rst
|
||||||
|
rm -f docs/modules.rst
|
||||||
|
sphinx-apidoc -o docs/ rbd_iscsi_client
|
||||||
|
$(MAKE) -C docs clean
|
||||||
|
$(MAKE) -C docs html
|
||||||
|
$(BROWSER) docs/_build/html/index.html
|
||||||
|
|
||||||
|
servedocs: docs ## compile the docs watching for changes
|
||||||
|
watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D .
|
||||||
|
|
||||||
|
release: dist ## package and upload a release
|
||||||
|
twine upload dist/*
|
||||||
|
|
||||||
|
dist: clean ## builds source and wheel package
|
||||||
|
python setup.py sdist
|
||||||
|
python setup.py bdist_wheel
|
||||||
|
ls -l dist
|
||||||
|
|
||||||
|
install: clean ## install the package to the active Python's site-packages
|
||||||
|
python setup.py install
|
41
README.rst
Normal file
41
README.rst
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
================
|
||||||
|
RBD iSCSI Client
|
||||||
|
================
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/rbd_iscsi_client.svg
|
||||||
|
:target: https://pypi.python.org/pypi/rbd_iscsi_client
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/travis/hemna/rbd_iscsi_client.svg
|
||||||
|
:target: https://travis-ci.org/hemna/rbd_iscsi_client
|
||||||
|
|
||||||
|
.. image:: https://readthedocs.org/projects/rbd-iscsi-client/badge/?version=latest
|
||||||
|
:target: https://rbd-iscsi-client.readthedocs.io/en/latest/?badge=latest
|
||||||
|
:alt: Documentation Status
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: https://pyup.io/repos/github/hemna/rbd_iscsi_client/shield.svg
|
||||||
|
:target: https://pyup.io/repos/github/hemna/rbd_iscsi_client/
|
||||||
|
:alt: Updates
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
REST client to talk to rbd-target-api
|
||||||
|
|
||||||
|
|
||||||
|
* Free software: Apache Software License 2.0
|
||||||
|
* Documentation: https://rbd-iscsi-client.readthedocs.io.
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
* TODO
|
||||||
|
|
||||||
|
Credits
|
||||||
|
-------
|
||||||
|
|
||||||
|
This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
|
||||||
|
|
||||||
|
.. _Cookiecutter: https://github.com/audreyr/cookiecutter
|
||||||
|
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
|
20
doc/source/Makefile
Normal file
20
doc/source/Makefile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = python -msphinx
|
||||||
|
SPHINXPROJ = rbd_iscsi_client
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
1
doc/source/authors.rst
Normal file
1
doc/source/authors.rst
Normal file
@ -0,0 +1 @@
|
|||||||
|
.. include:: ../../AUTHORS.rst
|
172
doc/source/conf.py
Executable file
172
doc/source/conf.py
Executable file
@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# rbd_iscsi_client documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Fri Jun 9 13:47:02 2017.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another
|
||||||
|
# directory, add these directories to sys.path here. If the directory is
|
||||||
|
# relative to the documentation root, use os.path.abspath to make it
|
||||||
|
# absolute, like shown here.
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
|
|
||||||
|
import rbd_iscsi_client
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
'reno.sphinxext',
|
||||||
|
'openstackdocstheme']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix(es) of source filenames.
|
||||||
|
# You can specify multiple suffix as a list of string:
|
||||||
|
#
|
||||||
|
# source_suffix = ['.rst', '.md']
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'RBD iSCSI Client'
|
||||||
|
copyright = u"2019, Walter A. Boring IV"
|
||||||
|
author = u"Walter A. Boring IV"
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement
|
||||||
|
# for |version| and |release|, also used in various other places throughout
|
||||||
|
# the built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = rbd_iscsi_client.__version__
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = rbd_iscsi_client.__version__
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#
|
||||||
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = None
|
||||||
|
|
||||||
|
add_function_parentheses = True
|
||||||
|
add_module_names = True
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This patterns also effect to html_static_path and html_extra_path
|
||||||
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
html_theme = 'openstackdocs'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a
|
||||||
|
# theme further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#
|
||||||
|
# html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTMLHelp output ---------------------------------------
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'rbd_iscsi_clientdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output ------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#
|
||||||
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#
|
||||||
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#
|
||||||
|
# 'preamble': '',
|
||||||
|
|
||||||
|
# Latex figure (float) alignment
|
||||||
|
#
|
||||||
|
# 'figure_align': 'htbp',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass
|
||||||
|
# [howto, manual, or own class]).
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, 'rbd_iscsi_client.tex',
|
||||||
|
u'RBD iSCSI Client Documentation',
|
||||||
|
u'Walter A. Boring IV', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output ------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
(master_doc, 'rbd_iscsi_client',
|
||||||
|
u'RBD iSCSI Client Documentation',
|
||||||
|
[author], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output ----------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
(master_doc, 'rbd_iscsi_client',
|
||||||
|
u'RBD iSCSI Client Documentation',
|
||||||
|
author,
|
||||||
|
'rbd_iscsi_client',
|
||||||
|
'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for openstackdocstheme -----------------------------------
|
||||||
|
repository_name = 'hemna/rbd-iscsi-client'
|
||||||
|
bug_project = 'rbd-iscsi-client'
|
||||||
|
bug_tag = ''
|
1
doc/source/contributing.rst
Normal file
1
doc/source/contributing.rst
Normal file
@ -0,0 +1 @@
|
|||||||
|
.. include:: ../../CONTRIBUTING.rst
|
1
doc/source/history.rst
Normal file
1
doc/source/history.rst
Normal file
@ -0,0 +1 @@
|
|||||||
|
.. include:: ../../HISTORY.rst
|
19
doc/source/index.rst
Normal file
19
doc/source/index.rst
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Welcome to RBD iSCSI Client's documentation!
|
||||||
|
============================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Contents:
|
||||||
|
|
||||||
|
readme
|
||||||
|
installation
|
||||||
|
usage
|
||||||
|
contributing
|
||||||
|
authors
|
||||||
|
history
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
51
doc/source/installation.rst
Normal file
51
doc/source/installation.rst
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
.. highlight:: shell
|
||||||
|
|
||||||
|
============
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
|
||||||
|
Stable release
|
||||||
|
--------------
|
||||||
|
|
||||||
|
To install RBD iSCSI Client, run this command in your terminal:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ pip install rbd-iscsi-client
|
||||||
|
|
||||||
|
This is the preferred method to install RBD iSCSI Client, 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.
|
||||||
|
|
||||||
|
.. _pip: https://pip.pypa.io
|
||||||
|
.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/
|
||||||
|
|
||||||
|
|
||||||
|
From sources
|
||||||
|
------------
|
||||||
|
|
||||||
|
The sources for RBD iSCSI Client can be downloaded from the `Github repo`_.
|
||||||
|
|
||||||
|
You can either clone the public repository:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ git clone git://github.com/hemna/rbd-iscsi-client
|
||||||
|
|
||||||
|
Or download the `tarball`_:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ curl -OL https://github.com/hemna/rbd-iscsi-client/tarball/master
|
||||||
|
|
||||||
|
Once you have a copy of the source, you can install it with:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ python setup.py install
|
||||||
|
|
||||||
|
|
||||||
|
.. _Github repo: https://github.com/hemna/rbd-iscsi-client
|
||||||
|
.. _tarball: https://github.com/hemna/rbd-iscsi-client/tarball/master
|
1
doc/source/readme.rst
Normal file
1
doc/source/readme.rst
Normal file
@ -0,0 +1 @@
|
|||||||
|
.. include:: ../../README.rst
|
7
doc/source/usage.rst
Normal file
7
doc/source/usage.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
=====
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
To use RBD iSCSI Client in a project::
|
||||||
|
|
||||||
|
import rbd_iscsi_client
|
7
rbd_iscsi_client/__init__.py
Normal file
7
rbd_iscsi_client/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Top-level package for RBD iSCSI Client."""
|
||||||
|
|
||||||
|
__author__ = """Walter A. Boring IV"""
|
||||||
|
__email__ = 'waboring@hemna.com'
|
||||||
|
__version__ = '0.1.0'
|
414
rbd_iscsi_client/exceptions.py
Normal file
414
rbd_iscsi_client/exceptions.py
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License."""
|
||||||
|
"""
|
||||||
|
Exceptions for the client
|
||||||
|
|
||||||
|
.. module: exceptions
|
||||||
|
|
||||||
|
:Author: Walter A. Boring IV
|
||||||
|
:Description: This contains the HTTP exceptions from rbd-target-api
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Python 3+ override
|
||||||
|
try:
|
||||||
|
basestring
|
||||||
|
except NameError:
|
||||||
|
basestring = str
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedVersion(Exception):
|
||||||
|
"""
|
||||||
|
Indicates that the user is trying to use an unsupported version of the API
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CommandError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthorizationFailure(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoUniqueMatch(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ClientException(Exception):
|
||||||
|
"""
|
||||||
|
The base exception class for all exceptions this library raises.
|
||||||
|
|
||||||
|
:param error: The error array
|
||||||
|
:type error: array
|
||||||
|
|
||||||
|
"""
|
||||||
|
_error_code = None
|
||||||
|
_error_desc = None
|
||||||
|
_error_ref = None
|
||||||
|
|
||||||
|
_debug1 = None
|
||||||
|
_debug2 = None
|
||||||
|
|
||||||
|
def __init__(self, error=None):
|
||||||
|
super(ClientException, self).__init__()
|
||||||
|
|
||||||
|
if not error:
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(error, basestring):
|
||||||
|
# instead of KeyError below, take it and make it the _error_desc.
|
||||||
|
self._error_desc = error
|
||||||
|
else:
|
||||||
|
if 'code' in error:
|
||||||
|
self._error_code = error['code']
|
||||||
|
if 'desc' in error:
|
||||||
|
self._error_desc = error['desc']
|
||||||
|
if 'ref' in error:
|
||||||
|
self._error_ref = error['ref']
|
||||||
|
|
||||||
|
if 'debug1' in error:
|
||||||
|
self._debug1 = error['debug1']
|
||||||
|
if 'debug2' in error:
|
||||||
|
self._debug2 = error['debug2']
|
||||||
|
|
||||||
|
def get_code(self):
|
||||||
|
return self._error_code
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return self._error_desc
|
||||||
|
|
||||||
|
def get_ref(self):
|
||||||
|
return self._error_ref
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
formatted_string = self.message
|
||||||
|
if self.http_status:
|
||||||
|
formatted_string += " (HTTP %s)" % self.http_status
|
||||||
|
if self._error_code:
|
||||||
|
formatted_string += " %s" % self._error_code
|
||||||
|
if self._error_desc:
|
||||||
|
formatted_string += " - %s" % self._error_desc
|
||||||
|
if self._error_ref:
|
||||||
|
formatted_string += " - %s" % self._error_ref
|
||||||
|
|
||||||
|
if self._debug1:
|
||||||
|
formatted_string += " (1: '%s')" % self._debug1
|
||||||
|
|
||||||
|
if self._debug2:
|
||||||
|
formatted_string += " (2: '%s')" % self._debug2
|
||||||
|
|
||||||
|
return formatted_string
|
||||||
|
|
||||||
|
# SSL Errors
|
||||||
|
|
||||||
|
|
||||||
|
class SSLCertFailed(ClientException):
|
||||||
|
"""
|
||||||
|
The SSL certificate from the server could not be verified
|
||||||
|
"""
|
||||||
|
http_status = ""
|
||||||
|
message = "SSL Certificate Verification Failed"
|
||||||
|
|
||||||
|
|
||||||
|
# Python Requests Errors
|
||||||
|
|
||||||
|
|
||||||
|
class RequestException(ClientException):
|
||||||
|
"""
|
||||||
|
There was an ambiguous exception that occurred in Requests
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionError(ClientException):
|
||||||
|
"""
|
||||||
|
There was an error connecting to the server
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPError(ClientException):
|
||||||
|
"""
|
||||||
|
An HTTP error occurred
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class URLRequired(ClientException):
|
||||||
|
"""
|
||||||
|
A valid URL is required to make a request
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TooManyRedirects(ClientException):
|
||||||
|
"""
|
||||||
|
Too many redirects
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Timeout(ClientException):
|
||||||
|
"""
|
||||||
|
The request timed out
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# 400 Errors
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPBadRequest(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 400 - Bad request: you sent some malformed data.
|
||||||
|
"""
|
||||||
|
http_status = 400
|
||||||
|
message = "Bad request"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPUnauthorized(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 401 - Unauthorized: bad credentials.
|
||||||
|
"""
|
||||||
|
http_status = 401
|
||||||
|
message = "Unauthorized"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPForbidden(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 403 - Forbidden: your credentials don't give you access to this
|
||||||
|
resource.
|
||||||
|
"""
|
||||||
|
http_status = 403
|
||||||
|
message = "Forbidden"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPNotFound(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 404 - Not found
|
||||||
|
"""
|
||||||
|
http_status = 404
|
||||||
|
message = "Not found"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPMethodNotAllowed(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 405 - Method not Allowed
|
||||||
|
"""
|
||||||
|
http_status = 405
|
||||||
|
message = "Method Not Allowed"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPNotAcceptable(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 406 - Method not Acceptable
|
||||||
|
"""
|
||||||
|
http_status = 406
|
||||||
|
message = "Method Not Acceptable"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPProxyAuthRequired(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 407 - The client must first authenticate itself with the proxy.
|
||||||
|
"""
|
||||||
|
http_status = 407
|
||||||
|
message = "Proxy Authentication Required"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPRequestTimeout(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 408 - The server timed out waiting for the request.
|
||||||
|
"""
|
||||||
|
http_status = 408
|
||||||
|
message = "Request Timeout"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPConflict(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 409 - Conflict: A Conflict happened on the server
|
||||||
|
"""
|
||||||
|
http_status = 409
|
||||||
|
message = "Conflict"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPGone(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 410 - Indicates that the resource requested is no longer available and
|
||||||
|
will not be available again.
|
||||||
|
"""
|
||||||
|
http_status = 410
|
||||||
|
message = "Gone"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPLengthRequired(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 411 - The request did not specify the length of its content, which is
|
||||||
|
required by the requested resource.
|
||||||
|
"""
|
||||||
|
http_status = 411
|
||||||
|
message = "Length Required"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPPreconditionFailed(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 412 - The server does not meet one of the preconditions that the
|
||||||
|
requester put on the request.
|
||||||
|
"""
|
||||||
|
http_status = 412
|
||||||
|
message = "Over limit"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPRequestEntityTooLarge(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 413 - The request is larger than the server is willing or able to
|
||||||
|
process
|
||||||
|
"""
|
||||||
|
http_status = 413
|
||||||
|
message = "Request Entity Too Large"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPRequestURITooLong(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 414 - The URI provided was too long for the server to process.
|
||||||
|
"""
|
||||||
|
http_status = 414
|
||||||
|
message = "Request URI Too Large"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPUnsupportedMediaType(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 415 - The request entity has a media type which the server or resource
|
||||||
|
does not support.
|
||||||
|
"""
|
||||||
|
http_status = 415
|
||||||
|
message = "Unsupported Media Type"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPRequestedRangeNotSatisfiable(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 416 - The client has asked for a portion of the file, but the server
|
||||||
|
cannot supply that portion.
|
||||||
|
"""
|
||||||
|
http_status = 416
|
||||||
|
message = "Requested Range Not Satisfiable"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPExpectationFailed(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 417 - The server cannot meet the requirements of the Expect
|
||||||
|
request-header field.
|
||||||
|
"""
|
||||||
|
http_status = 417
|
||||||
|
message = "Expectation Failed"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPTeaPot(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 418 - I'm a Tea Pot
|
||||||
|
"""
|
||||||
|
http_status = 418
|
||||||
|
message = "I'm A Teapot. (RFC 2324)"
|
||||||
|
|
||||||
|
|
||||||
|
# 500 Errors
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPInternalServerError(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 500 - Internal Server Error: an internal error occured.
|
||||||
|
"""
|
||||||
|
http_status = 500
|
||||||
|
message = "Internal Server Error"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPNotImplemented(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 501 - Not Implemented: the server does not support this operation.
|
||||||
|
"""
|
||||||
|
http_status = 501
|
||||||
|
message = "Not Implemented"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPBadGateway(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 502 - The server was acting as a gateway or proxy and received an
|
||||||
|
invalid response from the upstream server.
|
||||||
|
"""
|
||||||
|
http_status = 502
|
||||||
|
message = "Bad Gateway"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPServiceUnavailable(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 503 - The server is currently unavailable
|
||||||
|
"""
|
||||||
|
http_status = 503
|
||||||
|
message = "Service Unavailable"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPGatewayTimeout(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 504 - The server was acting as a gateway or proxy and did
|
||||||
|
not receive a timely response from the upstream server.
|
||||||
|
"""
|
||||||
|
http_status = 504
|
||||||
|
message = "Gateway Timeout"
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPVersionNotSupported(ClientException):
|
||||||
|
"""
|
||||||
|
HTTP 505 - The server does not support the HTTP protocol version used
|
||||||
|
in the request.
|
||||||
|
"""
|
||||||
|
http_status = 505
|
||||||
|
message = "Version Not Supported"
|
||||||
|
|
||||||
|
|
||||||
|
# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__()
|
||||||
|
# so we can do this:
|
||||||
|
# _code_map = dict((c.http_status, c)
|
||||||
|
# for c in ClientException.__subclasses__())
|
||||||
|
#
|
||||||
|
# Instead, we have to hardcode it:
|
||||||
|
_code_map = dict((c.http_status, c) for c in
|
||||||
|
[HTTPBadRequest, HTTPUnauthorized,
|
||||||
|
HTTPForbidden, HTTPNotFound, HTTPMethodNotAllowed,
|
||||||
|
HTTPNotAcceptable, HTTPProxyAuthRequired,
|
||||||
|
HTTPRequestTimeout, HTTPConflict, HTTPGone,
|
||||||
|
HTTPLengthRequired, HTTPPreconditionFailed,
|
||||||
|
HTTPRequestEntityTooLarge, HTTPRequestURITooLong,
|
||||||
|
HTTPUnsupportedMediaType, HTTPRequestedRangeNotSatisfiable,
|
||||||
|
HTTPExpectationFailed, HTTPTeaPot,
|
||||||
|
HTTPNotImplemented, HTTPBadGateway,
|
||||||
|
HTTPServiceUnavailable, HTTPGatewayTimeout,
|
||||||
|
HTTPVersionNotSupported, HTTPInternalServerError])
|
||||||
|
|
||||||
|
|
||||||
|
def from_response(response, body):
|
||||||
|
"""
|
||||||
|
Return an instance of an ClientException or subclass
|
||||||
|
based on a Python Requests response.
|
||||||
|
|
||||||
|
Usage::
|
||||||
|
|
||||||
|
resp, body = http.request(...)
|
||||||
|
if resp.status != 200:
|
||||||
|
raise exception_from_response(resp, body)
|
||||||
|
|
||||||
|
"""
|
||||||
|
cls = _code_map.get(response.status, ClientException)
|
||||||
|
return cls(body)
|
26
rbd_iscsi_client/i18n.py
Normal file
26
rbd_iscsi_client/i18n.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Copyright 2014 IBM Corp.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""oslo.i18n integration module.
|
||||||
|
See https://docs.openstack.org/oslo.i18n/latest/ .
|
||||||
|
"""
|
||||||
|
|
||||||
|
import oslo_i18n as i18n
|
||||||
|
|
||||||
|
DOMAIN = 'rbd-iscsi-client'
|
||||||
|
|
||||||
|
_translators = i18n.TranslatorFactory(domain=DOMAIN)
|
||||||
|
|
||||||
|
# The primary translation function using the well-known name "_"
|
||||||
|
_ = _translators.primary
|
366
rbd_iscsi_client/rbd_iscsi_client.py
Normal file
366
rbd_iscsi_client/rbd_iscsi_client.py
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License."""
|
||||||
|
""" RBDISCSIClient.
|
||||||
|
|
||||||
|
.. module: rbd_iscsi_client
|
||||||
|
|
||||||
|
:Author: Walter A. Boring IV
|
||||||
|
:Description: This is the HTTP REST Client that is used to make calls to
|
||||||
|
the ceph-iscsi/rbd-target-api service running on the ceph iscsi gateway
|
||||||
|
host.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
|
from rbd_iscsi_client import exceptions
|
||||||
|
|
||||||
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
|
|
||||||
|
class RBDISCSIClient(object):
|
||||||
|
|
||||||
|
USER_AGENT = "os_client"
|
||||||
|
|
||||||
|
username = None
|
||||||
|
password = None
|
||||||
|
api_url = None
|
||||||
|
|
||||||
|
auth = None
|
||||||
|
http_log_debug = False
|
||||||
|
tries = 5
|
||||||
|
delay = 0
|
||||||
|
backoff = 2
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
retry_exceptions = (exceptions.HTTPServiceUnavailable,
|
||||||
|
requests.exceptions.ConnectionError)
|
||||||
|
|
||||||
|
def __init__(self, username, password, base_url,
|
||||||
|
suppress_ssl_warnings=False, timeout=None,
|
||||||
|
secure=False):
|
||||||
|
super(RBDISCSIClient, self).__init__()
|
||||||
|
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.api_url = base_url
|
||||||
|
self.timeout = timeout
|
||||||
|
self.secure = secure
|
||||||
|
|
||||||
|
self.times = []
|
||||||
|
|
||||||
|
if suppress_ssl_warnings:
|
||||||
|
requests.packages.urllib3.disable_warnings()
|
||||||
|
|
||||||
|
self.auth = HTTPBasicAuth(username, password)
|
||||||
|
|
||||||
|
def set_debug_flag(self, flag):
|
||||||
|
"""Turn on/off http request/response debugging."""
|
||||||
|
if not self.http_log_debug and flag:
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
self._logger.setLevel(logging.DEBUG)
|
||||||
|
self._logger.addHandler(ch)
|
||||||
|
self.http_log_debug = True
|
||||||
|
|
||||||
|
def _http_log_req(self, args, kwargs):
|
||||||
|
if not self.http_log_debug:
|
||||||
|
return
|
||||||
|
|
||||||
|
string_parts = ['curl -i']
|
||||||
|
for element in args:
|
||||||
|
if element in ('GET', 'POST'):
|
||||||
|
string_parts.append(' -X %s' % element)
|
||||||
|
else:
|
||||||
|
string_parts.append(' %s' % element)
|
||||||
|
|
||||||
|
for element in kwargs['headers']:
|
||||||
|
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
|
||||||
|
string_parts.append(header)
|
||||||
|
|
||||||
|
if 'data' in kwargs:
|
||||||
|
string_parts.append(' -d ')
|
||||||
|
for key in kwargs['data']:
|
||||||
|
string_parts.append('%(key)s=%(value)s&' %
|
||||||
|
{'key': key,
|
||||||
|
'value': kwargs['data'][key]})
|
||||||
|
|
||||||
|
self._logger.debug("\nREQ: %s\n" % "".join(string_parts))
|
||||||
|
|
||||||
|
def _http_log_resp(self, resp, body):
|
||||||
|
if not self.http_log_debug:
|
||||||
|
return
|
||||||
|
# Replace commas with newlines to break the debug into new lines,
|
||||||
|
# making it easier to read
|
||||||
|
self._logger.debug("RESP:%s\n", str(resp).replace("',", "'\n"))
|
||||||
|
self._logger.debug("RESP BODY:%s\n", body)
|
||||||
|
|
||||||
|
def request(self, *args, **kwargs):
|
||||||
|
"""Perform an HTTP Request.
|
||||||
|
|
||||||
|
You should use get, post, delete instead.
|
||||||
|
|
||||||
|
"""
|
||||||
|
kwargs.setdefault('headers', kwargs.get('headers', {}))
|
||||||
|
kwargs['headers']['User-Agent'] = self.USER_AGENT
|
||||||
|
kwargs['headers']['Accept'] = 'application/json'
|
||||||
|
if 'data' in kwargs:
|
||||||
|
payload = kwargs['data']
|
||||||
|
else:
|
||||||
|
payload = None
|
||||||
|
|
||||||
|
# args[0] contains the URL, args[1] contains the HTTP verb/method
|
||||||
|
http_url = args[0]
|
||||||
|
http_method = args[1]
|
||||||
|
|
||||||
|
self._http_log_req(args, kwargs)
|
||||||
|
r = None
|
||||||
|
resp = None
|
||||||
|
body = None
|
||||||
|
while r is None and self.tries > 0:
|
||||||
|
try:
|
||||||
|
# Check to see if the request is being retried. If it is, we
|
||||||
|
# want to delay.
|
||||||
|
if self.delay:
|
||||||
|
time.sleep(self.delay)
|
||||||
|
|
||||||
|
if self.timeout:
|
||||||
|
r = requests.request(http_method, http_url, data=payload,
|
||||||
|
headers=kwargs['headers'],
|
||||||
|
auth=self.auth,
|
||||||
|
verify=self.secure,
|
||||||
|
timeout=self.timeout)
|
||||||
|
else:
|
||||||
|
r = requests.request(http_method, http_url, data=payload,
|
||||||
|
auth=self.auth,
|
||||||
|
headers=kwargs['headers'],
|
||||||
|
verify=self.secure)
|
||||||
|
|
||||||
|
resp = r.headers
|
||||||
|
body = r.text
|
||||||
|
if isinstance(body, bytes):
|
||||||
|
body = body.decode('utf-8')
|
||||||
|
|
||||||
|
# resp['status'], status['content-location'], and resp.status
|
||||||
|
# need to be manually set as Python Requests doesn't provide
|
||||||
|
# them automatically.
|
||||||
|
resp['status'] = str(r.status_code)
|
||||||
|
resp.status = r.status_code
|
||||||
|
if 'location' not in resp:
|
||||||
|
resp['content-location'] = r.url
|
||||||
|
|
||||||
|
r.close()
|
||||||
|
self._http_log_resp(resp, body)
|
||||||
|
|
||||||
|
# Try and convert the body response to an object
|
||||||
|
# This assumes the body of the reply is JSON
|
||||||
|
if body:
|
||||||
|
try:
|
||||||
|
body = json.loads(body)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
body = None
|
||||||
|
|
||||||
|
if resp.status >= 400:
|
||||||
|
if body and 'message' in body:
|
||||||
|
body['desc'] = body['message']
|
||||||
|
|
||||||
|
raise exceptions.from_response(resp, body)
|
||||||
|
except requests.exceptions.SSLError as err:
|
||||||
|
self._logger.error(
|
||||||
|
"SSL certificate verification failed: (%s). You must have "
|
||||||
|
"a valid SSL certificate or disable SSL "
|
||||||
|
"verification.", err)
|
||||||
|
raise exceptions.SSLCertFailed(
|
||||||
|
"SSL Certificate Verification Failed.")
|
||||||
|
except self.retry_exceptions as ex:
|
||||||
|
# If we catch an exception where we want to retry, we need to
|
||||||
|
# decrement the retry count prepare to try again.
|
||||||
|
r = None
|
||||||
|
self.tries -= 1
|
||||||
|
self.delay = self.delay * self.backoff + 1
|
||||||
|
|
||||||
|
# Raise exception, we have exhausted all retries.
|
||||||
|
if self.tries is 0:
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
return resp, body
|
||||||
|
|
||||||
|
def _time_request(self, url, method, **kwargs):
|
||||||
|
start_time = time.time()
|
||||||
|
resp, body = self.request(url, method, **kwargs)
|
||||||
|
self.times.append(("%s %s" % (method, url),
|
||||||
|
start_time, time.time()))
|
||||||
|
return resp, body
|
||||||
|
|
||||||
|
def _cs_request(self, url, method, **kwargs):
|
||||||
|
resp, body = self._time_request(self.api_url + url, method,
|
||||||
|
**kwargs)
|
||||||
|
return resp, body
|
||||||
|
|
||||||
|
def get(self, url, **kwargs):
|
||||||
|
return self._cs_request(url, 'GET', **kwargs)
|
||||||
|
|
||||||
|
def post(self, url, **kwargs):
|
||||||
|
return self._cs_request(url, 'POST', **kwargs)
|
||||||
|
|
||||||
|
def put(self, url, **kwargs):
|
||||||
|
return self._cs_request(url, 'PUT', **kwargs)
|
||||||
|
|
||||||
|
def delete(self, url, **kwargs):
|
||||||
|
return self._cs_request(url, 'DELETE', **kwargs)
|
||||||
|
|
||||||
|
def get_api(self):
|
||||||
|
"""Get the API endpoints."""
|
||||||
|
return self.get("/api")
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
"""Get the complete config object."""
|
||||||
|
return self.get("/api/config")
|
||||||
|
|
||||||
|
def get_gatewayinfo(self):
|
||||||
|
"""Get the number of active sessions on local gateway."""
|
||||||
|
return self.get("/api/gatewayinfo")
|
||||||
|
|
||||||
|
def get_targets(self):
|
||||||
|
"""Get the list of targets defined in the config."""
|
||||||
|
api = "/api/targets"
|
||||||
|
return self.get(api)
|
||||||
|
|
||||||
|
def create_target_iqn(self, target_iqn, mode=None, controls=None):
|
||||||
|
"""Create the target iqn on the gateway."""
|
||||||
|
api = "/api/target/%(target_iqn)s" % {'target_iqn': target_iqn}
|
||||||
|
payload = {}
|
||||||
|
if mode:
|
||||||
|
payload['mode'] = mode
|
||||||
|
|
||||||
|
if controls:
|
||||||
|
payload['controls'] = controls
|
||||||
|
|
||||||
|
return self.put(api, data=payload)
|
||||||
|
|
||||||
|
def delete_target_iqn(self, target_iqn):
|
||||||
|
"""Delete a target iqn from the gateways."""
|
||||||
|
api = "/api/target/%(target_iqn)s" % {'target_iqn': target_iqn}
|
||||||
|
return self.delete(api)
|
||||||
|
|
||||||
|
def get_clients(self, target_iqn):
|
||||||
|
"""List clients defined to the configuration."""
|
||||||
|
api = "/api/clients/%(target_iqn)s" % {'target_iqn': target_iqn}
|
||||||
|
return self.get(api)
|
||||||
|
|
||||||
|
def get_client_info(self, target_iqn, client_iqn):
|
||||||
|
"""Fetch the Client information from the gateways.
|
||||||
|
|
||||||
|
Alias, IP address and state for each connected portal.
|
||||||
|
"""
|
||||||
|
api = ("/api/clientinfo/%(target_iqn)s/%(client_iqn)s" %
|
||||||
|
{'target_iqn': target_iqn,
|
||||||
|
'client_iqn': client_iqn})
|
||||||
|
return self.get(api)
|
||||||
|
|
||||||
|
def create_client(self, target_iqn, client_iqn):
|
||||||
|
"""Delete a client."""
|
||||||
|
api = ("/api/client/%(target_iqn)s/%(client_iqn)s" %
|
||||||
|
{'target_iqn': target_iqn,
|
||||||
|
'client_iqn': client_iqn})
|
||||||
|
return self.put(api)
|
||||||
|
|
||||||
|
def delete_client(self, target_iqn, client_iqn):
|
||||||
|
"""Delete a client."""
|
||||||
|
api = ("/api/client/%(target_iqn)s/%(client_iqn)s" %
|
||||||
|
{'target_iqn': target_iqn,
|
||||||
|
'client_iqn': client_iqn})
|
||||||
|
return self.delete(api)
|
||||||
|
|
||||||
|
def set_client_auth(self, target_iqn, client_iqn, username, password):
|
||||||
|
"""Set the client chap credentials."""
|
||||||
|
url = ("/api/clientauth/%(target_iqn)s/%(client_iqn)s" %
|
||||||
|
{'target_iqn': target_iqn,
|
||||||
|
'client_iqn': client_iqn})
|
||||||
|
args = {'username': username,
|
||||||
|
'password': password}
|
||||||
|
return self.put(url, data=args)
|
||||||
|
|
||||||
|
def get_disks(self):
|
||||||
|
"""Get the rbd disks defined to the gateways."""
|
||||||
|
return self.get("/api/disks")
|
||||||
|
|
||||||
|
def create_disk(self, pool, image, size=None, extras=None):
|
||||||
|
"""Add a disk to the gateway."""
|
||||||
|
url = ("/api/disk/%(pool)s/%(image)s" %
|
||||||
|
{'pool': pool,
|
||||||
|
'image': image})
|
||||||
|
args = {'pool': pool,
|
||||||
|
'image': image,
|
||||||
|
'mode': 'create'}
|
||||||
|
if size:
|
||||||
|
args['size'] = size
|
||||||
|
|
||||||
|
if extras:
|
||||||
|
args.update(extras)
|
||||||
|
return self.put(url, data=args)
|
||||||
|
|
||||||
|
def find_disk(self, pool, image):
|
||||||
|
"""Find the disk in the gateway."""
|
||||||
|
url = ("/api/disk/%(pool)s/%(image)s" %
|
||||||
|
{'pool': pool,
|
||||||
|
'image': image})
|
||||||
|
return self.get(url)
|
||||||
|
|
||||||
|
def delete_disk(self, pool, image):
|
||||||
|
"""delete a disk from the gateway."""
|
||||||
|
url = ("/api/disk/%(pool)s/%(image)s" %
|
||||||
|
{'pool': pool,
|
||||||
|
'image': image})
|
||||||
|
return self.delete(url)
|
||||||
|
|
||||||
|
def register_disk(self, target_iqn, volume):
|
||||||
|
"""Add the volume to the target definition.
|
||||||
|
|
||||||
|
This is done after the disk is created in a pool, and
|
||||||
|
before the disk can be exported to an initiator.
|
||||||
|
"""
|
||||||
|
url = ("/api/targetlun/%(target_iqn)s" %
|
||||||
|
{'target_iqn': target_iqn})
|
||||||
|
args = {'disk': volume}
|
||||||
|
return self.put(url, data=args)
|
||||||
|
|
||||||
|
def unregister_disk(self, target_iqn, volume):
|
||||||
|
"""Remove the volume from the target definition.
|
||||||
|
|
||||||
|
This is done after the disk is unexported from an initiator
|
||||||
|
and before the disk can be deleted from the gateway.
|
||||||
|
"""
|
||||||
|
url = ("/api/targetlun/%(target_iqn)s" %
|
||||||
|
{'target_iqn': target_iqn})
|
||||||
|
args = {'disk': volume}
|
||||||
|
return self.delete(url, data=args)
|
||||||
|
|
||||||
|
def export_disk(self, target_iqn, client_iqn, pool, disk):
|
||||||
|
"""Add a disk to export to a client."""
|
||||||
|
url = ("/api/clientlun/%(target_iqn)s/%(client_iqn)s" %
|
||||||
|
{'target_iqn': target_iqn,
|
||||||
|
'client_iqn': client_iqn})
|
||||||
|
args = {'disk': "%(pool)s/%(disk)s" % {'pool': pool, 'disk': disk},
|
||||||
|
'client_iqn': client_iqn}
|
||||||
|
return self.put(url, data=args)
|
||||||
|
|
||||||
|
def unexport_disk(self, target_iqn, client_iqn, pool, disk):
|
||||||
|
"""Remove a disk to export to a client."""
|
||||||
|
url = ("/api/clientlun/%(target_iqn)s/%(client_iqn)s" %
|
||||||
|
{'target_iqn': target_iqn,
|
||||||
|
'client_iqn': client_iqn})
|
||||||
|
args = {'disk': "%(pool)s/%(disk)s" % {'pool': pool, 'disk': disk}}
|
||||||
|
return self.delete(url, data=args)
|
0
rbd_iscsi_client/tests/__init__.py
Normal file
0
rbd_iscsi_client/tests/__init__.py
Normal file
22
rbd_iscsi_client/tests/test_rbd_iscsi_client.py
Normal file
22
rbd_iscsi_client/tests/test_rbd_iscsi_client.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""Tests for `rbd_iscsi_client` package."""
|
||||||
|
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from rbd_iscsi_client import rbd_iscsi_client
|
||||||
|
|
||||||
|
|
||||||
|
class TestRbd_iscsi_client(unittest.TestCase):
|
||||||
|
"""Tests for `rbd_iscsi_client` package."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up test fixtures, if any."""
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Tear down test fixtures, if any."""
|
||||||
|
|
||||||
|
def test_000_something(self):
|
||||||
|
"""Test something."""
|
11
requirements.txt
Normal file
11
requirements.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# The order of packages is significant, because pip processes them in the order
|
||||||
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
|
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||||
|
Babel!=2.4.0,>=2.3.4 # BSD
|
||||||
|
oslo.log>=3.36.0 # Apache-2.0
|
||||||
|
oslo.i18n>=3.15.3 # Apache-2.0
|
||||||
|
oslo.utils>=3.33.0 # Apache-2.0
|
||||||
|
requests>=2.14.2 # Apache-2.0
|
||||||
|
six>=1.10.0 # MIT
|
59
setup.cfg
Normal file
59
setup.cfg
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
[bdist_wheel]
|
||||||
|
universal = 1
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
name = rbd-iscsi-client
|
||||||
|
summary = "REST client to talk to rbd-target-api"
|
||||||
|
description_file =
|
||||||
|
README.rst
|
||||||
|
author = Walter A. Boring IV
|
||||||
|
author-email = waboring@hemna.com
|
||||||
|
home-page = http://github.com/hemna/rbd-iscsi-client
|
||||||
|
license_file = LICENSE
|
||||||
|
classifier =
|
||||||
|
Environment :: OpenStack
|
||||||
|
Intended Audience :: Information Technology
|
||||||
|
Intended Audience :: System Administrators
|
||||||
|
License :: OSI Approved :: Apache Software License
|
||||||
|
Natural Language :: English
|
||||||
|
Programming Language :: Python :: 2
|
||||||
|
Programming Language :: Python :: 2.7
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3.6
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
|
|
||||||
|
[global]
|
||||||
|
setup-hooks =
|
||||||
|
pbr.hooks.setup_hook
|
||||||
|
|
||||||
|
[files]
|
||||||
|
packages =
|
||||||
|
rbd_iscsi_client
|
||||||
|
|
||||||
|
[egg_info]
|
||||||
|
tag_build =
|
||||||
|
tag_date = 0
|
||||||
|
tag_svn_revision = 0
|
||||||
|
|
||||||
|
[build_sphinx]
|
||||||
|
warning-is-error = 1
|
||||||
|
source-dir = doc/source
|
||||||
|
build-dir = doc/build
|
||||||
|
all_files = 1
|
||||||
|
|
||||||
|
[upload_sphinx]
|
||||||
|
upload-dir = doc/build/html
|
||||||
|
|
||||||
|
[compile_catalog]
|
||||||
|
directory = rbd_iscsi_client/locale
|
||||||
|
domain = rbd_iscsi_client
|
||||||
|
|
||||||
|
[update_catalog]
|
||||||
|
domain = rbd_iscsi_client
|
||||||
|
output_dir = rbd_iscsi_client/locale
|
||||||
|
input_file = rbd_iscsi_client/locale/rbd_iscsi_client.pot
|
||||||
|
|
||||||
|
[extract_messages]
|
||||||
|
keywords = _ gettext ngettext l_ lazy_gettext
|
||||||
|
mapping_file = babel.cfg
|
||||||
|
output_file = rbd_iscsi_client/locale/rbd_iscsi_client.pot
|
27
setup.py
Normal file
27
setup.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||||
|
import setuptools
|
||||||
|
|
||||||
|
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||||
|
# setuptools if some other modules registered functions in `atexit`.
|
||||||
|
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||||
|
try:
|
||||||
|
import multiprocessing # noqa
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
setup_requires=['pbr>=2.0.0'],
|
||||||
|
pbr=True)
|
15
test-requirements.txt
Normal file
15
test-requirements.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
# The order of packages is significant, because pip processes them in the order
|
||||||
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
|
hacking>=1.1.0,<1.2.0 # Apache-2.0
|
||||||
|
coverage!=4.4,>=4.0 # Apache-2.0
|
||||||
|
ddt>=1.0.1 # MIT
|
||||||
|
reno>=2.5.0 # Apache-2.0
|
||||||
|
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
||||||
|
openstackdocstheme>=1.18.1 # Apache-2.0
|
||||||
|
oslotest>=3.2.0 # Apache-2.0
|
||||||
|
testscenarios>=0.4 # Apache-2.0/BSD
|
||||||
|
testtools>=2.2.0 # MIT
|
||||||
|
stestr>=1.0.0 # Apache-2.0
|
15
tools/fast8.sh
Executable file
15
tools/fast8.sh
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd $(dirname "$0")/..
|
||||||
|
CHANGED=$(git diff --name-only HEAD~1 | tr '\n' ' ')
|
||||||
|
|
||||||
|
# Skip files that don't exist
|
||||||
|
# (have been git rm'd)
|
||||||
|
CHECK=""
|
||||||
|
for FILE in $CHANGED; do
|
||||||
|
if [ -f "$FILE" ]; then
|
||||||
|
CHECK="$CHECK $FILE"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
diff -u --from-file /dev/null $CHECK | flake8 --diff
|
214
tools/lintstack.py
Executable file
214
tools/lintstack.py
Executable file
@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright (c) 2013, AT&T Labs, Yun Mao <yunmao@gmail.com>
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""pylint error checking."""
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pylint import lint
|
||||||
|
from pylint.reporters import text
|
||||||
|
from six.moves import cStringIO as StringIO
|
||||||
|
|
||||||
|
ignore_codes = [
|
||||||
|
# Note(maoy): E1103 is error code related to partial type inference
|
||||||
|
"E1103"
|
||||||
|
]
|
||||||
|
|
||||||
|
ignore_messages = [
|
||||||
|
# Note(fengqian): this message is the pattern of [E0611].
|
||||||
|
# It should be ignored because use six module to keep py3.X compatibility.
|
||||||
|
"No name 'urllib' in module '_MovedItems'",
|
||||||
|
|
||||||
|
# Note(xyang): these error messages are for the code [E1101].
|
||||||
|
# They should be ignored because 'sha256' and 'sha224' are functions in
|
||||||
|
# 'hashlib'.
|
||||||
|
"Module 'hashlib' has no 'sha256' member",
|
||||||
|
"Module 'hashlib' has no 'sha224' member",
|
||||||
|
]
|
||||||
|
|
||||||
|
ignore_modules = ["os_brick/tests/",
|
||||||
|
"tools/lintstack.head.py"]
|
||||||
|
|
||||||
|
KNOWN_PYLINT_EXCEPTIONS_FILE = "tools/pylint_exceptions"
|
||||||
|
|
||||||
|
|
||||||
|
class LintOutput(object):
|
||||||
|
|
||||||
|
_cached_filename = None
|
||||||
|
_cached_content = None
|
||||||
|
|
||||||
|
def __init__(self, filename, lineno, line_content, code, message,
|
||||||
|
lintoutput):
|
||||||
|
self.filename = filename
|
||||||
|
self.lineno = lineno
|
||||||
|
self.line_content = line_content
|
||||||
|
self.code = code
|
||||||
|
self.message = message
|
||||||
|
self.lintoutput = lintoutput
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_line(cls, line):
|
||||||
|
m = re.search(r"(\S+):(\d+): \[(\S+)(, \S+)?] (.*)", line)
|
||||||
|
matched = m.groups()
|
||||||
|
filename, lineno, code, message = (matched[0], int(matched[1]),
|
||||||
|
matched[2], matched[-1])
|
||||||
|
if cls._cached_filename != filename:
|
||||||
|
with open(filename) as f:
|
||||||
|
cls._cached_content = list(f.readlines())
|
||||||
|
cls._cached_filename = filename
|
||||||
|
line_content = cls._cached_content[lineno - 1].rstrip()
|
||||||
|
return cls(filename, lineno, line_content, code, message,
|
||||||
|
line.rstrip())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_msg_to_dict(cls, msg):
|
||||||
|
"""Converts pytlint message to a unique-error dictionary.
|
||||||
|
|
||||||
|
From the output of pylint msg, to a dict, where each key
|
||||||
|
is a unique error identifier, value is a list of LintOutput
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
for line in msg.splitlines():
|
||||||
|
obj = cls.from_line(line)
|
||||||
|
if obj.is_ignored():
|
||||||
|
continue
|
||||||
|
key = obj.key()
|
||||||
|
if key not in result:
|
||||||
|
result[key] = []
|
||||||
|
result[key].append(obj)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def is_ignored(self):
|
||||||
|
if self.code in ignore_codes:
|
||||||
|
return True
|
||||||
|
if any(self.filename.startswith(name) for name in ignore_modules):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def key(self):
|
||||||
|
if self.code in ["E1101", "E1103"]:
|
||||||
|
# These two types of errors are like Foo class has no member bar.
|
||||||
|
# We discard the source code so that the error will be ignored
|
||||||
|
# next time another Foo.bar is encountered.
|
||||||
|
return self.message, ""
|
||||||
|
return self.message, self.line_content.strip()
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return json.dumps(self.__dict__)
|
||||||
|
|
||||||
|
def review_str(self):
|
||||||
|
return ("File %(filename)s\nLine %(lineno)d:%(line_content)s\n"
|
||||||
|
"%(code)s: %(message)s" %
|
||||||
|
{'filename': self.filename,
|
||||||
|
'lineno': self.lineno,
|
||||||
|
'line_content': self.line_content,
|
||||||
|
'code': self.code,
|
||||||
|
'message': self.message})
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorKeys(object):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def print_json(cls, errors, output=sys.stdout):
|
||||||
|
print("# automatically generated by tools/lintstack.py", file=output)
|
||||||
|
for i in sorted(errors.keys()):
|
||||||
|
print(json.dumps(i), file=output)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_file(cls, filename):
|
||||||
|
keys = set()
|
||||||
|
for line in open(filename):
|
||||||
|
if line and line[0] != "#":
|
||||||
|
d = json.loads(line)
|
||||||
|
keys.add(tuple(d))
|
||||||
|
return keys
|
||||||
|
|
||||||
|
|
||||||
|
def run_pylint():
|
||||||
|
buff = StringIO()
|
||||||
|
reporter = text.ParseableTextReporter(output=buff)
|
||||||
|
args = ["--include-ids=y", "-E", "os_brick"]
|
||||||
|
lint.Run(args, reporter=reporter, exit=False)
|
||||||
|
val = buff.getvalue()
|
||||||
|
buff.close()
|
||||||
|
return val
|
||||||
|
|
||||||
|
|
||||||
|
def generate_error_keys(msg=None):
|
||||||
|
print("Generating", KNOWN_PYLINT_EXCEPTIONS_FILE)
|
||||||
|
if msg is None:
|
||||||
|
msg = run_pylint()
|
||||||
|
errors = LintOutput.from_msg_to_dict(msg)
|
||||||
|
with open(KNOWN_PYLINT_EXCEPTIONS_FILE, "w") as f:
|
||||||
|
ErrorKeys.print_json(errors, output=f)
|
||||||
|
|
||||||
|
|
||||||
|
def validate(newmsg=None):
|
||||||
|
print("Loading", KNOWN_PYLINT_EXCEPTIONS_FILE)
|
||||||
|
known = ErrorKeys.from_file(KNOWN_PYLINT_EXCEPTIONS_FILE)
|
||||||
|
if newmsg is None:
|
||||||
|
print("Running pylint. Be patient...")
|
||||||
|
newmsg = run_pylint()
|
||||||
|
errors = LintOutput.from_msg_to_dict(newmsg)
|
||||||
|
|
||||||
|
print("Unique errors reported by pylint: was %d, now %d."
|
||||||
|
% (len(known), len(errors)))
|
||||||
|
passed = True
|
||||||
|
for err_key, err_list in errors.items():
|
||||||
|
for err in err_list:
|
||||||
|
if err_key not in known:
|
||||||
|
print(err.lintoutput)
|
||||||
|
print()
|
||||||
|
passed = False
|
||||||
|
if passed:
|
||||||
|
print("Congrats! pylint check passed.")
|
||||||
|
redundant = known - set(errors.keys())
|
||||||
|
if redundant:
|
||||||
|
print("Extra credit: some known pylint exceptions disappeared.")
|
||||||
|
for i in sorted(redundant):
|
||||||
|
print(json.dumps(i))
|
||||||
|
print("Consider regenerating the exception file if you will.")
|
||||||
|
else:
|
||||||
|
print("Please fix the errors above. If you believe they are false "
|
||||||
|
"positives, run 'tools/lintstack.py generate' to overwrite.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print("""Usage: tools/lintstack.py [generate|validate]
|
||||||
|
To generate pylint_exceptions file: tools/lintstack.py generate
|
||||||
|
To validate the current commit: tools/lintstack.py
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
option = "validate"
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
option = sys.argv[1]
|
||||||
|
if option == "generate":
|
||||||
|
generate_error_keys()
|
||||||
|
elif option == "validate":
|
||||||
|
validate()
|
||||||
|
else:
|
||||||
|
usage()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
59
tools/lintstack.sh
Executable file
59
tools/lintstack.sh
Executable file
@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (c) 2012-2013, AT&T Labs, Yun Mao <yunmao@gmail.com>
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
# Use lintstack.py to compare pylint errors.
|
||||||
|
# We run pylint twice, once on HEAD, once on the code before the latest
|
||||||
|
# commit for review.
|
||||||
|
set -e
|
||||||
|
TOOLS_DIR=$(cd $(dirname "$0") && pwd)
|
||||||
|
# Get the current branch name.
|
||||||
|
GITHEAD=`git rev-parse --abbrev-ref HEAD`
|
||||||
|
if [[ "$GITHEAD" == "HEAD" ]]; then
|
||||||
|
# In detached head mode, get revision number instead
|
||||||
|
GITHEAD=`git rev-parse HEAD`
|
||||||
|
echo "Currently we are at commit $GITHEAD"
|
||||||
|
else
|
||||||
|
echo "Currently we are at branch $GITHEAD"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -f $TOOLS_DIR/lintstack.py $TOOLS_DIR/lintstack.head.py
|
||||||
|
|
||||||
|
if git rev-parse HEAD^2 2>/dev/null; then
|
||||||
|
# The HEAD is a Merge commit. Here, the patch to review is
|
||||||
|
# HEAD^2, the master branch is at HEAD^1, and the patch was
|
||||||
|
# written based on HEAD^2~1.
|
||||||
|
PREV_COMMIT=`git rev-parse HEAD^2~1`
|
||||||
|
git checkout HEAD~1
|
||||||
|
# The git merge is necessary for reviews with a series of patches.
|
||||||
|
# If not, this is a no-op so won't hurt either.
|
||||||
|
git merge $PREV_COMMIT
|
||||||
|
else
|
||||||
|
# The HEAD is not a merge commit. This won't happen on gerrit.
|
||||||
|
# Most likely you are running against your own patch locally.
|
||||||
|
# We assume the patch to examine is HEAD, and we compare it against
|
||||||
|
# HEAD~1
|
||||||
|
git checkout HEAD~1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# First generate tools/pylint_exceptions from HEAD~1
|
||||||
|
$TOOLS_DIR/lintstack.head.py generate
|
||||||
|
# Then use that as a reference to compare against HEAD
|
||||||
|
git checkout $GITHEAD
|
||||||
|
$TOOLS_DIR/lintstack.head.py
|
||||||
|
echo "Check passed. FYI: the pylint exceptions are:"
|
||||||
|
cat $TOOLS_DIR/pylint_exceptions
|
||||||
|
|
128
tox.ini
Normal file
128
tox.ini
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
[tox]
|
||||||
|
minversion = 2.0
|
||||||
|
envlist = py27,py36,py37,pep8
|
||||||
|
skipdist = True
|
||||||
|
|
||||||
|
[travis]
|
||||||
|
python =
|
||||||
|
3.7: py37
|
||||||
|
3.6: py36
|
||||||
|
2.7: py27
|
||||||
|
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
usedevelop = True
|
||||||
|
setenv =
|
||||||
|
VIRTUAL_EN={envdir}
|
||||||
|
OS_TEST_PATH=./rbd_iscsi_client/tests
|
||||||
|
OS_TEST_TIMEOUT=60
|
||||||
|
OS_STDOUT_CAPTURE=1
|
||||||
|
OS_STDERR_CAPTURE=1
|
||||||
|
PYTHONPATH = {toxinidir}
|
||||||
|
|
||||||
|
install_command = pip install {opts} {packages}
|
||||||
|
deps =
|
||||||
|
-c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
|
||||||
|
-r{toxinidir}/requirements.txt
|
||||||
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
|
||||||
|
commands =
|
||||||
|
stestr run {posargs}
|
||||||
|
stestr slowest
|
||||||
|
|
||||||
|
whitelist_externals = bash
|
||||||
|
find
|
||||||
|
passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
basepython = python3
|
||||||
|
envdir = {toxworkdir}/pep8
|
||||||
|
commands = flake8 {posargs}
|
||||||
|
|
||||||
|
[testenv:debug]
|
||||||
|
basepython = python3
|
||||||
|
commands =
|
||||||
|
find . type f -name "*.pyc" -delete
|
||||||
|
oslo_debug_helper {posargs}
|
||||||
|
|
||||||
|
[testenv:fast8]
|
||||||
|
basepython = python3
|
||||||
|
envdir = {toxworkdir}/pep8
|
||||||
|
commands =
|
||||||
|
{toxinidir}/tools/fast8.sh
|
||||||
|
|
||||||
|
[testenv:pylint]
|
||||||
|
basepython = python3
|
||||||
|
deps =
|
||||||
|
-c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/openstack/requirements/raw/branch/master/upper-constraints.txt}
|
||||||
|
-r{toxinidir}/requirements.txt
|
||||||
|
pylint==0.26.0
|
||||||
|
commands = bash tools/lintstack.sh
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
basepython = python3
|
||||||
|
commands = {posargs}
|
||||||
|
|
||||||
|
[testenv:cover]
|
||||||
|
basepython = python3
|
||||||
|
# To see the report of missing coverage add to commands
|
||||||
|
# coverage report --show-missing
|
||||||
|
setenv =
|
||||||
|
{[testenv]setenv}
|
||||||
|
PYTHON=coverage run --source rbd_iscsi_client --parallel-mode
|
||||||
|
commands =
|
||||||
|
stestr run {posargs}
|
||||||
|
coverage combine
|
||||||
|
coverage html -d cover
|
||||||
|
coverage xml -o cover/coverage/xml
|
||||||
|
|
||||||
|
[testenv:docs]
|
||||||
|
basepython = python3
|
||||||
|
commands = python setup.py build_sphinx
|
||||||
|
|
||||||
|
[testenv:releasenotes]
|
||||||
|
basepython = python3
|
||||||
|
commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
||||||
|
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
# Following checks are ignored on purpose.
|
||||||
|
#
|
||||||
|
# E251 unexpected spaces around keyword / parameter equals
|
||||||
|
# reason: no improvement in readability
|
||||||
|
# W503 line break before binary operator
|
||||||
|
# reason: pep8 itself is not sure about this one and
|
||||||
|
# reversed this rule in 2016
|
||||||
|
# W504 line break after binary operator
|
||||||
|
# reason: no agreement on this being universally
|
||||||
|
# preferable for our code. Disabled to keep checking
|
||||||
|
# tools from getting in our way with regards to this.
|
||||||
|
#
|
||||||
|
show-source = True
|
||||||
|
ignore = E251,W503,W504
|
||||||
|
enable-extensions=H106,H203,H204,H205
|
||||||
|
builtins = _
|
||||||
|
exclude=.venv,.git,.tox,dist,*lib/python*,*egg,build
|
||||||
|
max-complexity=30
|
||||||
|
|
||||||
|
[hacking]
|
||||||
|
import_exceptions = rbd_iscsi_client.i18n
|
||||||
|
|
||||||
|
[testenv:bindep]
|
||||||
|
basepython = python3
|
||||||
|
# Do not install any requirements. We want this to be fast and work even if
|
||||||
|
# system dependencies are missing, since it's used to tell you what system
|
||||||
|
# dependencies are missing! This also means that bindep must be installed
|
||||||
|
# separately, outside of the requirements files, and develop mode disabled
|
||||||
|
# explicitly to avoid unnecessarily installing the checked-out repo too (this
|
||||||
|
# further relies on "tox.skipsdist = True" above).
|
||||||
|
deps = bindep
|
||||||
|
commands = bindep test
|
||||||
|
usedevelop = False
|
||||||
|
|
||||||
|
[testenv:lower-constraints]
|
||||||
|
basepython = python3
|
||||||
|
deps =
|
||||||
|
-c{toxinidir}/lower-constraints.txt
|
||||||
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
-r{toxinidir}/requirements.txt
|
Loading…
Reference in New Issue
Block a user