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